diff options
author | Mike Greiling <mike@pixelcog.com> | 2018-10-24 15:12:38 +0000 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2018-10-24 15:12:38 +0000 |
commit | a6af6bf6e21bb5b36a94ba334c99bc586cb3297b (patch) | |
tree | 41a63f8d85f562573f45aa5898d749ceb51606a5 | |
parent | ac8f85805c0ff7dd00072b2974fdd05724863816 (diff) | |
parent | a1ee2072f1a7c197e13bd2d5f8ca59ad1deb1c49 (diff) | |
download | gitlab-ce-prettify-all-the-things-7.tar.gz |
Merge branch 'master' into 'prettify-all-the-things-7'prettify-all-the-things-7
# Conflicts:
# app/assets/javascripts/activities.js
893 files changed, 10495 insertions, 3695 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c652b6c75e2..ccc9e640970 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -139,7 +139,7 @@ stages: - export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}" - apk add --update openssl - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME - - chmod 755 $SCRIPT_NAME + - chmod 755 $(basename $SCRIPT_NAME) .rake-exec: &rake-exec <<: *dedicated-no-docs-no-db-pull-cache-job @@ -723,7 +723,7 @@ gitlab:assets:compile: - public/assets/ karma: - <<: *dedicated-no-docs-and-no-qa-pull-cache-job + <<: *dedicated-no-docs-pull-cache-job <<: *use-pg dependencies: - compile-assets @@ -929,3 +929,94 @@ no_ee_check: - scripts/no-ee-check only: - //@gitlab-org/gitlab-ce + +# GitLab Review apps +review: + image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base + stage: test + allow_failure: true + before_script: + - gem install gitlab --no-document + variables: + GIT_DEPTH: "1" + HOST_SUFFIX: "$CI_ENVIRONMENT_SLUG" + DOMAIN: "-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN" + GITLAB_HELM_CHART_REF: "master" + script: + - export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION) + - export GITALY_VERSION=$(<GITALY_SERVER_VERSION) + - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION) + - source ./scripts/review_apps/review-apps.sh + - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng + - check_kube_domain + - download_gitlab_chart + - ensure_namespace + - install_tiller + - create_secret + - install_external_dns + - deploy + environment: + name: review/$CI_COMMIT_REF_NAME + url: https://gitlab-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN + on_stop: stop_review + only: + refs: + - branches@gitlab-org/gitlab-ce + - branches@gitlab-org/gitlab-ee + kubernetes: active + except: + refs: + - master + - /(^docs[\/-].*|.*-docs$)/ + +stop_review: + <<: *single-script-job + image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base + stage: test + allow_failure: true + cache: {} + dependencies: [] + variables: + SCRIPT_NAME: "review_apps/review-apps.sh" + script: + - source $(basename "${SCRIPT_NAME}") + - delete + - cleanup + when: manual + environment: + name: review/$CI_COMMIT_REF_NAME + action: stop + only: + refs: + - branches@gitlab-org/gitlab-ce + - branches@gitlab-org/gitlab-ee + kubernetes: active + except: + - master + - /(^docs[\/-].*|.*-docs$)/ + +schedule:review_apps_cleanup: + <<: *dedicated-no-docs-pull-cache-job + image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base + stage: build + allow_failure: true + cache: {} + dependencies: [] + before_script: + - gem install gitlab --no-document + variables: + GIT_DEPTH: "1" + script: + - ruby -rrubygems scripts/review_apps/automated_cleanup.rb + environment: + name: review/auto-cleanup + action: stop + only: + refs: + - schedules@gitlab-org/gitlab-ce + - schedules@gitlab-org/gitlab-ee + kubernetes: active + except: + - master + - tags + - /(^docs[\/-].*|.*-docs$)/ diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index 64b54b171f7..69cf7fe1548 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -16,7 +16,6 @@ Set the title to: `[Security] Description of the original issue` - [ ] Add a link to the MR to the [links section](#links) - [ ] Add a link to an EE MR if required - [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**. -- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping. #### Backports @@ -26,7 +25,8 @@ Set the title to: `[Security] Description of the original issue` - [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable) - [ ] Create each MR targetting the security branch `security-X-Y` - [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR -- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager. +- [ ] Add the ~"Merge into Security" label to all of the MRs. +- [ ] Make sure all MRs have a link in the [links section](#links) [secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md index 580fab206b3..db8e30c419c 100644 --- a/.gitlab/issue_templates/Test plan.md +++ b/.gitlab/issue_templates/Test plan.md @@ -38,22 +38,22 @@ test plan](https://testing.googleblog.com/2011/09/10-minute-test-plan.html) and [this wiki page from an open-source tool that implements the ACC model](https://code.google.com/archive/p/test-analytics/wikis/AccExplained.wiki). --> -| | Simple | Secure | Responsive | Obvious | Stable | -|------------|:------:|:------:|:----------:|:-------:|:------:| -| Admin | | | | | | -| Groups | | | | | | -| Project | | | | | | -| Repository | | | | | | -| Issues | | | | | | -| MRs | | | | | | -| CI/CD | | | | | | -| Ops | | | | | | -| Registry | | | | | | -| Wiki | | | | | | -| Snippets | | | | | | -| Settings | | | | | | -| Tracking | | | | | | -| API | | | | | | +| | Secure | Responsive | Intuitive | Reliable | +|------------|:------:|:----------:|:---------:|:--------:| +| Admin | | | | | +| Groups | | | | | +| Project | | | | | +| Repository | | | | | +| Issues | | | | | +| MRs | | | | | +| CI/CD | | | | | +| Ops | | | | | +| Registry | | | | | +| Wiki | | | | | +| Snippets | | | | | +| Settings | | | | | +| Tracking | | | | | +| API | | | | | ## Capabilities @@ -65,7 +65,7 @@ more complex features could involve multiple or even all. Example (from https://gitlab.com/gitlab-org/gitlab-ce/issues/50353): * Respository is - * Simple + * Intuitive * It's easy to select the desired file template * It doesn't require unnecessary actions to save the change * It's easy to undo the change after selecting a template @@ -93,4 +93,4 @@ When adding new automated tests, please keep [testing levels](https://docs.gitla in mind. --> -/label ~Quality
\ No newline at end of file +/label ~Quality ~"test plan"
\ No newline at end of file diff --git a/.gitlab/merge_request_templates/Database changes.md b/.gitlab/merge_request_templates/Database changes.md index e636ec313df..354393b60e0 100644 --- a/.gitlab/merge_request_templates/Database changes.md +++ b/.gitlab/merge_request_templates/Database changes.md @@ -1,8 +1,23 @@ -Add a description of your merge request here. Merge requests without an adequate -description will not be reviewed until one is added. +## What does this MR do? + +<!-- +Describe in detail what your merge request does, why it does that, etc. Merge +requests without an adequate description will not be reviewed until one is +added. + +Please also keep this description up-to-date with any discussion that takes +place so that reviewers can understand your intent. This is especially +important if they didn't participate in the discussion. + +Make sure to remove this comment when you are done. +--> + +Add a description of your merge request here. ## Database checklist +- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#databases-guides) + When adding migrations: - [ ] Updated `db/schema.rb` @@ -35,16 +50,9 @@ When removing columns, tables, indexes or other structures: - [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary - [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs) -- [ ] [API support added](https://docs.gitlab.com/ee/development/api_styleguide.html) - [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html) -- Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html) - - [ ] Has been reviewed by a Backend [maintainer](https://about.gitlab.com/handbook/engineering/#maintainer) - - [ ] Has been reviewed by a Database [specialist](https://about.gitlab.com/team/structure/#specialist) +- [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html) - [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html) - [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides) -- [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) -- [ ] [Internationalization required/considered](https://docs.gitlab.com/ee/development/i18n/index.html) -- [ ] For a paid feature, have we considered GitLab.com plans, how it works for groups, and is there a design for promoting it to users who aren't on the correct plan? -- [ ] [End-to-end tests](https://docs.gitlab.com/ee/development/testing_guide/end_to_end_tests.html#testing-code-in-merge-requests) pass (`package-and-qa` manual pipeline job) /label ~database diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index ca38c881c66..8b7e7119790 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -19,6 +19,7 @@ Closes - [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process) - [ ] Crosslink the document from the higher-level index - [ ] Crosslink the document from other subject-related docs +- [ ] Feature moving tiers? Make sure the change is also reflected in [`features.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml) - [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers) - [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_ diff --git a/.prettierignore b/.prettierignore index b674ccd50cf..dc9e572ab54 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,7 @@ /public/ /vendor/ /tmp/ + +# ignore stylesheets for now as this clashes with our linter +*.css +*.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 2667c8a2fe1..2fc5b24aa39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,231 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.4.0 (2018-10-22) + +### Security (9 changes) + +- Filter user sensitive data from discussions JSON. !2536 +- Encrypt webhook tokens and URLs in the database. !21645 +- Redact confidential events in the API. +- Set timeout for syntax highlighting. +- Sanitize JSON data properly to fix XSS on Issue details page. +- Markdown API no longer displays confidential title references unless authorized. +- Properly filter private references from system notes. +- Fix stored XSS in merge requests from imported repository. +- Fix xss vulnerability sourced from package.json. + +### Removed (2 changes) + +- Remove background job throttling feature. !21748 +- Remove sidekiq info from performance bar. + +### Fixed (68 changes, 18 of them are from the community) + +- Fixes 500 for cherry pick API with empty branch name. !21501 (Jacopo Beschi @jacopo-beschi) +- Fix sorting by priority or popularity on group issues page, when also searching issue content. !21521 +- Fix vertical alignment of text in diffs. !21573 +- Fix performance bar modal position. !21577 +- Bump KaTeX version to 0.9.0. !21625 +- Correctly show legacy diff notes in the merge request changes tab. !21652 +- Synchronize the default branch when updating a remote mirror. !21653 +- Filter group milestones based on user membership. !21660 +- Fix double title in merge request chat messages. !21670 (Kukovskii Vladimir) +- Delete container repository tags outside of transaction. !21679 +- Images are no longer displayed in Todo descriptions. !21704 +- Fixed merge request widget discussion state not updating after resolving discussions. !21705 +- Vendor Auto-DevOps.gitlab-ci.yml to fix bug where the deploy job does not wait for Deployment to complete. !21713 +- Use Reliable Sidekiq fetch. !21715 +- No longer show open issues from archived projects in group issue board. !21721 +- Issue and MR count now ignores archived projects. !21721 +- Fix resizing of monitoring dashboard. !21730 +- Fix object storage uploads not working with AWS v2. !21731 +- Don't ignore first action when assign and unassign quick actions are used in the same comment. !21749 +- Align form labels following Bootstrap 4 docs. !21752 +- Respect the user commit email in more places. !21773 +- Use stats RPC when comparing diffs. !21778 +- Show commit details for selected commit in MR diffs. !21784 +- Resolve "Geo: Does not mark repositories as missing on primary due to stale cache". !21789 +- Fix leading slash in redirects and add rubocop cop. !21828 (Sanad Liaquat) +- Fix activity titles for MRs in chat notification services. !21834 +- Hides Close Merge request btn on merged Merge request. !21840 (Jacopo Beschi @jacopo-beschi) +- Doesn't synchronize the default branch for push mirrors. !21861 +- Fix broken styling when issue board is collapsed. !21868 (Andrea Leone) +- Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse. !21870 (David Piegza) +- Fix resolved discussions being unresolved when commented on. !21881 +- Fix timeout when running the RemoveRestrictedTodos background migration. !21893 +- Enable the ability to use the force env for rebuilding authorized_keys during a restore. !21896 +- Fix link handling for issue cards to avoid too sensitive drag events. !21910 (Johann Hubert Sonntagbauer) +- Guard against a login attempt with invalid CSRF token. !21934 +- Allow setting user's organization and location attributes through the API by adding them to the list of allowed parameters. !21938 (Alexis Reigel) +- Includes commit stats in POST project commits API. !21968 (Jacopo Beschi @jacopo-beschi) +- Fix loading issue on some merge request discussion. !21982 +- Prevent Error 500s with invalid relative links. !22001 +- Fix stale issue boards after browser back. !22006 (Johann Hubert Sonntagbauer) +- Filter issues without an Assignee via the API. !22009 (Eva Kadlecová) +- Fixes modal button alignment. !22024 (Jacopo Beschi @jacopo-beschi) +- Fix rendering placeholder notes. !22078 +- Instance Configuration page now displays correct SSH fingerprints. !22081 +- Fix showing diff file header for renamed files. !22089 +- Fix LFS uploaded images not being rendered. !22092 +- Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it. !22104 +- Trim whitespace when inviting a new user by email. !22119 (Jacopo Beschi @jacopo-beschi) +- Fix incorrect parent path on group settings page. !22142 +- Update copy to clipboard button data for application secret. !22268 (George Tsiolis) +- Improve MR file tree in smaller screens. !22273 +- Fix project deletion when there is a export available. !22276 +- Fixes stuck block URL linking to documentation instead of settings page. !22286 +- Fix caching issue with pipelines URL. !22293 +- Fix erased block not being rendered when job was erased. !22294 +- Load correct stage in the stages dropdown. !22317 +- Fixes close/reopen quick actions preview for issues and merge_requests. !22343 (Jacopo Beschi @jacopo-beschi) +- Allow Issue and Merge Request sidebar to be toggled from collapsed state. !22353 +- Fix filter bar height bug when a tag is added. +- Fix the state of the Done button when there is an error in the GitLab Todos section. (marcos8896) +- Fix wrong text color of help text in merge request creation. (Gerard Montemayor) +- Add borders and white background to markdown tables. +- Fixed mention autocomplete in edit merge request. +- Fix long webhook URL overflow for custom integration. (Kukovskii Vladimir) +- Fixed file templates not fully being fetched in Web IDE. +- Fixes performance bar looking for a key in a undefined prop. +- Hides sidebar for job page in mobile. +- Fixes triggered/created labeled in job header. + +### Changed (26 changes, 4 of them are from the community) + +- Enable unauthenticated access to public SSH keys via the API. !20118 (Ronald Claveau) +- Support Kubernetes RBAC for GitLab Managed Apps when creating new clusters. !21401 +- Highlight current user in comments. !21406 +- Excludes project marked from deletion to projects API. !21542 (Jacopo Beschi @jacopo-beschi) +- Improve install flow of Kubernetes cluster apps. !21567 +- Move including external files in .gitlab-ci.yml from Starter to Libre. !21603 +- Simplify runner registration token resetting. !21658 +- Filter any parameters ending with "key" in logs. !21688 +- Ensure the schema is loaded with post_migrations included. !21689 +- Updated icons used in filtered search dropdowns. !21694 +- Enable omniauth by default. !21700 +- Vendor Auto-DevOps.gitlab-ci.yml to refactor registry_login. !21714 (Laurent Goderre @LaurentGoderre) +- Add Gitaly diff stats RPC client. !21732 +- Allow user to revoke an authorized application even if User OAuth applications setting is disabled in admin settings. !21835 +- Change vertical margin of page titles to 16px. !21888 +- Preserve order of project tags list. !21897 +- Avoid close icon leaving the modal header. !21904 +- Allow /copy_metadata for new issues and MRs. !21953 +- Link to the tag for a version on the help page instead of to the commit. !22015 +- Show SHA for pre-release versions on the help page. !22026 +- Use local tiller for Auto DevOps. !22036 +- Remove 'rbac_clusters' feature flag. !22096 +- Increased retained event data by extending events pruner timeframe to 2 years. !22145 +- Add installation type to backup information file. !22150 +- Remove duplicate button from the markdown header toolbar. !22192 (George Tsiolis) +- Update to Rouge 3.3.0 including frozen string literals for improved memory usage. + +### Performance (17 changes, 6 of them are from the community) + +- Enable frozen string in app/controllers/**/*.rb. +- Improve lazy image loading performance by using IntersectionObserver where available. !21565 +- Adds support for Gitaly ListLastCommitsForTree RPC in order to make bulk-fetch of commits more performant. !21921 +- Dont create license_management build when not included in license. !21958 +- Skip creating auto devops jobs for sast, container_scanning, dast, dependency_scanning when not licensed. !21959 +- Reduce queries needed to compute notification recipients. !22050 +- Banzai label ref finder - minimize SQL calls by sharing context more aggresively. !22070 +- Removes expensive dead code on main MR page request. !22153 +- Lazy load xterm custom colors css. +- Mitigate N+1 queries when parsing commit references in comments. +- Enable more frozen string in app/controllers/. (gfyoung) +- Increase performance when creating discussions on diff. +- Enable frozen string in lib/api and lib/backup. (gfyoung) +- Enable frozen string in vestigial files. (gfyoung) +- Enable frozen string for app/helpers/**/*.rb. (gfyoung) +- Enable frozen string in app/graphql + app/finders. (gfyoung) +- Enable even more frozen string in app/controllers. (gfyoung) + +### Added (37 changes, 21 of them are from the community) + +- Allow file templates to be requested at the project level. !7776 +- Add /lock and /unlock quick actions. !15197 (Mehdi Lahmam (@mehlah)) +- Added search functionality for Work In Progress (WIP) merge requests. !18119 (Chantal Rollison) +- pipeline webhook event now contain pipeline variables. !18171 (Pierre Tardy) +- Add markdown header toolbar button to insert table. !18480 (George Tsiolis) +- Add link button to markdown editor toolbar. !18579 (Jan Beckmann) +- Add access control to GitLab pages and make it possible to enable/disable it in project settings. !18589 (Tuomo Ala-Vannesluoma) +- Add a filter bar to the admin runners view and add a state filter. !19625 (Alexis Reigel) +- Add a type filter to the admin runners view. !19649 (Alexis Reigel) +- Allow user to choose the email used for commits made through GitLab's UI. !21213 (Joshua Campbell) +- Add autocomplete drop down filter for project snippets. !21458 (Fabian Schneider) +- Allow events filter to be set in the URL in addition to cookie. !21557 (Igor @igas) +- Adds a initialize_with_readme parameter to POST /projects. !21617 (Steve) +- Add ability to skip user email confirmation with API. !21630 +- Add sorting for labels on labels page. !21642 +- Set user status from within user menu. !21643 +- Copy nurtch demo notebooks at Jupyter startup. !21698 (Amit Rathi) +- Allows to sort projects by most stars. !21762 (Jacopo Beschi @jacopo-beschi) +- Allow pipelines to schedule delayed job runs. !21767 +- Added tree of changed files to merge request diffs. !21833 +- Add GitLab version components to CI environment variables. !21853 +- Allows to chmod file with commits API. !21866 (Jacopo Beschi @jacopo-beschi) +- Make single diff patch limit configurable. !21886 +- Extend reports feature to support Security Products. !21892 +- Adds the user's public_email attribute to the API. !21909 (Alexis Reigel) +- Update all gitlab CI templates from gitlab-org/gitlab-ci-yml. !21929 +- Add support for setting the public email through the api. !21938 (Alexis Reigel) +- Support db migration and initialization for Auto DevOps. !21955 +- Add subscribe filter to group and project labels pages. !21965 +- Add support for pipeline only/except policy for modified paths. !21981 +- Docs for Project/Groups members API with inherited members. !21984 (Jacopo Beschi @jacopo-beschi) +- Adds Web IDE commits to usage ping. !22007 +- Add timed incremental rollout to Auto DevOps. !22023 +- Show percentage of language detection on the language bar. !22056 (Johann Hubert Sonntagbauer) +- Allows to filter issues by Any milestone in the API. !22080 (Jacopo Beschi @jacopo-beschi) +- Add button to download 2FA codes. (Luke Picciau) +- Render log artifact files in GitLab. + +### Other (42 changes, 16 of them are from the community) + +- Send deployment information in job API. !21307 +- Split admin settings into multiple sub pages. !21467 +- Remove Rugged and shell code from Gitlab::Git. !21488 +- Add trigger information in job API. !21495 +- Add empty state illustration information in job API. !21532 +- Add retried jobs to pipeline stage. !21558 +- Rails 5: fix issue move service In rails 5, the attributes method for an enum returns the name instead of the database integer. !21616 (Jasper Maes) +- Expose project runners in job API. !21618 +- create from template: hide checkbox for initializing repository with readme. !21646 +- Adds new 'Overview' tab on user profile page. !21663 +- Add clean-up phase for ScheduleDiffFilesDeletion migration. !21734 +- Prevents private profile help link from toggling checkbox. !21757 +- Make AutoDevOps work behind proxy. !21775 (Sergej - @kinolaev) +- Use Vue components and new API to render Artifacts, Trigger Variables and Commit blocks on Job page. !21777 +- Add wrapper rake task to migrate all uploads to OS. !21779 +- Retroactively fill pipeline source for external pipelines. !21814 +- Rename squash before merge vue component. !21851 (George Tsiolis) +- Fix merge request header margins. !21878 +- Fix committer typo. !21899 (George Tsiolis) +- Adds an extra width to the responsive tables. !21928 +- Expose has_trace in job API. !21950 +- Rename block scope local variable in table pagination spec. !21969 (George Tsiolis) +- Fix blue, orange, and red color inconsistencies. !21972 +- Update operations metrics empty state. !21974 (George Tsiolis) +- Improve empty project placeholder for non-members and members without write access. !21977 (George Tsiolis) +- Add copy to clipboard button for application id and secret. !21978 (George Tsiolis) +- Add link component to UserAvatarLink component. !21986 (George Tsiolis) +- Add link component to DownloadViewer component. !21987 (George Tsiolis) +- Rephrase 2FA and TOTP documentation and view. !21998 (Marc Schwede) +- Update project path on project name autofill. !22016 +- Improve logging when username update fails due to registry tags. !22038 +- Align collapsed sidebar avatar container. !22044 (George Tsiolis) +- Rails5: fix artifacts controller download spec Rails5 has params[:file_type] as '' if file_type is included as nil in the request. !22123 (Jasper Maes) +- Hide pagination for personal projects on profile overview tab. !22321 +- Extracts scroll position check into reusable functions. +- Uses Vuex store in job details page and removes old mediator pattern. +- Render 412 when invalid UTF-8 parameters are passed to controller. +- Renders Job show page in new Vue app. +- Add link to User Snippets in breadcrumbs of New User Snippet page. (J.D. Bean) +- Log project services errors when executing async. +- Update docs regarding frozen string. (gfyoung) +- Check frozen string in style builds. (gfyoung) + + ## 11.3.6 (2018-10-17) - No changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b33ef79558e..2dc8ac40dd4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ This [documentation](doc/development/contributing/index.md#security-vulnerabilit ## Code of Conduct -This [documentation](doc/development/contributing/index.md#code-of-conduct) has been moved. +This [documentation](https://about.gitlab.com/contributing/code-of-conduct/) has been moved. ## Closing policy for issues and merge requests diff --git a/Dangerfile b/Dangerfile index 10caacff4c4..469e77b2514 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,3 +1,4 @@ +danger.import_plugin('danger/plugins/helper.rb') danger.import_dangerfile(path: 'danger/metadata') danger.import_dangerfile(path: 'danger/changes_size') danger.import_dangerfile(path: 'danger/changelog') diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 33e061fe7a0..bcc9c2840a7 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.125.1 +0.126.0 @@ -417,8 +417,7 @@ end gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly' gem 'grpc', '~> 1.15.0' -# Locked until https://github.com/google/protobuf/issues/4210 is closed -gem 'google-protobuf', '= 3.5.1' +gem 'google-protobuf', '~> 3.6' gem 'toml-rb', '~> 1.0.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index a39788bee9f..bf16bef4f32 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -303,7 +303,7 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.5.1) + google-protobuf (3.6.1) googleapis-common-protos-types (1.0.2) google-protobuf (~> 3.0) googleauth (0.6.6) @@ -1005,7 +1005,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) google-api-client (~> 0.23) - google-protobuf (= 3.5.1) + google-protobuf (~> 3.6) gpgme grape (~> 1.1) grape-entity (~> 0.7.1) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 1421edb1d39..81547303ed2 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -306,7 +306,7 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.5.1) + google-protobuf (3.6.1) googleapis-common-protos-types (1.0.2) google-protobuf (~> 3.0) googleauth (0.6.6) @@ -1014,7 +1014,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) google-api-client (~> 0.23) - google-protobuf (= 3.5.1) + google-protobuf (~> 3.6) gpgme grape (~> 1.1) grape-entity (~> 0.7.1) diff --git a/README.md b/README.md index 335736e53f5..133c15a83a7 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the GitLab is a Ruby on Rails application that runs on the following software: - Ubuntu/Debian/CentOS/RHEL/OpenSUSE -- Ruby (MRI) 2.3 +- Ruby (MRI) 2.4 - Git 2.8.4+ - Redis 2.8+ - PostgreSQL (preferred) or MySQL @@ -1 +1 @@ -11.4.0-pre +11.5.0-pre diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index 29541e985be..05de970e387 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -6,8 +6,10 @@ import Pager from './pager'; import { localTimeAgo } from './lib/utils/datetime_utility'; export default class Activities { - constructor() { - Pager.init(20, true, false, data => data, this.updateTooltips); + constructor(container = '') { + this.container = container; + + Pager.init(20, true, false, data => data, this.updateTooltips, this.container); $('.event-filter-link').on('click', e => { e.preventDefault(); @@ -22,7 +24,7 @@ export default class Activities { reloadActivities() { $('.content_list').html(''); - Pager.init(20, true, false, data => data, this.updateTooltips); + Pager.init(20, true, false, data => data, this.updateTooltips, this.container); } toggleFilter(sender) { diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index 56f64f934a1..720f30e18e6 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -17,7 +17,7 @@ import flash from '~/flash'; export default function renderMermaid($els) { if (!$els.length) return; - import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid') + import(/* webpackChunkName: 'mermaid' */ 'mermaid') .then(mermaid => { mermaid.initialize({ // mermaid core options diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 852d71f4e84..37a3ceb5341 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -40,7 +40,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = ( }, selectable: true, filterable: true, - filterRemote: true, + filterRemote: !!$dropdown.data('refsUrl'), fieldName: $dropdown.data('fieldName'), filterInput: 'input[type="search"]', renderRow: function(ref) { diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index cfe4273742f..34e836a570a 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -1,17 +1,30 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; +import { TooltipDirective as Tooltip } from '@gitlab-org/gitlab-ui'; +import { convertPermissionToBoolean } from '~/lib/utils/common_utils'; import Icon from '~/vue_shared/components/icon.vue'; import FileRow from '~/vue_shared/components/file_row.vue'; import FileRowStats from './file_row_stats.vue'; +const treeListStorageKey = 'mr_diff_tree_list'; + export default { + directives: { + Tooltip, + }, components: { Icon, FileRow, }, data() { + const treeListStored = localStorage.getItem(treeListStorageKey); + const renderTreeList = treeListStored !== null ? + convertPermissionToBoolean(treeListStored) : true; + return { search: '', + renderTreeList, + focusSearch: false, }; }, computed: { @@ -20,15 +33,35 @@ export default { filteredTreeList() { const search = this.search.toLowerCase().trim(); - if (search === '') return this.tree; + if (search === '') return this.renderTreeList ? this.tree : this.allBlobs; return this.allBlobs.filter(f => f.name.toLowerCase().indexOf(search) >= 0); }, + rowDisplayTextKey() { + if (this.renderTreeList && this.search.trim() === '') { + return 'name'; + } + + return 'path'; + }, }, methods: { ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']), clearSearch() { this.search = ''; + this.toggleFocusSearch(false); + }, + toggleRenderTreeList(toggle) { + this.renderTreeList = toggle; + localStorage.setItem(treeListStorageKey, this.renderTreeList); + }, + toggleFocusSearch(toggle) { + this.focusSearch = toggle; + }, + blurSearch() { + if (this.search.trim() === '') { + this.toggleFocusSearch(false); + } }, }, FileRowStats, @@ -37,28 +70,67 @@ export default { <template> <div class="tree-list-holder d-flex flex-column"> - <div class="append-bottom-8 position-relative tree-list-search"> - <icon - name="search" - class="position-absolute tree-list-icon" - /> - <input - v-model="search" - :placeholder="s__('MergeRequest|Filter files')" - type="search" - class="form-control" - /> - <button - v-show="search" - :aria-label="__('Clear search')" - type="button" - class="position-absolute tree-list-icon tree-list-clear-icon border-0 p-0" - @click="clearSearch" - > + <div class="append-bottom-8 position-relative tree-list-search d-flex"> + <div class="flex-fill d-flex"> <icon - name="close" + name="search" + class="position-absolute tree-list-icon" + /> + <input + v-model="search" + :placeholder="s__('MergeRequest|Filter files')" + type="search" + class="form-control" + @focus="toggleFocusSearch(true)" + @blur="blurSearch" /> - </button> + <button + v-show="search" + :aria-label="__('Clear search')" + type="button" + class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0" + @click="clearSearch" + > + <icon + name="close" + /> + </button> + </div> + <div + v-show="!focusSearch" + class="btn-group prepend-left-8 tree-list-view-toggle" + > + <button + v-tooltip.hover + :aria-label="__('List view')" + :title="__('List view')" + :class="{ + active: !renderTreeList + }" + class="btn btn-default pt-0 pb-0 d-flex align-items-center" + type="button" + @click="toggleRenderTreeList(false)" + > + <icon + name="hamburger" + /> + </button> + <button + v-tooltip.hover + :aria-label="__('Tree view')" + :title="__('Tree view')" + :class="{ + active: renderTreeList + }" + class="btn btn-default pt-0 pb-0 d-flex align-items-center" + type="button" + @click="toggleRenderTreeList(true)" + > + <icon + name="file-tree" + /> + </button> + </div> </div> <div class="tree-list-scroll" @@ -72,6 +144,8 @@ export default { :hide-extra-on-tree="true" :extra-component="$options.FileRowStats" :show-changed-icon="true" + :display-text-key="rowDisplayTextKey" + :should-truncate-start="true" @toggleTreeOpen="toggleTreeOpen" @clickFile="scrollToFile" /> diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js index c4f0c41d3a8..b70125c80ca 100644 --- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js @@ -68,6 +68,11 @@ export const conditions = [ value: 'none', }, { + url: 'milestone_title=Any+Milestone', + tokenKey: 'milestone', + value: 'any', + }, + { url: 'milestone_title=%23upcoming', tokenKey: 'milestone', value: 'upcoming', diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index abb30d84fbc..749c09f897c 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -35,12 +35,12 @@ const createAction = config => ` </a> `; -const createFlashEl = (message, type, isInContentWrapper = false) => ` +const createFlashEl = (message, type, isFixedLayout = false) => ` <div class="flash-${type}" > <div - class="flash-text ${isInContentWrapper ? 'container-fluid container-limited' : ''}" + class="flash-text ${isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''}" > ${_.escape(message)} </div> @@ -74,12 +74,13 @@ const createFlash = function createFlash( addBodyClass = false, ) { const flashContainer = parent.querySelector('.flash-container'); + const navigation = parent.querySelector('.content'); if (!flashContainer) return null; - const isInContentWrapper = flashContainer.parentNode.classList.contains('content-wrapper'); + const isFixedLayout = navigation ? navigation.parentNode.classList.contains('container-limited') : true; - flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper); + flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout); const flashEl = flashContainer.querySelector(`.flash-${type}`); removeFlashClickListener(flashEl, fadeTransition); diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue index e2be805ed22..ec759043efc 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue @@ -25,14 +25,32 @@ export default { }, }, methods: { - createFile(target, file, isText) { + isText(content, fileType) { + const knownBinaryFileTypes = ['image/']; + const knownTextFileTypes = ['text/']; + const isKnownBinaryFileType = knownBinaryFileTypes.find(type => fileType.includes(type)); + const isKnownTextFileType = knownTextFileTypes.find(type => fileType.includes(type)); + const asciiRegex = /^[ -~\t\n\r]+$/; // tests whether a string contains ascii characters only (ranges from space to tilde, tabs and new lines) + + if (isKnownBinaryFileType) { + return false; + } + + if (isKnownTextFileType) { + return true; + } + + // if it's not a known file type, determine the type by evaluating the file contents + return asciiRegex.test(content); + }, + createFile(target, file) { const { name } = file; let { result } = target; + const encodedContent = result.split('base64,')[1]; + const rawContent = encodedContent ? atob(encodedContent) : ''; + const isText = this.isText(rawContent, file.type); - if (!isText) { - // eslint-disable-next-line prefer-destructuring - result = result.split('base64,')[1]; - } + result = isText ? rawContent : encodedContent; this.$emit('create', { name: `${this.path ? `${this.path}/` : ''}${name}`, @@ -43,15 +61,9 @@ export default { }, readFile(file) { const reader = new FileReader(); - const isText = file.type.match(/text.*/) !== null; - reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); - - if (isText) { - reader.readAsText(file); - } else { - reader.readAsDataURL(file); - } + reader.addEventListener('load', e => this.createFile(e.target, file), { once: true }); + reader.readAsDataURL(file); }, openFile() { Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index fa35b87ef2b..ba14aaeed2c 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -165,7 +165,7 @@ <gl-loading-icon v-if="isLoading" :size="2" - class="js-job-loading prepend-top-20" + class="js-job-loading qa-loading-animation prepend-top-20" /> <template v-else-if="shouldRenderContent"> @@ -217,8 +217,8 @@ /> <!--job log --> - <div - v-if="hasTrace" + <div + v-if="hasTrace" class="build-trace-container prepend-top-default"> <log-top-bar :class="{ diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue new file mode 100644 index 00000000000..6486b25c8a7 --- /dev/null +++ b/app/assets/javascripts/jobs/components/job_container_item.vue @@ -0,0 +1,65 @@ +<script> +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; + +export default { + components: { + CiIcon, + Icon, + }, + directives: { + tooltip, + }, + props: { + job: { + type: Object, + required: true, + }, + isActive: { + type: Boolean, + required: true, + }, + }, + computed: { + tooltipText() { + return `${this.job.name} - ${this.job.status.tooltip}`; + }, + }, +}; +</script> + +<template> + <div + class="build-job" + :class="{ + retried: job.retried, + active: isActive + }" + > + <a + v-tooltip + :href="job.status.details_path" + :title="tooltipText" + data-container="body" + data-boundary="viewport" + class="js-job-link" + > + <icon + v-if="isActive" + name="arrow-right" + class="js-arrow-right icon-arrow-right" + /> + + <ci-icon :status="job.status" /> + + <span>{{ job.name ? job.name : job.id }}</span> + + <icon + v-if="job.retried" + name="retry" + class="js-retry-icon" + /> + </a> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index accda5d1bd8..ffa6ada3e28 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -42,7 +42,7 @@ }; </script> <template> - <pre class="js-build-trace build-trace"> + <pre class="js-build-trace build-trace qa-build-trace"> <code class="bash" v-html="trace" diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue index 03f36ec5c8b..951bcb36600 100644 --- a/app/assets/javascripts/jobs/components/jobs_container.vue +++ b/app/assets/javascripts/jobs/components/jobs_container.vue @@ -1,17 +1,11 @@ <script> -import _ from 'underscore'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import Icon from '~/vue_shared/components/icon.vue'; -import tooltip from '~/vue_shared/directives/tooltip'; +import JobContainerItem from './job_container_item.vue'; export default { components: { - CiIcon, - Icon, - }, - directives: { - tooltip, + JobContainerItem, }, + props: { jobs: { type: Array, @@ -26,49 +20,16 @@ export default { isJobActive(currentJobId) { return this.jobId === currentJobId; }, - tooltipText(job) { - return `${_.escape(job.name)} - ${job.status.tooltip}`; - }, }, }; </script> <template> <div class="js-jobs-container builds-container"> - <div + <job-container-item v-for="job in jobs" :key="job.id" - class="build-job" - :class="{ retried: job.retried, active: isJobActive(job.id) }" - > - <a - v-tooltip - :href="job.status.details_path" - :title="tooltipText(job)" - data-container="body" - > - <icon - v-if="isJobActive(job.id)" - name="arrow-right" - class="js-arrow-right icon-arrow-right" - /> - - <ci-icon :status="job.status" /> - - <span> - <template v-if="job.name"> - {{ job.name }} - </template> - <template v-else> - {{ job.id }} - </template> - </span> - - <icon - v-if="job.retried" - name="retry" - class="js-retry-icon" - /> - </a> - </div> + :job="job" + :is-active="isJobActive(job.id)" + /> </div> </template> diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 03f3bb42193..2950c2299ab 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -419,7 +419,7 @@ export default class MergeRequestTabs { if (this.diffViewType() === 'parallel' || removeLimited) { $wrapper.removeClass('container-limited'); } else { - $wrapper.addClass('container-limited'); + $wrapper.toggleClass('container-limited', this.fixedLayoutPref); } } diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js index 8aabb840847..1c98683c597 100644 --- a/app/assets/javascripts/mr_notes/index.js +++ b/app/assets/javascripts/mr_notes/index.js @@ -4,6 +4,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; import initDiffsApp from '../diffs'; import notesApp from '../notes/components/notes_app.vue'; import discussionCounter from '../notes/components/discussion_counter.vue'; +import initDiscussionFilters from '../notes/discussion_filters'; import store from './stores'; import MergeRequest from '../merge_request'; @@ -88,5 +89,6 @@ export default function initMrNotes() { }, }); + initDiscussionFilters(store); initDiffsApp(store); } diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index ad6e7cf501d..1f80f24e045 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -56,10 +56,11 @@ export default { </script> <template> - <div class="line-resolve-all-container prepend-top-10"> + <div + v-if="discussionCount > 0" + class="line-resolve-all-container prepend-top-8"> <div> <div - v-if="discussionCount > 0" :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all"> <span diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue new file mode 100644 index 00000000000..27972682ca1 --- /dev/null +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -0,0 +1,82 @@ +<script> +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +import { mapGetters, mapActions } from 'vuex'; + +export default { + components: { + Icon, + }, + props: { + filters: { + type: Array, + required: true, + }, + defaultValue: { + type: Number, + default: null, + required: false, + }, + }, + data() { + return { currentValue: this.defaultValue }; + }, + computed: { + ...mapGetters([ + 'getNotesDataByProp', + ]), + currentFilter() { + if (!this.currentValue) return this.filters[0]; + return this.filters.find(filter => filter.value === this.currentValue); + }, + }, + methods: { + ...mapActions(['filterDiscussion']), + selectFilter(value) { + const filter = parseInt(value, 10); + + // close dropdown + $(this.$refs.dropdownToggle).dropdown('toggle'); + + if (filter === this.currentValue) return; + this.currentValue = filter; + this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter }); + }, + }, +}; +</script> + +<template> + <div class="discussion-filter-container d-inline-block align-bottom"> + <button + id="discussion-filter-dropdown" + ref="dropdownToggle" + class="btn btn-default" + data-toggle="dropdown" + aria-expanded="false" + > + {{ currentFilter.title }} + <icon name="chevron-down" /> + </button> + <div + class="dropdown-menu dropdown-menu-selectable dropdown-menu-right" + aria-labelledby="discussion-filter-dropdown"> + <div class="dropdown-content"> + <ul> + <li + v-for="filter in filters" + :key="filter.value" + > + <button + :class="{ 'is-active': filter.value === currentValue }" + type="button" + @click="selectFilter(filter.value)" + > + {{ filter.title }} + </button> + </li> + </ul> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 618a1581d8f..b0faa443a18 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -50,11 +50,11 @@ export default { }, data() { return { - isLoading: true, + currentFilter: null, }; }, computed: { - ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount']), + ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']), noteableType() { return this.noteableData.noteableType; }, @@ -102,6 +102,7 @@ export default { }, methods: { ...mapActions({ + setLoadingState: 'setLoadingState', fetchDiscussions: 'fetchDiscussions', poll: 'poll', actionToggleAward: 'toggleAward', @@ -133,19 +134,19 @@ export default { return discussion.individual_note ? { note: discussion.notes[0] } : { discussion }; }, fetchNotes() { - return this.fetchDiscussions(this.getNotesDataByProp('discussionsPath')) + return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') }) .then(() => { this.initPolling(); }) .then(() => { - this.isLoading = false; + this.setLoadingState(false); this.setNotesFetchedState(true); eventHub.$emit('fetchedNotesData'); }) .then(() => this.$nextTick()) .then(() => this.checkLocationHash()) .catch(() => { - this.isLoading = false; + this.setLoadingState(false); this.setNotesFetchedState(true); Flash('Something went wrong while fetching comments. Please try again.'); }); diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js new file mode 100644 index 00000000000..012ffc4093e --- /dev/null +++ b/app/assets/javascripts/notes/discussion_filters.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import DiscussionFilter from './components/discussion_filter.vue'; + +export default (store) => { + const discussionFilterEl = document.getElementById('js-vue-discussion-filter'); + + if (discussionFilterEl) { + const { defaultFilter, notesFilters } = discussionFilterEl.dataset; + const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null; + const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; + const filters = Object.keys(filterValues).map(entry => + ({ title: entry, value: filterValues[entry] })); + + return new Vue({ + el: discussionFilterEl, + name: 'DiscussionFilter', + components: { + DiscussionFilter, + }, + store, + render(createElement) { + return createElement('discussion-filter', { + props: { + filters, + defaultValue, + }, + }); + }, + }); + } + + return null; +}; diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 3aef30c608c..2f715c85fa6 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,10 +1,13 @@ import Vue from 'vue'; import notesApp from './components/notes_app.vue'; +import initDiscussionFilters from './discussion_filters'; import createStore from './stores'; document.addEventListener('DOMContentLoaded', () => { const store = createStore(); + initDiscussionFilters(store); + return new Vue({ el: '#js-vue-notes', components: { diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index f5dce94caad..47a6f07cce2 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -5,8 +5,9 @@ import * as constants from '../constants'; Vue.use(VueResource); export default { - fetchDiscussions(endpoint) { - return Vue.http.get(endpoint); + fetchDiscussions(endpoint, filter) { + const config = filter !== undefined ? { params: { notes_filter: filter } } : null; + return Vue.http.get(endpoint, config); }, deleteNote(endpoint) { return Vue.http.delete(endpoint); diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 7ab7e5a9abb..b5dd49bc6c9 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -11,6 +11,7 @@ import loadAwardsHandler from '../../awards_handler'; import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub'; +import { __ } from '~/locale'; let eTagPoll; @@ -36,9 +37,9 @@ export const setNotesFetchedState = ({ commit }, state) => export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data); -export const fetchDiscussions = ({ commit }, path) => +export const fetchDiscussions = ({ commit }, { path, filter }) => service - .fetchDiscussions(path) + .fetchDiscussions(path, filter) .then(res => res.json()) .then(discussions => { commit(types.SET_INITIAL_DISCUSSIONS, discussions); @@ -251,7 +252,7 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { if (discussion) { commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); } else if (note.type === constants.DIFF_NOTE) { - dispatch('fetchDiscussions', state.notesData.discussionsPath); + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); } else { commit(types.ADD_NEW_NOTE, note); } @@ -345,5 +346,23 @@ export const updateMergeRequestWidget = () => { mrWidgetEventHub.$emit('mr.discussion.updated'); }; +export const setLoadingState = ({ commit }, data) => { + commit(types.SET_NOTES_LOADING_STATE, data); +}; + +export const filterDiscussion = ({ dispatch }, { path, filter }) => { + dispatch('setLoadingState', true); + dispatch('fetchDiscussions', { path, filter }) + .then(() => { + dispatch('setLoadingState', false); + dispatch('setNotesFetchedState', true); + }) + .catch(() => { + dispatch('setLoadingState', false); + dispatch('setNotesFetchedState', true); + Flash(__('Something went wrong while fetching comments. Please try again.')); + }); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index a829149a17e..21c334a9d33 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -11,6 +11,8 @@ export const getNotesData = state => state.notesData; export const isNotesFetched = state => state.isNotesFetched; +export const isLoading = state => state.isLoading; + export const getNotesDataByProp = state => prop => state.notesData[prop]; export const getNoteableData = state => state.noteableData; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 61dbb075586..400142668ea 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -11,6 +11,7 @@ export default () => ({ // View layer isToggleStateButtonLoading: false, isNotesFetched: false, + isLoading: true, // holds endpoints and permissions provided through haml notesData: { diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 6f374f78691..2fa53aef1d4 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE'; export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; 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'; // 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 73e55705f39..65085452139 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -216,6 +216,10 @@ export default { Object.assign(state, { isNotesFetched: value }); }, + [types.SET_NOTES_LOADING_STATE](state, value) { + state.isLoading = value; + }, + [types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) { const discussion = utils.findNoteObjectById(state.discussions, discussionId); diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js index 3b58c54b3f4..386a9b2c740 100644 --- a/app/assets/javascripts/pager.js +++ b/app/assets/javascripts/pager.js @@ -7,14 +7,21 @@ const ENDLESS_SCROLL_BOTTOM_PX = 400; const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000; export default { - init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) { + init( + limit = 0, + preload = false, + disable = false, + prepareData = $.noop, + callback = $.noop, + container = '', + ) { this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']); this.limit = limit; this.offset = parseInt(getParameterByName('offset'), 10) || this.limit; this.disable = disable; this.prepareData = prepareData; this.callback = callback; - this.loading = $('.loading').first(); + this.loading = $(`${container} .loading`).first(); if (preload) { this.offset = 0; this.getOld(); diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index d0bce857029..32b55575f95 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -5,6 +5,7 @@ import initSettingsPanels from '~/settings_panels'; import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory'; import mountBadgeSettings from '~/pages/shared/mount_badge_settings'; import { GROUP_BADGE } from '~/badges/constants'; +import projectSelect from '~/project_select'; document.addEventListener('DOMContentLoaded', () => { groupAvatar(); @@ -15,4 +16,6 @@ document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'), ); mountBadgeSettings(GROUP_BADGE); + + projectSelect(); }); diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 1de9945baad..04bcb16f036 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -170,7 +170,7 @@ export default class UserTabs { this.loadActivityCalendar('activity'); // eslint-disable-next-line no-new - new Activities(); + new Activities('#activity'); this.loaded.activity = true; } diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue index fb8c6402d02..b373d83a44b 100644 --- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue +++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue @@ -82,7 +82,7 @@ :loading-text="groupedSummaryText" :error-text="groupedSummaryText" :has-issues="reports.length > 0" - class="mr-widget-border-top grouped-security-reports mr-report" + class="mr-widget-section grouped-security-reports mr-report" > <div slot="body" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 9161f703697..6c87287a4c4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -1,6 +1,7 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import tooltip from '../../vue_shared/directives/tooltip'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; @@ -18,6 +19,7 @@ export default { StatusIcon, Icon, TooltipOnTruncate, + FilteredSearchDropdown, }, directives: { tooltip, @@ -30,8 +32,10 @@ export default { }, }, data() { + const features = window.gon.features || {}; return { isStopping: false, + enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges, }; }, computed: { @@ -118,18 +122,65 @@ export default { /> </div> <div> - <a - v-if="hasExternalUrls" - :href="deployment.external_url" - target="_blank" - rel="noopener noreferrer nofollow" - class="deploy-link js-deploy-url btn btn-default btn-sm inline" - > - <span> - View app - <icon name="external-link" /> - </span> - </a> + <template v-if="hasExternalUrls"> + <filtered-search-dropdown + v-if="enableCiEnvironmentsStatusChanges" + class="js-mr-wigdet-deployment-dropdown inline" + :items="deployment.changes" + :main-action-link="deployment.external_url" + filter-key="path" + > + <template + slot="mainAction" + slot-scope="slotProps" + > + <a + :href="deployment.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="deploy-link js-deploy-url inline" + :class="slotProps.className" + > + <span> + {{ __('View app') }} + <icon name="external-link" /> + </span> + </a> + </template> + + <template + slot="result" + slot-scope="slotProps" + > + <a + :href="slotProps.result.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="menu-item" + > + <strong class="str-truncated-100 append-bottom-0 d-block"> + {{ slotProps.result.path }} + </strong> + + <p class="text-secondary str-truncated-100 append-bottom-0 d-block"> + {{ slotProps.result.external_url }} + </p> + </a> + </template> + </filtered-search-dropdown> + <a + v-else + :href="deployment.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline" + > + <span> + {{ __('View app') }} + <icon name="external-link" /> + </span> + </a> + </template> <loading-button v-if="deployment.stop_url" :loading="isStopping" 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 7ac3fedb2e3..8180f13a7cb 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 @@ -112,7 +112,8 @@ export default { eventHub.$on('mr.discussion.updated', this.checkStatus); }, mounted() { - this.handleMounted(); + this.setFaviconHelper(); + this.initDeploymentsPolling(); }, beforeDestroy() { eventHub.$off('mr.discussion.updated', this.checkStatus); @@ -250,10 +251,6 @@ export default { this.stopPolling(); }); }, - handleMounted() { - this.setFaviconHelper(); - this.initDeploymentsPolling(); - }, }, }; </script> @@ -275,12 +272,13 @@ export default { :key="deployment.id" :deployment="deployment" /> - <grouped-test-reports-app - v-if="mr.testResultsPath" - class="js-reports-container" - :endpoint="mr.testResultsPath" - /> <div class="mr-section-container"> + <grouped-test-reports-app + v-if="mr.testResultsPath" + class="js-reports-container" + :endpoint="mr.testResultsPath" + /> + <div class="mr-widget-section"> <component :is="componentName" diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue index a2518e2a611..c60052fec50 100644 --- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue +++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue @@ -43,7 +43,7 @@ export default { computed: { cssClass() { const className = this.status.group; - return className ? `ci-status ci-${className}` : 'ci-status'; + return className ? `ci-status ci-${className} qa-status-badge` : 'ci-status qa-status-badge'; }, }, }; diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js index 19611b14be4..bffaa096210 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -549,6 +549,7 @@ const fileNameIcons = { jenkinsfile: 'jenkins', 'firebase.json': 'firebase', '.firebaserc': 'firebase', + Rakefile: 'ruby', 'rollup.config.js': 'rollup', 'rollup.config.ts': 'rollup', 'rollup-config.js': 'rollup', diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index 36a345130c0..2d89a156117 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -34,10 +34,21 @@ export default { required: false, default: false, }, + displayTextKey: { + type: String, + required: false, + default: 'name', + }, + shouldTruncateStart: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { mouseOver: false, + truncateStart: 0, }; }, computed: { @@ -60,6 +71,15 @@ export default { 'is-open': this.file.opened, }; }, + outputText() { + const text = this.file[this.displayTextKey]; + + if (this.truncateStart === 0) { + return text; + } + + return `...${text.substring(this.truncateStart, text.length)}`; + }, }, watch: { 'file.active': function fileActiveWatch(active) { @@ -72,6 +92,15 @@ export default { if (this.hasPathAtCurrentRoute()) { this.scrollIntoView(true); } + + if (this.shouldTruncateStart) { + const { scrollWidth, offsetWidth } = this.$refs.textOutput; + const textOverflow = scrollWidth - offsetWidth; + + if (textOverflow > 0) { + this.truncateStart = Math.ceil(textOverflow / 5) + 3; + } + } }, methods: { toggleTreeOpen(path) { @@ -139,6 +168,7 @@ export default { class="file-row-name-container" > <span + ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated" > @@ -156,7 +186,7 @@ export default { :size="16" class="append-right-5" /> - {{ file.name }} + {{ outputText }} </span> <component :is="extraComponent" @@ -175,6 +205,8 @@ export default { :hide-extra-on-tree="hideExtraOnTree" :extra-component="extraComponent" :show-changed-icon="showChangedIcon" + :display-text-key="displayTextKey" + :should-truncate-start="shouldTruncateStart" @toggleTreeOpen="toggleTreeOpen" @clickFile="clickedFile" /> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue new file mode 100644 index 00000000000..460fa6ad72e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue @@ -0,0 +1,143 @@ +<script> +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +/** + * Renders a split dropdown with + * an input that allows to search through the given + * array of options. + */ +export default { + name: 'FilteredSearchDropdown', + components: { + Icon, + }, + props: { + title: { + type: String, + required: false, + default: '', + }, + buttonType: { + required: false, + validator: value => + ['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf( + value, + ) !== -1, + default: 'default', + }, + size: { + required: false, + type: String, + default: 'sm', + }, + items: { + type: Array, + required: true, + }, + visibleItems: { + type: Number, + required: false, + default: 5, + }, + filterKey: { + type: String, + required: true, + }, + }, + data() { + return { + filter: '', + }; + }, + computed: { + className() { + return `btn btn-${this.buttonType} btn-${this.size}`; + }, + filteredResults() { + if (this.filter !== '') { + return this.items.filter( + item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()), + ); + } + + return this.items.slice(0, this.visibleItems); + } + }, + mounted() { + /** + * Resets the filter every time the user closes the dropdown + */ + $(this.$el) + .on('shown.bs.dropdown', () => { + this.$nextTick(() => this.$refs.searchInput.focus()); + }) + .on('hidden.bs.dropdown', () => { + this.filter = ''; + }); + }, +}; +</script> +<template> + <div class="dropdown"> + <div class="btn-group"> + <slot + name="mainAction" + :class-name="className" + > + <button + type="button" + :class="className" + > + {{ title }} + </button> + </slot> + + <button + type="button" + :class="className" + class="dropdown-toggle dropdown-toggle-split" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + aria-label="Expand dropdown" + > + <icon + name="angle-down" + :size="12" + /> + </button> + <div class="dropdown-menu dropdown-menu-right"> + <div class="dropdown-input"> + <input + ref="searchInput" + v-model="filter" + type="search" + placeholder="Filter" + class="js-filtered-dropdown-input dropdown-input-field" + /> + <icon + class="dropdown-input-search" + name="search" + /> + </div> + + <div class="dropdown-content"> + <ul> + <li + v-for="(result, i) in filteredResults" + :key="i" + class="js-filtered-dropdown-result" + > + <slot + name="result" + :result="result" + > + {{ result[filterKey] }} + </slot> + </li> + </ul> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 704adf7864f..3ddb39730c4 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -1,16 +1,16 @@ <script> import $ from 'jquery'; -import tooltip from '../../directives/tooltip'; -import toolbarButton from './toolbar_button.vue'; -import icon from '../icon.vue'; +import Tooltip from '../../directives/tooltip'; +import ToolbarButton from './toolbar_button.vue'; +import Icon from '../icon.vue'; export default { directives: { - tooltip, + Tooltip, }, components: { - toolbarButton, - icon, + ToolbarButton, + Icon, }, props: { previewMarkdown: { @@ -68,27 +68,27 @@ export default { :class="{ active: !previewMarkdown }" class="md-header-tab" > - <a + <button class="js-write-link" - href="#md-write-holder" tabindex="-1" - @click.prevent="writeMarkdownTab($event)" + type="button" + @click="writeMarkdownTab($event)" > Write - </a> + </button> </li> <li :class="{ active: previewMarkdown }" class="md-header-tab" > - <a + <button class="js-preview-link js-md-preview-button" - href="#md-preview-holder" tabindex="-1" - @click.prevent="previewMarkdownTab($event)" + type="button" + @click="previewMarkdownTab($event)" > Preview - </a> + </button> </li> <li :class="{ active: !previewMarkdown }" diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index af73954bd2e..1e00aa4ff7e 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -238,10 +238,6 @@ h3.popover-header { } .card { - .card-title { - margin-bottom: 0; - } - &.card-without-border { @extend .border-0; } @@ -255,13 +251,6 @@ h3.popover-header { } } -.card-header { - h3.card-title, - h4.card-title { - margin-top: 0; - } -} - .nav-tabs { // Override bootstrap's default border border-bottom: 0; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 4ffb3e9ab42..4041f2b4479 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -51,6 +51,7 @@ @import 'framework/blank'; @import 'framework/wells'; @import 'framework/page_header'; +@import 'framework/page_title'; @import 'framework/awards'; @import 'framework/images'; @import 'framework/broadcast_messages'; diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 50ebc6d0dd1..b8bb9e1e07b 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -161,6 +161,7 @@ .nav-links li { &.active a, + &.md-header-tab.active button, a.active { border-bottom: 2px solid $active-tab-border; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index c430009bfe0..d1ce3a582bb 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -530,9 +530,6 @@ .header-user { &.show .dropdown-menu { - width: auto; - min-width: unset; - max-height: 323px; margin-top: 4px; color: $gl-text-color; left: auto; @@ -544,15 +541,19 @@ display: block; } - .user-status-emoji { + .user-status { margin-right: 0; - display: block; - vertical-align: text-top; - max-width: 148px; - font-size: 12px; + max-width: 240px; + font-size: $gl-font-size-small; gl-emoji { - font-size: $gl-font-size; + font-size: $gl-font-size-small; + } + + .user-status-emoji { + gl-emoji { + font-size: $gl-font-size; + } } } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 554e2b6720a..3142f94b192 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -72,6 +72,7 @@ .md-header-tab { @include media-breakpoint-down(xs) { flex: 1; + flex-direction: column; width: 100%; border-bottom: 1px solid $border-color; text-align: center; diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss new file mode 100644 index 00000000000..e8302953a63 --- /dev/null +++ b/app/assets/stylesheets/framework/page_title.scss @@ -0,0 +1,18 @@ +.page-title-holder { + @extend .d-flex; + @extend .align-items-center; + + padding-top: $gl-padding-top; + border-bottom: 1px solid $border-color; + + .page-title { + margin: $gl-padding 0; + font-size: 1.75em; + font-weight: $gl-font-weight-bold; + color: $gl-text-color; + } + + .page-title-controls { + margin-left: auto; + } +} diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index 5ca4d944d73..3a117106cff 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -53,8 +53,3 @@ margin-top: $gl-padding; } } - -.card-title { - font-size: inherit; - line-height: inherit; -} diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 8bab8cf36b1..f47dfe1b563 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -8,15 +8,17 @@ height: auto; border-bottom: 1px solid $border-color; - li { + li:not(.md-header-toolbar) { display: flex; - a { + a, + button { padding: $gl-btn-padding; padding-bottom: 11px; font-size: 14px; line-height: 28px; color: $gl-text-color-secondary; + border: 0; border-bottom: 2px solid transparent; white-space: nowrap; @@ -33,7 +35,13 @@ } } + button { + padding-top: 0; + background-color: transparent; + } + &.active a, + &.active button, a.active { color: $black; font-weight: $gl-font-weight-bold; @@ -42,6 +50,10 @@ color: $black; } } + + &.md-header-tab button { + line-height: 19px; + } } } diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss index 7cda674e5c8..3f4be8829d7 100644 --- a/app/assets/stylesheets/framework/terms.scss +++ b/app/assets/stylesheets/framework/terms.scss @@ -19,17 +19,12 @@ justify-content: space-between; line-height: $line-height-base; - .card-title { + .logo-text { + width: 55px; + height: 24px; display: flex; - align-items: center; - - .logo-text { - width: 55px; - height: 24px; - display: flex; - flex-direction: column; - justify-content: center; - } + flex-direction: column; + justify-content: center; } .navbar-collapse { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 0fde6e18cc7..ad66a0365ed 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -194,6 +194,7 @@ $well-light-text-color: #5b6169; * Text */ $gl-font-size: 14px; +$gl-font-size-small: 12px; $gl-font-weight-normal: 400; $gl-font-weight-bold: 600; $gl-text-color: #2e2e2e; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 8d884ad6891..52c91266ff4 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1027,8 +1027,12 @@ overflow-x: auto; } -.tree-list-search .form-control { - padding-left: 30px; +.tree-list-search { + flex: 0 0 34px; + + .form-control { + padding-left: 30px; + } } .tree-list-icon { @@ -1063,3 +1067,9 @@ } } } + +.tree-list-view-toggle { + svg { + top: 0; + } +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 0f95fb911e1..8ea34f5d19d 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -185,7 +185,17 @@ ul.related-merge-requests > li { } .new-branch-col { - padding-top: 10px; + font-size: 0; + + .discussion-filter-container { + &:not(:only-child) { + margin-right: $gl-padding-8; + } + + @include media-breakpoint-down(md) { + margin-top: $gl-padding-8; + } + } } .create-mr-dropdown-wrap { @@ -205,6 +215,10 @@ ul.related-merge-requests > li { .btn-group:not(.hidden) { display: flex; + + @include media-breakpoint-down(md) { + margin-top: $gl-padding-8; + } } .js-create-merge-request { @@ -251,7 +265,6 @@ ul.related-merge-requests > li { .new-branch-col { padding-top: 0; - text-align: right; align-self: center; } @@ -262,3 +275,9 @@ ul.related-merge-requests > li { } } } + +@include media-breakpoint-up(lg) { + .new-branch-col { + text-align: right; + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 895db89f289..fa6afbf81de 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -47,7 +47,6 @@ } } - .mr-widget-heading { position: relative; border: 1px solid $border-color; @@ -454,7 +453,7 @@ .mr-list { .merge-request { - padding: 10px 0 10px 15px; + padding: 10px 0 10px 15px; position: relative; display: -webkit-flex; display: flex; @@ -468,7 +467,6 @@ margin-bottom: 2px; .ci-status-link { - svg { height: 16px; width: 16px; @@ -698,7 +696,6 @@ .table-holder { .ci-table { - th { background-color: $white-light; color: $gl-text-color-secondary; @@ -775,7 +772,7 @@ &.affix { left: 0; - transition: right .15s; + transition: right 0.15s; @include media-breakpoint-down(xs) { right: 0; @@ -821,9 +818,17 @@ display: flex; justify-content: space-between; - @include media-breakpoint-down(xs) { + @include media-breakpoint-down(md) { flex-direction: column-reverse; } + + .discussion-filter-container { + margin-top: $gl-padding-8; + + &:not(:only-child) { + padding-right: $gl-padding-8; + } + } } .limit-container-width:not(.container-limited) { @@ -884,7 +889,7 @@ } > *:not(:last-child) { - margin-right: .3em; + margin-right: 0.3em; } svg { @@ -907,6 +912,10 @@ .btn svg { fill: $theme-gray-700; } + + .dropdown-menu { + width: 400px; + } } // Hack alert: we've rewritten `btn` class in a way that @@ -917,7 +926,7 @@ &[disabled] { cursor: not-allowed; box-shadow: none; - opacity: .65; + opacity: 0.65; &:hover { color: $gl-gray-500; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index bfba1bf1b2b..be535ade0a6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -618,7 +618,6 @@ ul.notes { .line-resolve-all-container { @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-right: 0; - padding-left: $gl-padding; } > div { @@ -756,3 +755,23 @@ ul.notes { margin-top: 4px; } } + +.discussion-filter-container { + + .btn > svg { + width: $gl-col-padding; + height: $gl-col-padding; + } + + .dropdown-menu { + margin-bottom: $gl-padding-4; + + @include media-breakpoint-down(md) { + margin-left: $btn-side-margin + $contextual-sidebar-collapsed-width; + } + + @include media-breakpoint-down(xs) { + margin-left: $btn-side-margin; + } + } +} diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb index 00d2cc01192..6fc336714b6 100644 --- a/app/controllers/admin/applications_controller.rb +++ b/app/controllers/admin/applications_controller.rb @@ -6,11 +6,9 @@ class Admin::ApplicationsController < Admin::ApplicationController before_action :set_application, only: [:show, :edit, :update, :destroy] before_action :load_scopes, only: [:new, :create, :edit, :update] - # rubocop: disable CodeReuse/ActiveRecord def index - @applications = Doorkeeper::Application.where("owner_id IS NULL") + @applications = ApplicationsFinder.new.execute end - # rubocop: enable CodeReuse/ActiveRecord def show end @@ -49,11 +47,9 @@ class Admin::ApplicationsController < Admin::ApplicationController private - # rubocop: disable CodeReuse/ActiveRecord def set_application - @application = Doorkeeper::Application.where("owner_id IS NULL").find(params[:id]) + @application = ApplicationsFinder.new(id: params[:id]).execute end - # rubocop: enable CodeReuse/ActiveRecord # Only allow a trusted parameter "white list" through. def application_params diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index b5fb5511638..23cc9ee247a 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -3,8 +3,8 @@ class Admin::DashboardController < Admin::ApplicationController include CountHelper - COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest, - Note, Snippet, Key, Milestone].freeze + COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue, + MergeRequest, Note, Snippet, Key, Milestone].freeze # rubocop: disable CodeReuse/ActiveRecord def index diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 3766b64a091..0d5c8657c9e 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -20,7 +20,7 @@ class AutocompleteController < ApplicationController end def user - user = UserFinder.new(params).execute! + user = UserFinder.new(params[:id]).find_by_id! render json: UserSerializer.new.represent(user) end diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 4f3d737e3ce..7f874687212 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -18,10 +18,15 @@ module Boards list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params) issues = list_service.execute issues = issues.page(params[:page]).per(params[:per] || 20).without_count - make_sure_position_is_set(issues) if Gitlab::Database.read_write? - issues = issues.preload(:project, - :milestone, + Issue.move_to_end(issues) if Gitlab::Database.read_write? + issues = issues.preload(:milestone, :assignees, + project: [ + :route, + { + namespace: [:route] + } + ], labels: [:priorities], notes: [:award_emoji, :author] ) @@ -60,12 +65,6 @@ module Boards render json: data end - def make_sure_position_is_set(issues) - issues.each do |issue| - issue.move_to_end && issue.save unless issue.relative_position - end - end - def issue @issue ||= issues_finder.find(params[:id]) end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 07e01e903ea..ad9cc0925b7 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -2,6 +2,7 @@ module IssuableActions extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize included do before_action :labels, only: [:show, :new, :edit] @@ -95,10 +96,14 @@ module IssuableActions def discussions notes = issuable.discussion_notes .inc_relations_for_view + .with_notes_filter(notes_filter) .includes(:noteable) .fresh - notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes) + if notes_filter != UserPreference::NOTES_FILTERS[:only_comments] + notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes) + end + notes = prepare_notes_for_rendering(notes) notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) } @@ -110,6 +115,32 @@ module IssuableActions private + def notes_filter + strong_memoize(:notes_filter) do + notes_filter_param = params[:notes_filter]&.to_i + + # GitLab Geo does not expect database UPDATE or INSERT statements to happen + # on GET requests. + # This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo. + if Gitlab::Database.read_only? + notes_filter_param || current_user&.notes_filter_for(issuable) + else + notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param + + # We need to invalidate the cache for polling notes otherwise it will + # ignore the filter. + # The ideal would be to invalidate the cache for each user. + issuable.expire_note_etag_cache if notes_filter_updated? + + notes_filter + end + end + end + + def notes_filter_updated? + current_user&.user_preference&.previous_changes&.any? + end + def discussion_serializer DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity) end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 3a45d6205ab..777b147e2dd 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -17,10 +17,17 @@ module NotesActions notes_json = { notes: [], last_fetched_at: current_fetched_at } - notes = notes_finder.execute - .inc_relations_for_view + notes = notes_finder + .execute + .inc_relations_for_view + + if notes_filter != UserPreference::NOTES_FILTERS[:only_comments] + notes = + ResourceEvents::MergeIntoNotesService + .new(noteable, current_user, last_fetched_at: current_fetched_at) + .execute(notes) + end - notes = ResourceEvents::MergeIntoNotesService.new(noteable, current_user, last_fetched_at: current_fetched_at).execute(notes) notes = prepare_notes_for_rendering(notes) notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) } @@ -224,6 +231,10 @@ module NotesActions request.headers['X-Last-Fetched-At'] end + def notes_filter + current_user&.notes_filter_for(params[:target_type]) + end + def notes_finder @notes_finder ||= NotesFinder.new(project, current_user, finder_params) end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 1dfa814cdd5..e3eec5a020d 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -20,7 +20,7 @@ class Import::GithubController < Import::BaseController end def personal_access_token - session[access_token_key] = params[:personal_access_token] + session[access_token_key] = params[:personal_access_token]&.strip redirect_to status_import_url end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 01801c31327..3c3dc03a4ee 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -38,7 +38,7 @@ class Profiles::KeysController < Profiles::ApplicationController def get_keys if params[:username].present? begin - user = User.find_by_username(params[:username]) + user = UserFinder.new(params[:username]).find_by_username if user.present? render text: user.all_ssh_keys.join("\n"), content_type: "text/plain" else diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index fb2cfdedd9b..56a884b8a2a 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -9,7 +9,7 @@ class Projects::BlobController < Projects::ApplicationController include ActionView::Helpers::SanitizeHelper prepend_before_action :authenticate_user!, only: [:edit] - before_action :set_request_format, only: [:edit, :show, :update] + before_action :set_request_format, only: [:edit, :show, :update, :destroy] before_action :require_non_empty_project, except: [:new, :create] before_action :authorize_download_code! diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8bc3a81d771..757b03d0b0e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -14,6 +14,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :set_issuables_index, only: [:index] before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] + before_action do + push_frontend_feature_flag(:ci_environments_status_changes) + end def index @merge_requests = @issuables @@ -198,43 +201,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def ci_environments_status - environments = - begin - @merge_request.environments_for(current_user).map do |environment| - project = environment.project - deployment = environment.first_deployment_for(@merge_request.diff_head_sha) - - stop_url = - if can?(current_user, :stop_environment, environment) - stop_project_environment_path(project, environment) - end - - metrics_url = - if can?(current_user, :read_environment, environment) && environment.has_metrics? - metrics_project_environment_deployment_path(project, environment, deployment) - end - - metrics_monitoring_url = - if can?(current_user, :read_environment, environment) - environment_metrics_path(environment) - end - - { - id: environment.id, - name: environment.name, - url: project_environment_path(project, environment), - metrics_url: metrics_url, - metrics_monitoring_url: metrics_monitoring_url, - stop_url: stop_url, - external_url: environment.external_url, - external_url_formatted: environment.formatted_external_url, - deployed_at: deployment.try(:created_at), - deployed_at_formatted: deployment.try(:formatted_deployment_time) - } - end.compact - end + environments = @merge_request.environments_for(current_user).map do |environment| + EnvironmentStatus.new(environment, @merge_request) + end - render json: environments + render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments) end def rebase diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 4bac763d000..3152a38fd8e 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController alias_method :awardable, :note def finder_params - params.merge(last_fetched_at: last_fetched_at) + params.merge(last_fetched_at: last_fetched_at, notes_filter: notes_filter) end def authorize_admin_note! diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 694c3a59e2b..dd9bf17cf0c 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -26,12 +26,9 @@ class SnippetsController < ApplicationController layout 'snippets' respond_to :html - # rubocop: disable CodeReuse/ActiveRecord def index if params[:username].present? - @user = User.find_by(username: params[:username]) - - return render_404 unless @user + @user = UserFinder.new(params[:username]).find_by_username! @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope]) .execute.page(params[:page]) @@ -41,7 +38,6 @@ class SnippetsController < ApplicationController redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) end end - # rubocop: enable CodeReuse/ActiveRecord def new @snippet = PersonalSnippet.new diff --git a/app/finders/applications_finder.rb b/app/finders/applications_finder.rb new file mode 100644 index 00000000000..3ded90f3fd5 --- /dev/null +++ b/app/finders/applications_finder.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ApplicationsFinder + attr_reader :params + + def initialize(params = {}) + @params = params + end + + def execute + applications = Doorkeeper::Application.where(owner_id: nil) # rubocop: disable CodeReuse/ActiveRecord + by_id(applications) + end + + private + + def by_id(applications) + return applications unless params[:id] + + Doorkeeper::Application.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord + end +end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 1f98ecf95ca..8abfe0c4c17 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -256,7 +256,7 @@ class IssuableFinder if assignee_id? User.find_by(id: params[:assignee_id]) elsif assignee_username? - User.find_by(username: params[:assignee_username]) + User.find_by_username(params[:assignee_username]) else nil end @@ -284,7 +284,7 @@ class IssuableFinder if author_id? User.find_by(id: params[:author_id]) elsif author_username? - User.find_by(username: params[:author_username]) + User.find_by_username(params[:author_username]) else nil end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index c67c2065440..817aac8b5d5 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -24,6 +24,8 @@ class NotesFinder def execute notes = init_collection notes = since_fetch_at(notes) + notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter? + notes.fresh end @@ -134,4 +136,8 @@ class NotesFinder last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i) notes.updated_after(last_fetched_at - FETCH_OVERLAP) end + + def notes_filter? + @params[:notes_filter].present? + end end diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb index 815388c894e..556be4c4338 100644 --- a/app/finders/user_finder.rb +++ b/app/finders/user_finder.rb @@ -7,22 +7,52 @@ # times we may want to exclude blocked user. By using this finder (and extending # it whenever necessary) we can keep this logic in one place. class UserFinder - attr_reader :params + def initialize(username_or_id) + @username_or_id = username_or_id + end + + # Tries to find a User by id, returning nil if none could be found. + def find_by_id + User.find_by_id(@username_or_id) + end - def initialize(params) - @params = params + # Tries to find a User by id, raising a `ActiveRecord::RecordNotFound` if it could + # not be found. + def find_by_id! + User.find(@username_or_id) end - # Tries to find a User, returning nil if none could be found. - # rubocop: disable CodeReuse/ActiveRecord - def execute - User.find_by(id: params[:id]) + # Tries to find a User by username, returning nil if none could be found. + def find_by_username + User.find_by_username(@username_or_id) end - # rubocop: enable CodeReuse/ActiveRecord - # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could + # Tries to find a User by username, raising a `ActiveRecord::RecordNotFound` if it could # not be found. - def execute! - User.find(params[:id]) + def find_by_username! + User.find_by_username!(@username_or_id) + end + + # Tries to find a User by username or id, returning nil if none could be found. + def find_by_id_or_username + if input_is_id? + find_by_id + else + find_by_username + end + end + + # Tries to find a User by username or id, raising a `ActiveRecord::RecordNotFound` if it could + # not be found. + def find_by_id_or_username! + if input_is_id? + find_by_id! + else + find_by_username! + end + end + + def input_is_id? + @username_or_id.is_a?(Numeric) || @username_or_id =~ /^\d+$/ end end diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb index f2ad9b4bda5..81ae50c0bd1 100644 --- a/app/finders/users_finder.rb +++ b/app/finders/users_finder.rb @@ -43,13 +43,11 @@ class UsersFinder private - # rubocop: disable CodeReuse/ActiveRecord def by_username(users) return users unless params[:username] - users.where(username: params[:username]) + users.by_username(params[:username]) end - # rubocop: enable CodeReuse/ActiveRecord def by_search(users) return users unless params[:search].present? diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index f7e087a6234..ff7f1e3a9aa 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -183,23 +183,23 @@ module BlobHelper end private :template_dropdown_names - def licenses_for_select(project = @project) + def licenses_for_select(project) @licenses_for_select ||= template_dropdown_names(TemplateFinder.build(:licenses, project).execute) end - def gitignore_names(project = @project) + def gitignore_names(project) @gitignore_names ||= template_dropdown_names(TemplateFinder.build(:gitignores, project).execute) end - def gitlab_ci_ymls(project = @project) + def gitlab_ci_ymls(project) @gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute) end - def dockerfile_names(project = @project) + def dockerfile_names(project) @dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute) end - def blob_editor_paths(project = @project) + def blob_editor_paths(project) { 'relative-url-root' => Rails.application.config.relative_url_root, 'assets-prefix' => Gitlab::Application.config.assets.prefix, diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 6f9e2ef78cd..923a06a0512 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -42,7 +42,7 @@ module CiStatusHelper when 'manual' s_('CiStatusText|blocked') when 'scheduled' - s_('CiStatusText|scheduled') + s_('CiStatusText|delayed') else # All states are already being translated inside the detailed statuses: # :running => Gitlab::Ci::Status::Running diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb index e16223a82c9..13839474e1f 100644 --- a/app/helpers/count_helper.rb +++ b/app/helpers/count_helper.rb @@ -8,4 +8,18 @@ module CountHelper number_with_delimiter(count) end + + # This will approximate the fork count by checking all counting all fork network + # memberships, and deducting 1 for each root of the fork network. + # This might be inacurate as the root of the fork network might have been deleted. + # + # This makes querying this information a lot more effecient and it should be + # accurate enough for the instance wide statistics + def approximate_fork_count_with_delimiters(count_data) + fork_network_count = count_data[ForkNetwork] + fork_network_member_count = count_data[ForkNetworkMember] + approximate_fork_count = fork_network_member_count - fork_network_count + + number_with_delimiter(approximate_fork_count) + end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index f573fd399a5..0c313e9e6d3 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,6 +1,15 @@ # frozen_string_literal: true module GroupsHelper + def group_overview_nav_link_paths + %w[ + groups#show + groups#activity + groups#subgroups + analytics#show + ] + end + def group_nav_link_paths %w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index] end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 97406fefd43..6069640b9c8 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -386,8 +386,8 @@ module IssuablesHelper { todo_text: "Add todo", mark_text: "Mark todo as done", - todo_icon: (is_collapsed ? icon('plus-square') : nil), - mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil), + 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), diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index 3e6a301b77d..719c351242c 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -21,17 +21,15 @@ module TimeHelper "#{from.to_s(:short)} - #{to.to_s(:short)}" end - def duration_in_numbers(duration_in_seconds, allow_overflow = false) - if allow_overflow - seconds = duration_in_seconds % 1.minute - minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute) - hours = duration_in_seconds / 1.hour + def duration_in_numbers(duration_in_seconds) + seconds = duration_in_seconds % 1.minute + minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute) + hours = duration_in_seconds / 1.hour - "%02d:%02d:%02d" % [hours, minutes, seconds] + if hours == 0 + "%02d:%02d" % [minutes, seconds] else - time_format = duration_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S" - - Time.at(duration_in_seconds).utc.strftime(time_format) + "%02d:%02d:%02d" % [hours, minutes, seconds] end end end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index cb73fc74bb6..34a889057ab 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -19,7 +19,9 @@ module Ci sast: 'gl-sast-report.json', dependency_scanning: 'gl-dependency-scanning-report.json', container_scanning: 'gl-container-scanning-report.json', - dast: 'gl-dast-report.json' + dast: 'gl-dast-report.json', + license_management: 'gl-license-management-report.json', + performance: 'performance.json' }.freeze TYPE_AND_FORMAT_PAIRS = { @@ -27,11 +29,17 @@ module Ci metadata: :gzip, trace: :raw, junit: :gzip, - codequality: :gzip, - sast: :gzip, - dependency_scanning: :gzip, - container_scanning: :gzip, - dast: :gzip + + # All these file formats use `raw` as we need to store them uncompressed + # for Frontend to fetch the files and do analysis + # When they will be only used by backend, they can be `gzipped`. + codequality: :raw, + sast: :raw, + dependency_scanning: :raw, + container_scanning: :raw, + dast: :raw, + license_management: :raw, + performance: :raw }.freeze belongs_to :project @@ -76,7 +84,9 @@ module Ci dependency_scanning: 6, ## EE-specific container_scanning: 7, ## EE-specific dast: 8, ## EE-specific - codequality: 9 ## EE-specific + codequality: 9, ## EE-specific + license_management: 10, ## EE-specific + performance: 11 ## EE-specific } enum file_format: { @@ -100,7 +110,8 @@ module Ci } FILE_FORMAT_ADAPTERS = { - gzip: Gitlab::Ci::Build::Artifacts::GzipFileAdapter + gzip: Gitlab::Ci::Build::Artifacts::Adapters::GzipStream, + raw: Gitlab::Ci::Build::Artifacts::Adapters::RawStream }.freeze def valid_file_format? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 17024e8a0af..aeee7f0a5d2 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -268,6 +268,12 @@ module Ci stage unless stage.statuses_count.zero? end + def ref_exists? + project.repository.ref_exists?(git_ref) + rescue Gitlab::Git::Repository::NoRepository + false + end + ## # TODO We do not completely switch to persisted stages because of # race conditions with setting statuses gitlab-ce#23257. @@ -674,11 +680,11 @@ module Ci def push_details strong_memoize(:push_details) do - Gitlab::Git::Push.new(project, before_sha, sha, push_ref) + Gitlab::Git::Push.new(project, before_sha, sha, git_ref) end end - def push_ref + def git_ref if branch? Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s elsif tag? diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index a4a2e2b79a6..b311f5e0617 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.31'.freeze + VERSION = '0.1.35'.freeze self.table_name = 'clusters_applications_runners' diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 20d53b8e620..95efecfc41d 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -31,6 +31,9 @@ module Clusters has_one :application_runner, class_name: 'Clusters::Applications::Runner' has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter' + has_many :kubernetes_namespaces + has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace' + accepts_nested_attributes_for :provider_gcp, update_only: true accepts_nested_attributes_for :platform_kubernetes, update_only: true diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb new file mode 100644 index 00000000000..fb5f6b65d9d --- /dev/null +++ b/app/models/clusters/kubernetes_namespace.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Clusters + class KubernetesNamespace < ActiveRecord::Base + self.table_name = 'clusters_kubernetes_namespaces' + + belongs_to :cluster_project, class_name: 'Clusters::Project' + belongs_to :cluster, class_name: 'Clusters::Cluster' + belongs_to :project, class_name: '::Project' + has_one :platform_kubernetes, through: :cluster + + validates :namespace, presence: true + validates :namespace, uniqueness: { scope: :cluster_id } + + before_validation :set_namespace_and_service_account_to_default, on: :create + + attr_encrypted :service_account_token, + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-cbc' + + def token_name + "#{namespace}-token" + end + + private + + def set_namespace_and_service_account_to_default + self.namespace ||= default_namespace + self.service_account_name ||= default_service_account_name + end + + def default_namespace + platform_kubernetes&.namespace.presence || project_namespace + end + + def default_service_account_name + "#{namespace}-service-account" + end + + def project_namespace + Gitlab::NamespaceSanitizer.sanitize(project_slug) + end + + def project_slug + "#{project.path}-#{project.id}".downcase + end + end +end diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 3a335909101..e8e943872de 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -7,6 +7,8 @@ module Clusters include ReactiveCaching include EnumWithNil + RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze + self.table_name = 'cluster_platforms_kubernetes' self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] } @@ -32,6 +34,8 @@ module Clusters message: Gitlab::Regex.kubernetes_namespace_regex_message } + validates :namespace, exclusion: { in: RESERVED_NAMESPACES } + # We expect to be `active?` only when enabled and cluster is created (the api_url is assigned) validates :api_url, url: true, presence: true validates :token, presence: true @@ -45,6 +49,7 @@ module Clusters delegate :project, to: :cluster, allow_nil: true delegate :enabled?, to: :cluster, allow_nil: true delegate :managed?, to: :cluster, allow_nil: true + delegate :kubernetes_namespace, to: :cluster alias_method :active?, :enabled? @@ -116,10 +121,19 @@ module Clusters end def default_namespace + kubernetes_namespace&.namespace.presence || fallback_default_namespace + end + + # DEPRECATED + # + # On 11.4 Clusters::KubernetesNamespace was introduced, this model will allow to + # have multiple namespaces per project. This method will be removed after migration + # has been completed. + def fallback_default_namespace return unless project slug = "#{project.path}-#{project.id}".downcase - slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '') + Gitlab::NamespaceSanitizer.sanitize(slug) end def build_kube_client!(api_groups: ['api'], api_version: 'v1') diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb index 839ce796081..15092b1c9d2 100644 --- a/app/models/clusters/project.rb +++ b/app/models/clusters/project.rb @@ -6,5 +6,8 @@ module Clusters belongs_to :cluster, class_name: 'Clusters::Cluster' belongs_to :project, class_name: '::Project' + + has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id + has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id end end diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb index 85229cded5d..045bf392ac8 100644 --- a/app/models/concerns/relative_positioning.rb +++ b/app/models/concerns/relative_positioning.rb @@ -12,6 +12,49 @@ module RelativePositioning after_save :save_positionable_neighbours end + class_methods do + def move_to_end(objects) + parent_ids = objects.map(&:parent_ids).flatten.uniq + max_relative_position = in_parents(parent_ids).maximum(:relative_position) || START_POSITION + objects = objects.reject(&:relative_position) + + self.transaction do + objects.each do |object| + relative_position = position_between(max_relative_position, MAX_POSITION) + object.relative_position = relative_position + max_relative_position = relative_position + object.save + end + end + end + + # This method takes two integer values (positions) and + # calculates the position between them. The range is huge as + # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time + # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number + def position_between(pos_before, pos_after) + pos_before ||= MIN_POSITION + pos_after ||= MAX_POSITION + + pos_before, pos_after = [pos_before, pos_after].sort + + halfway = (pos_after + pos_before) / 2 + distance_to_halfway = pos_after - halfway + + if distance_to_halfway < IDEAL_DISTANCE + halfway + else + if pos_before == MIN_POSITION + pos_after - IDEAL_DISTANCE + elsif pos_after == MAX_POSITION + pos_before + IDEAL_DISTANCE + else + halfway + end + end + end + end + def min_relative_position self.class.in_parents(parent_ids).minimum(:relative_position) end @@ -57,7 +100,7 @@ module RelativePositioning @positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables end - self.relative_position = position_between(before.relative_position, after.relative_position) + self.relative_position = self.class.position_between(before.relative_position, after.relative_position) end def move_after(before = self) @@ -72,7 +115,7 @@ module RelativePositioning pos_after = issue_to_move.relative_position end - self.relative_position = position_between(pos_before, pos_after) + self.relative_position = self.class.position_between(pos_before, pos_after) end def move_before(after = self) @@ -87,15 +130,15 @@ module RelativePositioning pos_before = issue_to_move.relative_position end - self.relative_position = position_between(pos_before, pos_after) + self.relative_position = self.class.position_between(pos_before, pos_after) end def move_to_end - self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION) + self.relative_position = self.class.position_between(max_relative_position || START_POSITION, MAX_POSITION) end def move_to_start - self.relative_position = position_between(min_relative_position || START_POSITION, MIN_POSITION) + self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION) end # Indicates if there is an issue that should be shifted to free the place @@ -112,32 +155,6 @@ module RelativePositioning private - # This method takes two integer values (positions) and - # calculates the position between them. The range is huge as - # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time - # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number - def position_between(pos_before, pos_after) - pos_before ||= MIN_POSITION - pos_after ||= MAX_POSITION - - pos_before, pos_after = [pos_before, pos_after].sort - - halfway = (pos_after + pos_before) / 2 - distance_to_halfway = pos_after - halfway - - if distance_to_halfway < IDEAL_DISTANCE - halfway - else - if pos_before == MIN_POSITION - pos_after - IDEAL_DISTANCE - elsif pos_after == MAX_POSITION - pos_before + IDEAL_DISTANCE - else - halfway - end - end - end - # rubocop:disable Gitlab/ModuleWithInstanceVariables def save_positionable_neighbours return unless @positionable_neighbours diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb new file mode 100644 index 00000000000..5ff3acc0e58 --- /dev/null +++ b/app/models/environment_status.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +class EnvironmentStatus + include Gitlab::Utils::StrongMemoize + + attr_reader :environment, :merge_request + + delegate :id, to: :environment + delegate :name, to: :environment + delegate :project, to: :environment + delegate :deployed_at, to: :deployment, allow_nil: true + + def initialize(environment, merge_request) + @environment = environment + @merge_request = merge_request + end + + def deployment + strong_memoize(:deployment) do + environment.first_deployment_for(merge_request.diff_head_sha) + end + end + + def deployed_at + deployment&.created_at + end + + def changes + sha = merge_request.diff_head_sha + return [] if project.route_map_for(sha).nil? + + changed_files.map { |file| build_change(file, sha) }.compact + end + + def changed_files + merge_request.merge_request_diff + .merge_request_diff_files.where(deleted_file: false) + end + + private + + PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze + + def build_change(file, sha) + public_path = project.public_path_for_source_path(file.new_path, sha) + return if public_path.nil? + + ext = File.extname(public_path) + return if ext.present? && ext !~ PAGE_EXTENSIONS + + { + path: public_path, + external_url: environment.external_url_for(file.new_path, sha) + } + end +end diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb deleted file mode 100644 index 0f7067238cd..00000000000 --- a/app/models/forked_project_link.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -class ForkedProjectLink < ActiveRecord::Base - belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project' - belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project' -end diff --git a/app/models/list.rb b/app/models/list.rb index 1a30acc83cf..029685be927 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -15,6 +15,7 @@ class List < ActiveRecord::Base scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } + scope :preload_associations, -> { preload(:board, :label) } class << self def destroyable_types diff --git a/app/models/note.rb b/app/models/note.rb index 95e1d3afa00..e1bd943e8e4 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -110,6 +110,15 @@ class Note < ActiveRecord::Base :system_note_metadata, :note_diff_file) end + scope :with_notes_filter, -> (notes_filter) do + case notes_filter + when UserPreference::NOTES_FILTERS[:only_comments] + user + else + all + end + end + scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) } scope :new_diff_notes, -> { where(type: 'DiffNote') } scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) } diff --git a/app/models/project.rb b/app/models/project.rb index b80e41e4a96..382fb4f463a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -167,20 +167,15 @@ class Project < ActiveRecord::Base has_one :packagist_service has_one :hangouts_chat_service - # TODO: replace these relations with the fork network versions - has_one :forked_project_link, foreign_key: "forked_to_project_id" - has_one :forked_from_project, through: :forked_project_link - - has_many :forked_project_links, foreign_key: "forked_from_project_id" - has_many :forks, through: :forked_project_links, source: :forked_to_project - # TODO: replace these relations with the fork network versions - has_one :root_of_fork_network, foreign_key: 'root_project_id', inverse_of: :root_project, class_name: 'ForkNetwork' has_one :fork_network_member has_one :fork_network, through: :fork_network_member + has_one :forked_from_project, through: :fork_network_member + has_many :forked_to_members, class_name: 'ForkNetworkMember', foreign_key: 'forked_from_project_id' + has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -553,6 +548,8 @@ class Project < ActiveRecord::Base self[:lfs_enabled] && Gitlab.config.lfs.enabled end + alias_method :lfs_enabled, :lfs_enabled? + def auto_devops_enabled? if auto_devops&.enabled.nil? has_auto_devops_implicitly_enabled? @@ -693,6 +690,8 @@ class Project < ActiveRecord::Base else super end + rescue + super end def valid_import_url? @@ -1250,12 +1249,7 @@ class Project < ActiveRecord::Base end def forked? - return true if fork_network && fork_network.root_project != self - - # TODO: Use only the above conditional using the `fork_network` - # This is the old conditional that looks at the `forked_project_link`, we - # fall back to this while we're migrating the new models - !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) + fork_network && fork_network.root_project != self end def fork_source @@ -1546,9 +1540,7 @@ class Project < ActiveRecord::Base def visibility_level_allowed_as_fork?(level = self.visibility_level) return true unless forked? - # self.forked_from_project will be nil before the project is saved, so - # we need to go through the relation - original_project = forked_project_link&.forked_from_project + original_project = fork_source return true unless original_project level <= original_project.visibility_level @@ -1640,34 +1632,6 @@ class Project < ActiveRecord::Base end # rubocop: enable CodeReuse/ServiceClass - def rename_repo - path_before = previous_changes['path'].first - full_path_before = full_path_was - full_path_after = build_full_path - - Gitlab::AppLogger.info("Attempting to rename #{full_path_was} -> #{full_path_after}") - - if has_container_registry_tags? - Gitlab::AppLogger.info("Project #{full_path_was} cannot be renamed because container registry tags are present!") - - # we currently don't support renaming repository if it contains images in container registry - raise StandardError.new('Project cannot be renamed, because images are present in its container registry') - end - - expire_caches_before_rename(full_path_before) - - if rename_or_migrate_repository! - Gitlab::AppLogger.info("Project was renamed: #{full_path_before} -> #{full_path_after}") - after_rename_repository(full_path_before, path_before) - else - Gitlab::AppLogger.info("Repository could not be renamed: #{full_path_before} -> #{full_path_after}") - - # if we cannot move namespace directory we should rollback - # db changes in order to prevent out of sync between db and fs - raise StandardError.new('Repository cannot be renamed') - end - end - def write_repository_config(gl_full_path: full_path) # We'd need to keep track of project full path otherwise directory tree # created with hashed storage enabled cannot be usefully imported using @@ -2096,51 +2060,6 @@ class Project < ActiveRecord::Base auto_cancel_pending_pipelines == 'enabled' end - private - - # rubocop: disable CodeReuse/ServiceClass - def rename_or_migrate_repository! - if Gitlab::CurrentSettings.hashed_storage_enabled? && - storage_upgradable? && - Feature.disabled?(:skip_hashed_storage_upgrade) # kill switch in case we need to disable upgrade behavior - ::Projects::HashedStorageMigrationService.new(self, full_path_was).execute - else - storage.rename_repo - end - end - # rubocop: enable CodeReuse/ServiceClass - - def storage_upgradable? - storage_version != LATEST_STORAGE_VERSION - end - - def after_rename_repository(full_path_before, path_before) - execute_rename_repository_hooks!(full_path_before) - - write_repository_config - - # We need to check if project had been rolled out to move resource to hashed storage or not and decide - # if we need execute any take action or no-op. - unless hashed_storage?(:attachments) - Gitlab::UploadsTransfer.new.rename_project(path_before, self.path, namespace.full_path) - end - - Gitlab::PagesTransfer.new.rename_project(path_before, self.path, namespace.full_path) - end - - # rubocop: disable CodeReuse/ServiceClass - def execute_rename_repository_hooks!(full_path_before) - # When we import a project overwriting the original project, there - # is a move operation. In that case we don't want to send the instructions. - send_move_instructions(full_path_before) unless import_started? - - self.old_path_with_namespace = full_path_before - SystemHooksService.new.execute_hooks_for(self, :rename) - - reload_repository! - end - # rubocop: enable CodeReuse/ServiceClass - def storage @storage ||= if hashed_storage?(:repository) @@ -2150,6 +2069,12 @@ class Project < ActiveRecord::Base end end + def storage_upgradable? + storage_version != LATEST_STORAGE_VERSION + end + + private + def use_hashed_storage if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled self.storage_version = LATEST_STORAGE_VERSION diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index d502423726c..d121d088ff6 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -80,13 +80,18 @@ class BambooService < CiService private + def get_build_result_index + # When Bamboo returns multiple results for a given changeset, arbitrarily assume the most relevant result to be the last one. + -1 + end + def read_build_page(response) - if response.code != 200 || response['results']['results']['size'] == '0' + if response.code != 200 || response.dig('results', 'results', 'size') == '0' # If actual build link can't be determined, send user to build summary page. URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s else # If actual build link is available, go to build result page. - result_key = response['results']['results']['result']['planResultKey']['key'] + result_key = response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key') URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s end end @@ -94,10 +99,10 @@ class BambooService < CiService def read_commit_status(response) return :error unless response.code == 200 || response.code == 404 - status = if response.code == 404 || response['results']['results']['size'] == '0' + status = if response.code == 404 || response.dig('results', 'results', 'size') == '0' 'Pending' else - response['results']['results']['result']['buildState'] + response.dig('results', 'results', 'result', get_build_result_index, 'buildState') end if status.include?('Success') diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb index 5b0e5fed092..c34078f13c1 100644 --- a/app/models/project_services/microsoft_teams_service.rb +++ b/app/models/project_services/microsoft_teams_service.rb @@ -17,7 +17,7 @@ class MicrosoftTeamsService < ChatNotificationService 'This service sends notifications about projects events to Microsoft Teams channels.<br /> To set up this service: <ol> - <li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li> + <li><a href="https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors/connectors-using#setting-up-a-custom-incoming-webhook">Setup a custom Incoming Webhook using Office 365 Connectors For Microsoft Teams</a>.</li> <li>Paste the <strong>Webhook URL</strong> into the field below.</li> <li>Select events below to enable notifications.</li> </ol>' diff --git a/app/models/user.rb b/app/models/user.rb index a0665518cf5..ca7fc3b058f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -152,6 +152,7 @@ class User < ActiveRecord::Base belongs_to :accepted_term, class_name: 'ApplicationSetting::Term' has_one :status, class_name: 'UserStatus' + has_one :user_preference # # Validations @@ -224,6 +225,8 @@ class User < ActiveRecord::Base enum project_view: [:readme, :activity, :files] delegate :path, to: :namespace, allow_nil: true, prefix: true + delegate :notes_filter_for, to: :user_preference + delegate :set_notes_filter, to: :user_preference state_machine :state, initial: :active do event :block do @@ -264,7 +267,7 @@ class User < ActiveRecord::Base scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } scope :confirmed, -> { where.not(confirmed_at: nil) } - scope :by_username, -> (usernames) { iwhere(username: usernames) } + scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) } scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } # Limits the users to those that have TODOs, optionally in the given state. @@ -1367,6 +1370,11 @@ class User < ActiveRecord::Base !consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user? end + # Avoid migrations only building user preference object when needed. + def user_preference + super.presence || build_user_preference + end + def todos_limited_to(ids) todos.where(id: ids) end diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb new file mode 100644 index 00000000000..6cd91abc261 --- /dev/null +++ b/app/models/user_preference.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class UserPreference < ActiveRecord::Base + # We could use enums, but Rails 4 doesn't support multiple + # enum options with same name for multiple fields, also it creates + # extra methods that aren't really needed here. + NOTES_FILTERS = { all_notes: 0, only_comments: 1 }.freeze + + belongs_to :user + + validates :issue_notes_filter, :merge_request_notes_filter, inclusion: { in: NOTES_FILTERS.values }, presence: true + + class << self + def notes_filters + { + s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes], + s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments] + } + end + end + + def set_notes_filter(filter_id, issuable) + # No need to update the column if the value is already set. + if filter_id && NOTES_FILTERS.values.include?(filter_id) + field = notes_filter_field_for(issuable) + self[field] = filter_id + + save if attribute_changed?(field) + end + + notes_filter_for(issuable) + end + + # Returns the current discussion filter for a given issuable + # or issuable type. + def notes_filter_for(resource) + self[notes_filter_field_for(resource)] + end + + private + + def notes_filter_field_for(resource) + field_key = + if resource.is_a?(Issuable) + resource.model_name.param_key + else + resource + end + + "#{field_key}_notes_filter" + end +end diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb index 880218e2727..300f85e1e9d 100644 --- a/app/presenters/ci/build_runner_presenter.rb +++ b/app/presenters/ci/build_runner_presenter.rb @@ -30,12 +30,12 @@ module Ci def create_reports(reports, expire_in:) return unless reports&.any? - reports.map do |k, v| + reports.map do |report_type, report_paths| { - artifact_type: k.to_sym, - artifact_format: :gzip, - name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES[k.to_sym], - paths: v, + artifact_type: report_type.to_sym, + artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym), + name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym), + paths: report_paths, when: 'always', expire_in: expire_in } diff --git a/app/serializers/current_user_entity.rb b/app/serializers/current_user_entity.rb new file mode 100644 index 00000000000..71d14e727dd --- /dev/null +++ b/app/serializers/current_user_entity.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Always use this entity when rendering data for current user +# for attributes that does not need to be visible to other users +# like user preferences. +class CurrentUserEntity < UserEntity + expose :user_preference, using: UserPreferenceEntity +end diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb new file mode 100644 index 00000000000..3dfa4f204c9 --- /dev/null +++ b/app/serializers/environment_status_entity.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class EnvironmentStatusEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :name + + expose :url do |es| + project_environment_path(es.project, es.environment) + end + + expose :metrics_url, if: ->(*) { can_read_environment? && environment.has_metrics? } do |es| + metrics_project_environment_deployment_path(es.project, es.environment, es.deployment) + end + + expose :metrics_monitoring_url, if: ->(*) { can_read_environment? } do |es| + environment_metrics_path(es.environment) + end + + expose :stop_url, if: ->(*) { can_stop_environment? } do |es| + stop_project_environment_path(es.project, es.environment) + end + + expose :external_url do |es| + es.environment.external_url + end + + expose :external_url_formatted do |es| + es.environment.formatted_external_url + end + + expose :deployed_at + + expose :deployed_at_formatted do |es| + es.deployment.try(:formatted_deployment_time) + end + + expose :changes, if: ->(*) { Feature.enabled?(:ci_environments_status_changes, project) } + + private + + def environment + object.environment + end + + def project + object.environment.project + end + + def current_user + request.current_user + end + + def can_read_environment? + can?(current_user, :read_environment, environment) + end + + def can_stop_environment? + can?(current_user, :stop_environment, environment) + end +end diff --git a/app/serializers/environment_status_serializer.rb b/app/serializers/environment_status_serializer.rb new file mode 100644 index 00000000000..f8d37934763 --- /dev/null +++ b/app/serializers/environment_status_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class EnvironmentStatusSerializer < BaseSerializer + entity EnvironmentStatusEntity +end diff --git a/app/serializers/merge_request_user_entity.rb b/app/serializers/merge_request_user_entity.rb index fd2d2897113..53257b0602c 100644 --- a/app/serializers/merge_request_user_entity.rb +++ b/app/serializers/merge_request_user_entity.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestUserEntity < UserEntity +class MergeRequestUserEntity < CurrentUserEntity include RequestAwareEntity include BlobHelper include TreeHelper diff --git a/app/serializers/user_preference_entity.rb b/app/serializers/user_preference_entity.rb new file mode 100644 index 00000000000..fbdaab459b3 --- /dev/null +++ b/app/serializers/user_preference_entity.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class UserPreferenceEntity < Grape::Entity + expose :issue_notes_filter + expose :merge_request_notes_filter + + expose :notes_filters do |user_preference| + UserPreference.notes_filters + end +end diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index 4c5e22bdd7e..201048aaba5 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -17,11 +17,29 @@ class AuditEventService end def security_event - SecurityEvent.create( + log_security_event_to_file + log_security_event_to_database + end + + private + + def base_payload + { author_id: @author.id, entity_id: @entity.id, - entity_type: @entity.class.name, - details: @details - ) + entity_type: @entity.class.name + } + end + + def file_logger + @file_logger ||= Gitlab::AuditJsonLogger.build + end + + def log_security_event_to_file + file_logger.info(base_payload.merge(@details)) + end + + def log_security_event_to_database + SecurityEvent.create(base_payload.merge(details: @details)) end end diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb index e10eb52e041..5cf5f14a55b 100644 --- a/app/services/boards/lists/list_service.rb +++ b/app/services/boards/lists/list_service.rb @@ -6,7 +6,7 @@ module Boards def execute(board) board.lists.create(list_type: :backlog) unless board.lists.backlog.exists? - board.lists + board.lists.preload_associations end end end diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index fe47aa2f140..0bf0e967dcc 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -14,9 +14,11 @@ module Groups group.assign_attributes(params) begin - after_update if group.save + success = group.save - true + after_update if success + + success rescue Gitlab::UpdatePathError => e group.errors.add(:base, e.message) diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb new file mode 100644 index 00000000000..4131da44f5a --- /dev/null +++ b/app/services/projects/after_rename_service.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module Projects + # Service class for performing operations that should take place after a + # project has been renamed. + # + # Example usage: + # + # project = Project.find(42) + # + # project.update(...) + # + # Projects::AfterRenameService.new(project).execute + class AfterRenameService + attr_reader :project, :full_path_before, :full_path_after, :path_before + + RenameFailedError = Class.new(StandardError) + + # @param [Project] project The Project of the repository to rename. + def initialize(project) + @project = project + + # The full path of the namespace + project, before the rename took place. + @full_path_before = project.full_path_was + + # The full path of the namespace + project, after the rename took place. + @full_path_after = project.build_full_path + + # The path of just the project, before the rename took place. + @path_before = project.path_was + end + + def execute + first_ensure_no_registry_tags_are_present + expire_caches_before_rename + rename_or_migrate_repository! + send_move_instructions + execute_system_hooks + update_repository_configuration + rename_transferred_documents + log_completion + end + + def first_ensure_no_registry_tags_are_present + return unless project.has_container_registry_tags? + + raise RenameFailedError.new( + "Project #{full_path_before} cannot be renamed because images are " \ + "present in its container registry" + ) + end + + def expire_caches_before_rename + project.expire_caches_before_rename(full_path_before) + end + + def rename_or_migrate_repository! + success = + if migrate_to_hashed_storage? + ::Projects::HashedStorageMigrationService + .new(project, full_path_before) + .execute + else + project.storage.rename_repo + end + + rename_failed! unless success + end + + def send_move_instructions + return unless send_move_instructions? + + project.send_move_instructions(full_path_before) + end + + def execute_system_hooks + project.old_path_with_namespace = full_path_before + SystemHooksService.new.execute_hooks_for(project, :rename) + end + + def update_repository_configuration + project.reload_repository! + project.write_repository_config + end + + def rename_transferred_documents + if rename_uploads? + Gitlab::UploadsTransfer + .new + .rename_project(path_before, project_path, namespace_full_path) + end + + Gitlab::PagesTransfer + .new + .rename_project(path_before, project_path, namespace_full_path) + end + + def log_completion + Gitlab::AppLogger.info( + "Project #{project.id} has been renamed from " \ + "#{full_path_before} to #{full_path_after}" + ) + end + + def migrate_to_hashed_storage? + Gitlab::CurrentSettings.hashed_storage_enabled? && + project.storage_upgradable? && + Feature.disabled?(:skip_hashed_storage_upgrade) + end + + def send_move_instructions? + !project.import_started? + end + + def rename_uploads? + !project.hashed_storage?(:attachments) + end + + def project_path + project.path + end + + def namespace_full_path + project.namespace.full_path + end + + def rename_failed! + error = "Repository #{full_path_before} could not be renamed to #{full_path_after}" + + Gitlab::AppLogger.error(error) + + raise RenameFailedError.new(error) + end + end +end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 0e6a7e8da54..20bfe5af7a1 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -13,8 +13,8 @@ module Projects return ::Projects::CreateFromTemplateService.new(current_user, params).execute end - forked_from_project_id = params.delete(:forked_from_project_id) import_data = params.delete(:import_data) + relations_block = params.delete(:relations_block) @project = Project.new(params) @@ -24,11 +24,6 @@ module Projects return @project end - unless allowed_fork?(forked_from_project_id) - @project.errors.add(:forked_from_project_id, 'is forbidden') - return @project - end - set_project_name_from_path # get namespace id @@ -47,6 +42,7 @@ module Projects @project.namespace_id = current_user.namespace_id end + relations_block&.call(@project) yield(@project) if block_given? # If the block added errors, don't try to save the project @@ -54,10 +50,6 @@ module Projects @project.creator = current_user - if forked_from_project_id - @project.build_forked_project_link(forked_from_project_id: forked_from_project_id) - end - save_project_and_import_data(import_data) after_create_actions if @project.persisted? @@ -80,15 +72,6 @@ module Projects end # rubocop: disable CodeReuse/ActiveRecord - def allowed_fork?(source_project_id) - return true if source_project_id.nil? - - source_project = Project.find_by(id: source_project_id) - current_user.can?(:fork_project, source_project) - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord def allowed_namespace?(user, namespace_id) namespace = Namespace.find_by(id: namespace_id) current_user.can?(:create_projects, namespace) diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index cbbb88a9410..8dc0e044875 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -12,31 +12,42 @@ module Projects private + def allowed_fork? + current_user.can?(:fork_project, @project) + end + def link_existing_project(fork_to_project) return if fork_to_project.forked? - link_fork_network(fork_to_project) + build_fork_network_member(fork_to_project) - # A forked project stores its LFS objects in the `forked_from_project`. - # So the LFS objects become inaccessible, and therefore delete them from - # the database so they'll get cleaned up. - # - # TODO: refactor this to get the correct lfs objects when implementing - # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 - fork_to_project.lfs_objects_projects.delete_all + if link_fork_network(fork_to_project) + # A forked project stores its LFS objects in the `forked_from_project`. + # So the LFS objects become inaccessible, and therefore delete them from + # the database so they'll get cleaned up. + # + # TODO: refactor this to get the correct lfs objects when implementing + # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 + fork_to_project.lfs_objects_projects.delete_all - fork_to_project + fork_to_project + end end def fork_new_project new_params = { - forked_from_project_id: @project.id, visibility_level: allowed_visibility_level, description: @project.description, name: @project.name, path: @project.path, shared_runners_enabled: @project.shared_runners_enabled, - namespace_id: target_namespace.id + namespace_id: target_namespace.id, + fork_network: fork_network, + # We need to assign the fork network membership after the project has + # been instantiated to avoid ActiveRecord trying to create it when + # initializing the project, as that would cause a foreign key constraint + # exception. + relations_block: -> (project) { build_fork_network_member(project) } } if @project.avatar.present? && @project.avatar.image? @@ -46,38 +57,35 @@ module Projects new_project = CreateService.new(current_user, new_params).execute return new_project unless new_project.persisted? + # Set the forked_from_project relation after saving to avoid having to + # reload the project to reset the association information and cause an + # extra query. + new_project.forked_from_project = @project + builds_access_level = @project.project_feature.builds_access_level new_project.project_feature.update(builds_access_level: builds_access_level) - link_fork_network(new_project) - new_project end def fork_network - if @project.fork_network - @project.fork_network - elsif forked_from_project = @project.forked_from_project - # TODO: remove this case when all background migrations have completed - # this only happens when a project had a `forked_project_link` that was - # not migrated to the `fork_network` relation - forked_from_project.fork_network || forked_from_project.create_root_of_fork_network + @fork_network ||= @project.fork_network || @project.build_root_of_fork_network + end + + def build_fork_network_member(fork_to_project) + if allowed_fork? + fork_to_project.build_fork_network_member(forked_from_project: @project, + fork_network: fork_network) else - @project.create_root_of_fork_network + fork_to_project.errors.add(:forked_from_project_id, 'is forbidden') end end def link_fork_network(fork_to_project) - fork_network.fork_network_members.create(project: fork_to_project, - forked_from_project: @project) - - # TODO: remove this when ForkedProjectLink model is removed - unless fork_to_project.forked_project_link - fork_to_project.create_forked_project_link(forked_to_project: fork_to_project, - forked_from_project: @project) - end + return if fork_to_project.errors.any? - refresh_forks_count + fork_to_project.fork_network_member.save && + refresh_forks_count end def refresh_forks_count diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb index 00e73148358..ca85e2dc281 100644 --- a/app/services/projects/forks_count_service.rb +++ b/app/services/projects/forks_count_service.rb @@ -9,10 +9,7 @@ module Projects # rubocop: disable CodeReuse/ActiveRecord def self.query(project_ids) - # We can't directly change ForkedProjectLink to ForkNetworkMember here - # Nowadays, when a call using v3 to projects/:id/fork is made, - # the relationship to ForkNetworkMember is not updated - ForkedProjectLink.where(forked_from_project: project_ids) + ForkNetworkMember.where(forked_from_project: project_ids) end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb index 2948555a17c..33f0bab12c9 100644 --- a/app/services/projects/move_forks_service.rb +++ b/app/services/projects/move_forks_service.rb @@ -6,7 +6,6 @@ module Projects return unless super && source_project.fork_network Project.transaction(requires_new: true) do - move_forked_project_links move_fork_network_members update_root_project refresh_forks_count @@ -18,18 +17,6 @@ module Projects private # rubocop: disable CodeReuse/ActiveRecord - def move_forked_project_links - # Update ancestor - ForkedProjectLink.where(forked_to_project: source_project) - .update_all(forked_to_project_id: @project.id) - - # Update the descendants - ForkedProjectLink.where(forked_from_project: source_project) - .update_all(forked_from_project_id: @project.id) - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord def move_fork_network_members ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id) ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id) diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index a8b7c7f136a..1b8a920268f 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -25,7 +25,6 @@ module Projects end @project.fork_network_member.destroy - @project.forked_project_link.destroy end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index f25a4e30938..93e48fc0199 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -67,7 +67,7 @@ module Projects end if project.previous_changes.include?('path') - project.rename_repo + AfterRenameService.new(project).execute else system_hook_service.execute_hooks_for(project, :update) end diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index defa579f9a8..751aae2696d 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -643,7 +643,7 @@ module QuickActions if users.empty? users = - if params == 'me' + if params.strip == 'me' [current_user] else User.where(username: params.split(' ').map(&:strip)) diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 34724e0250d..1fee8bfcd31 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -22,7 +22,7 @@ class WebHookService end def execute - start_time = Time.now + start_time = Gitlab::Metrics::System.monotonic_time response = if parsed_url.userinfo.blank? make_request(hook.url) @@ -35,7 +35,7 @@ class WebHookService url: hook.url, request_data: data, response: response, - execution_duration: Time.now - start_time + execution_duration: Gitlab::Metrics::System.monotonic_time - start_time ) { @@ -43,13 +43,13 @@ class WebHookService http_status: response.code, message: response.to_s } - rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError => e + rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep => e log_execution( trigger: hook_name, url: hook.url, request_data: data, response: InternalErrorResponse.new, - execution_duration: Time.now - start_time, + execution_duration: Gitlab::Metrics::System.monotonic_time - start_time, error_message: e.to_s ) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 85c04f8a01d..7ac79cc77f5 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -42,7 +42,7 @@ %p Forks %span.light.float-right - = approximate_count_with_delimiters(@counts, ForkedProjectLink) + = approximate_fork_count_with_delimiters(@counts) %p Issues %span.light.float-right diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 0c683f86252..5f205d1bcbc 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -63,10 +63,9 @@ .card .card-header - %h3.card-title - = _('Projects') - %span.badge.badge-pill - #{@group.projects.count} + = _('Projects') + %span.badge.badge-pill + #{@group.projects.count} %ul.content-list - @projects.each do |project| %li @@ -119,7 +118,7 @@ = _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name } %span.badge.badge-pill= @group.members.size .float-right - = link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm" + = link_to icon('pencil-square-o', text: _('Manage access')), group_group_members_path(@group), class: "btn btn-sm" %ul.content-list.group-users-list.content-list.members-list = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } .card-footer diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index fefb4c7455d..03cce4745aa 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -183,7 +183,7 @@ project members %span.badge.badge-pill= @project.users.size .float-right - = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm" + = link_to icon('pencil-square-o', text: 'Manage access'), project_project_members_path(@project), class: "btn btn-sm" %ul.content-list.project_members.members-list = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } .card-footer diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml index 7503548fa3d..ec1a3fef435 100644 --- a/app/views/dashboard/_activity_head.html.haml +++ b/app/views/dashboard/_activity_head.html.haml @@ -1,3 +1,6 @@ +.page-title-holder + %h1.page-title= _('Activity') + .top-area %ul.nav-links.nav.nav-tabs %li{ class: active_when(params[:filter].nil?) }> diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 727784141bb..8ab5dc37f34 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,3 +1,10 @@ +.page-title-holder + %h1.page-title= _('Groups') + + - if current_user.can_create_group? + .page-title-controls + = link_to _("New group"), new_group_path, class: "btn btn-success" + .top-area %ul.nav-links.mobile-separator.nav.nav-tabs = nav_link(page: dashboard_groups_path) do @@ -9,5 +16,3 @@ .nav-controls = render 'shared/groups/search_form' = render 'shared/groups/dropdown' - - if current_user.can_create_group? - = link_to _("New group"), new_group_path, class: "btn btn-success" diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 69a2e408073..1050945b15a 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,6 +1,13 @@ = content_for :flash_message do = render 'shared/project_limit' +.page-title-holder + %h1.page-title= _('Projects') + + - if current_user.can_create_project? + .page-title-controls + = link_to "New project", new_project_path, class: "btn btn-success" + .top-area.scrolling-tabs-container.inner-page-scroll-tabs .fade-left= icon('angle-left') .fade-right= icon('angle-right') @@ -18,5 +25,3 @@ .nav-controls = render 'shared/projects/search_form' = render 'shared/projects/dropdown' - - if current_user.can_create_project? - = link_to "New project", new_project_path, class: "btn btn-success" diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 4f38339b87a..8d99f84755a 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,3 +1,10 @@ +.page-title-holder + %h1.page-title= _('Snippets') + + - if current_user + .page-title-controls + = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet" + .top-area %ul.nav-links.nav.nav-tabs = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do @@ -6,7 +13,3 @@ = nav_link(page: explore_snippets_path) do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do Explore snippets - - - if current_user - .nav-controls.d-none.d-sm-block - = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet" diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 86a21e24ac9..832ba877558 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -4,11 +4,17 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues") +.page-title-holder + %h1.page-title= _('Issues') + + - if current_user + .page-title-controls + = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues + .top-area = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set .nav-controls = render 'shared/issuable/feed_buttons' - = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/issuable/filter', type: :issues diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 61aae31be60..fba8d1cf667 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -2,10 +2,15 @@ - page_title _("Merge Requests") - @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id) +.page-title-holder + %h1.page-title= _('Merge Requests') + + - if current_user + .page-title-controls + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests + .top-area = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set - .nav-controls - = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index f66e2b40d76..ae0e38bf0ee 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -2,12 +2,18 @@ - page_title 'Milestones' - header_title 'Milestones', dashboard_milestones_path +.page-title-holder + %h1.page-title= _('Milestones') + + - if current_user + .page-title-controls + = render 'shared/new_project_item_select', + path: 'milestones/new', label: 'New milestone', + include_groups: true, type: :milestones + .top-area = render 'shared/milestones_filter', counts: @milestone_states - .nav-controls - = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones - .milestones %ul.content-list - if @milestones.blank? diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 8b3974d97f8..d2593179f17 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -2,6 +2,9 @@ - page_title "Todos" - header_title "Todos", dashboard_todos_path +.page-title-holder + %h1.page-title= _('Todos') + - if current_user.todos.any? .top-area %ul.nav-links.mobile-separator.nav.nav-tabs diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index f3792c5e397..869c54d89ea 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -37,6 +37,8 @@ .settings-content = render 'shared/badges/badge_settings' += render_if_exists 'groups/templates_setting', expanded: expanded + %section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' } diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 86c5f6a7aa3..684b51b8552 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -1,5 +1,4 @@ -- @breadcrumb_link = dashboard_groups_path -- breadcrumb_title "Groups" +- @hide_breadcrumbs = true - @hide_top_links = true - page_title 'New Group' - header_title "Groups", dashboard_groups_path diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 8bd5708d490..2cdaa85bdaa 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -6,5 +6,5 @@ -# Don't show a flash message if the message is nil - if value %div{ class: "flash-#{key}" } - %div{ class: "#{container_class} #{extra_flash_class}" } + %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" } %span= value diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index a41d30da450..1b2a4cd6780 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -6,12 +6,13 @@ .mobile-overlay .alert-wrapper = render "layouts/broadcast" - = render 'layouts/header/read_only_banner' + = render "layouts/header/read_only_banner" = yield :flash_message = render "shared/ping_consent" - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" - = render "layouts/flash" + = render "layouts/flash", extra_flash_class: 'limit-container-width' + .d-flex %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } .content{ id: "content-body" } = yield diff --git a/app/views/layouts/dashboard.html.haml b/app/views/layouts/dashboard.html.haml index 489ef245a4d..c10be282952 100644 --- a/app/views/layouts/dashboard.html.haml +++ b/app/views/layouts/dashboard.html.haml @@ -1,5 +1,6 @@ - page_title _("Dashboard") - header_title _("Dashboard"), root_path unless header_title - sidebar "dashboard" +- @hide_breadcrumbs = true = render template: "layouts/application" diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml index 80bda34a3f5..24751ab4e06 100644 --- a/app/views/layouts/explore.html.haml +++ b/app/views/layouts/explore.html.haml @@ -1,4 +1,6 @@ - page_title _("Explore") +- @hide_breadcrumbs = true + - unless current_user - header_title _("Explore GitLab"), explore_root_path diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index 261d758622b..4f3e4031fe3 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -6,9 +6,11 @@ = current_user.name = current_user.to_reference - if current_user.status - .user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } - = emoji_icon current_user.status.emoji - = current_user.status.message_html.html_safe + .user-status.d-flex.align-items-center.prepend-top-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } + %span.user-status-emoji.d-flex.align-items-center + = emoji_icon current_user.status.emoji + %span.user-status-message.str-truncated + = current_user.status.message_html.html_safe %li.divider - if can?(current_user, :update_user_status, current_user) %li @@ -19,12 +21,7 @@ - if current_user_menu?(:settings) %li = link_to s_("CurrentUser|Settings"), profile_path - - if current_user_menu?(:help) - %li - = link_to _("Help"), help_path - - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) - %li.divider - = render 'shared/user_dropdown_contributing_link' - if current_user_menu?(:sign_out) + %li.divider %li = link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 39604611440..596fc3985b3 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -53,6 +53,12 @@ = sprite_icon('todo-done', size: 16) %span.badge.badge-pill.todos-count{ class: ('hidden' if todos_pending_count.zero?) } = todos_count_format(todos_pending_count) + %li.nav-item.header-help.dropdown + = link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do + = sprite_icon('question', size: 16) + = sprite_icon('angle-down', css_class: 'caret-down') + .dropdown-menu.dropdown-menu-right + = render 'layouts/header/help_dropdown' - if header_link?(:user_dropdown) %li.nav-item.header-user.dropdown = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml new file mode 100644 index 00000000000..953c0e7f46c --- /dev/null +++ b/app/views/layouts/header/_help_dropdown.html.haml @@ -0,0 +1,6 @@ +%ul + - if current_user_menu?(:help) + %li + = link_to _("Help"), help_path + - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) + = render 'shared/user_dropdown_contributing_link' diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 4aa22138498..163556f4509 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -12,7 +12,7 @@ = @group.name %ul.sidebar-top-level-items.qa-group-sidebar - if group_sidebar_link?(:overview) - = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups', 'analytics#show'], html_options: { class: 'home' }) do + = nav_link(path: group_overview_nav_link_paths, html_options: { class: 'home' }) do = link_to group_path(@group) do .nav-icon-container = sprite_icon('home') @@ -36,6 +36,16 @@ %span = _('Activity') + = render_if_exists 'groups/sidebar/security_dashboard' + + - if group_sidebar_link?(:contribution_analytics) + = nav_link(path: 'analytics#show') do + = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do + %span + Contribution Analytics + + = render_if_exists "layouts/nav/ee/epic_link", group: @group + - if group_sidebar_link?(:issues) = nav_link(path: issues_sub_menu_items) do = link_to issues_group_path(@group) do @@ -132,4 +142,6 @@ %span = _('CI / CD') + = render_if_exists "groups/ee/settings_nav" + = render 'shared/sidebar_toggle_button' diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml index 977eb350365..cdad617f006 100644 --- a/app/views/layouts/terms.html.haml +++ b/app/views/layouts/terms.html.haml @@ -16,19 +16,18 @@ .content{ id: "content-body" } .card .card-header - .card-title - = brand_header_logo - - logo_text = brand_header_logo_type - - if logo_text.present? - %span.logo-text.prepend-left-8 - = logo_text - - if header_link?(:user_dropdown) - .navbar-collapse - %ul.nav.navbar-nav - %li.header-user.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') - .dropdown-menu.dropdown-menu-right - = render 'layouts/header/current_user_dropdown' + = brand_header_logo + - logo_text = brand_header_logo_type + - if logo_text.present? + %span.logo-text.prepend-left-8 + = logo_text + - if header_link?(:user_dropdown) + .navbar-collapse + %ul.nav.navbar-nav + %li.header-user.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') + .dropdown-menu.dropdown-menu-right + = render 'layouts/header/current_user_dropdown' = yield diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index f398d97028b..0f709c65d0e 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -11,10 +11,10 @@ .md-header %ul.nav.nav-tabs.nav-links.clearfix %li.md-header-tab.active - %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } + %button.js-md-write-button{ tabindex: -1 } Write %li.md-header-tab - %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } + %button.js-md-preview-button{ tabindex: -1 } Preview %li.md-header-toolbar.active diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml index 5adca007f7e..45e1d32980c 100644 --- a/app/views/projects/_wiki.html.haml +++ b/app/views/projects/_wiki.html.haml @@ -5,14 +5,14 @@ = render_wiki_content(@wiki_home, legacy_render_context(params)) - else - can_create_wiki = can?(current_user, :create_wiki, @project) - .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] } - .text-center{ class: container_class } + .landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] } + .col-12.col-md-3.p-0 + .svg-content + = image_tag 'illustrations/wiki_login_empty.svg' + .col-12.col-md-9.text-center.text-md-left.pl-md-0.pl-sm-3.mb-4 %h4 - This project does not have a wiki homepage yet + = _("This project does not have a wiki homepage yet") - if can_create_wiki %p - Add a homepage to your wiki that contains information about your project - %p - We recommend you - = link_to "add a homepage", project_wiki_path(@project, :home) - to your project's wiki and GitLab will show it here instead of this message. + = _("Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message.") + = link_to _("Create your first page"), project_wiki_path(@project, :home) + '?view=create', class: "btn btn-primary" diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml index 2c8dd45670f..bd46f5a4349 100644 --- a/app/views/projects/blob/_template_selectors.html.haml +++ b/app/views/projects/blob/_template_selectors.html.haml @@ -5,13 +5,13 @@ .template-type-selector.js-template-type-selector-wrap.hidden = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', title: "Choose a template type" } ) .license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden - = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector qa-license-dropdown', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) + = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector qa-license-dropdown', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select(@project), project: @project.name, fullname: @project.namespace.human_name } } ) .gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden - = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) + = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names(@project) } } ) .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden - = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) + = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls(@project) } } ) .dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden - = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } ) + = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names(@project) } } ) .template-selectors-undo-menu.hidden %span.text-info Template applied %button.btn.btn-sm.btn-info Undo diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index fdab8a53b41..3f2d96b70e5 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -24,7 +24,7 @@ = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id, legacy_render: params[:legacy_render]) do = editing_preview_title(@blob.name) - = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do + = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 39442564a2b..4be87b9e074 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -7,7 +7,7 @@ New file = render 'template_selectors' .file-editor - = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do + = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths(@project)) do = render 'projects/blob/editor', ref: @ref = render 'shared/new_commit_form', placeholder: "Add new file" diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml index 398f76d379a..0e4b119bb54 100644 --- a/app/views/projects/branches/_panel.html.haml +++ b/app/views/projects/branches/_panel.html.haml @@ -9,8 +9,7 @@ .card.prepend-top-10 .card-header - %h4.card-title - = panel_title + = panel_title %ul.content-list.all-branches - branches.first(overview_max_branches).each do |branch| = render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 95828626bd9..f5685d3b50d 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -48,7 +48,7 @@ - if job.try(:allow_failure) %span.badge.badge-danger allowed to fail - if job.schedulable? - %span.badge.badge-info= s_('DelayedJobs|scheduled') + %span.badge.badge-info= s_('DelayedJobs|delayed') - elsif job.action? %span.badge.badge-info manual @@ -108,7 +108,7 @@ .btn.btn-default.has-tooltip{ disabled: true, title: job.scheduled_at } = sprite_icon('planning') - = duration_in_numbers(job.execute_in, true) + = duration_in_numbers(job.execute_in) - confirmation_message = s_("DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes.") % { job_name: job.name } = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 28998acdc13..4917f4b8903 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -10,4 +10,4 @@ noteable_data: serialize_issuable(@issue), noteable_type: 'Issue', target_type: 'issue', - current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } } + current_user_data: UserSerializer.new.represent(current_user, {only_path: true}, CurrentUserEntity).to_json } } diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index a678cb6f058..5374f4a1de0 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -8,12 +8,13 @@ - create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid) - refs_path = refs_namespace_project_path(@project.namespace, @project, search: '') - .create-mr-dropdown-wrap{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } } + .create-mr-dropdown-wrap.d-inline-block{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } } .btn-group.unavailable %button.btn.btn-grouped{ type: 'button', disabled: 'disabled' } = icon('spinner', class: 'fa-spin') %span.text Checking branch availability… + .btn-group.available.hidden %button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } } = value diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index c39fd0063be..b50b3ca207b 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -77,11 +77,12 @@ #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } } // This element is filled in using JavaScript. - .content-block.emoji-block + .content-block.emoji-block.emoji-block-sticky .row - .col-sm-8.js-noteable-awards + .col-md-12.col-lg-6.js-noteable-awards = render 'award_emoji/awards_block', awardable: @issue, inline: true - .col-sm-4.new-branch-col + .col-md-12.col-lg-6.new-branch-col + #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } } = render 'new_branch' unless @issue.confidential? %section.issuable-discussion diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index ef2fa8668c0..efc2d88172e 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -51,8 +51,10 @@ = tab_link_for @merge_request, :diffs do Changes %span.badge.badge-pill= @merge_request.diff_size - - #js-vue-discussion-counter + .d-inline-flex.flex-wrap + #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request), + notes_filters: UserPreference.notes_filters.to_json } } + #js-vue-discussion-counter .tab-content#diff-notes-app #notes.notes.tab-pane.voting_notes diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index d99b809c387..eede8704564 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -1,5 +1,4 @@ -- @breadcrumb_link = dashboard_projects_path -- breadcrumb_title "Projects" +- @hide_breadcrumbs = true - @hide_top_links = true - page_title 'New Project' - header_title "Projects", dashboard_projects_path diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index dbb563f51ea..2575efc0981 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -13,7 +13,11 @@ = pluralize @pipeline.total_size, "job" - if @pipeline.ref from - = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name" + - if @pipeline.ref_exists? + = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name" + - else + %span.ref-name + = @pipeline.ref - if @pipeline.duration in = time_interval_in_words(@pipeline.duration) diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml index 9a06eca89bb..1913d06a6f8 100644 --- a/app/views/projects/protected_branches/shared/_branches_list.html.haml +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -1,8 +1,7 @@ .protected-branches-list.js-protected-branches-list.qa-protected-branches-list - if @protected_branches.empty? .card-header.bg-white - %h3.card-title.mb-0 - Protected branch (#{@protected_branches_count}) + Protected branch (#{@protected_branches_count}) %p.settings-message.text-center There are currently no protected branches, protect a branch with the form above. - else diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml index c3b8f2f8964..d617d85afc2 100644 --- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml @@ -2,8 +2,7 @@ %input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' } .card .card-header - %h3.card-title - Protect a branch + Protect a branch .card-body = form_errors(@protected_branch) .form-group.row diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml index b274c73d035..cbf1938664c 100644 --- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml @@ -2,8 +2,7 @@ %input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' } .card .card-header - %h3.card-title - Protect a tag + Protect a tag .card-body = form_errors(@protected_tag) .form-group.row diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml index c3081d75fb4..382ea848243 100644 --- a/app/views/projects/protected_tags/shared/_tags_list.html.haml +++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml @@ -1,8 +1,7 @@ .protected-tags-list.js-protected-tags-list - if @protected_tags.empty? .card-header - %h3.card-title - Protected tag (#{@protected_tags_count}) + Protected tag (#{@protected_tags_count}) %p.settings-message.text-center There are currently no protected tags, protect a tag with the form above. - else diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index 0426f2215ad..db1f15f96b8 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -18,8 +18,7 @@ .col-lg-12 .card .card-header - %h4.card-title - = s_('ContainerRegistry|How to use the Container Registry') + = s_('ContainerRegistry|How to use the Container Registry') .card-body %p - link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank') diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml index 98d64fafe86..597c029f755 100644 --- a/app/views/projects/services/prometheus/_metrics.html.haml +++ b/app/views/projects/services/prometheus/_metrics.html.haml @@ -2,9 +2,8 @@ .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-header - %h3.card-title - = s_('PrometheusService|Common metrics') - %span.badge.badge-pill.js-monitored-count 0 + = s_('PrometheusService|Common metrics') + %span.badge.badge-pill.js-monitored-count 0 .card-body .loading-metrics.js-loading-metrics %p.prepend-top-10.prepend-left-10 @@ -17,10 +16,9 @@ .card.hidden.js-panel-missing-env-vars .card-header - %h3.card-title - = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel') - = s_('PrometheusService|Missing environment variable') - %span.badge.badge-pill.js-env-var-count 0 + = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel') + = s_('PrometheusService|Missing environment variable') + %span.badge.badge-pill.js-env-var-count 0 .card-body.hidden .flash-container .flash-notice diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 24724394259..5e6d06d980e 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -20,8 +20,9 @@ .col-sm-10.create-from .dropdown = hidden_field_tag :ref, default_ref - = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do + = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select monospace', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do .text-left.dropdown-toggle-text= default_ref + = icon('chevron-down') = render 'shared/ref_dropdown', dropdown_class: 'wide' .form-text.text-muted = s_('TagsPage|Existing branch name, tag, or commit SHA') diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 587aeafa82f..5e0523f0b96 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -1,6 +1,6 @@ .tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path } .table-holder - %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" } + %table.table#tree-slider{ class: "table_#{@hex_path} tree-table qa-file-tree" } %thead %tr %th= s_('ProjectFileTree|Name') diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml index a15bb4c4f3f..a559ce41e57 100644 --- a/app/views/projects/triggers/_index.html.haml +++ b/app/views/projects/triggers/_index.html.haml @@ -3,8 +3,7 @@ = render "projects/triggers/content" .card .card-header - %h4.card-title - Manage your project's triggers + Manage your project's triggers .card-body = render "projects/triggers/form", btn_text: "Add trigger" %hr diff --git a/app/views/shared/_user_dropdown_contributing_link.html.haml b/app/views/shared/_user_dropdown_contributing_link.html.haml index 333d6fa3489..564d21a39be 100644 --- a/app/views/shared/_user_dropdown_contributing_link.html.haml +++ b/app/views/shared/_user_dropdown_contributing_link.html.haml @@ -1,5 +1,3 @@ %li = link_to "https://about.gitlab.com/contributing", target: '_blank', class: 'text-nowrap' do = _("Contribute to GitLab") - = sprite_icon('external-link', size: 16) -%li.divider diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index c4d177361e7..cb45928d9a5 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -36,7 +36,7 @@ %button.btn.btn-link{ type: 'button' } = sprite_icon('search') %span - Press Enter or click to search + = _('Press Enter or click to search') %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item %button.btn.btn-link{ type: 'button' } @@ -61,7 +61,7 @@ %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'none' } } %button.btn.btn-link{ type: 'button' } - No Assignee + = _('No Assignee') %li.divider.droplab-item-ignore - if current_user = render 'shared/issuable/user_dropdown_item', @@ -74,13 +74,16 @@ %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'none' } } %button.btn.btn-link{ type: 'button' } - No Milestone + = _('None') + %li.filter-dropdown-item{ data: { value: 'any' } } + %button.btn.btn-link{ type: 'button' } + = _('Any') %li.filter-dropdown-item{ data: { value: 'upcoming' } } %button.btn.btn-link{ type: 'button' } - Upcoming + = _('Upcoming') %li.filter-dropdown-item{ 'data-value' => 'started' } %button.btn.btn-link{ type: 'button' } - Started + = _('Started') %li.divider.droplab-item-ignore %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item @@ -90,7 +93,7 @@ %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'none' } } %button.btn.btn-link{ type: 'button' } - No Label + = _('No Label') %li.divider.droplab-item-ignore %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml index 583b33a8a1b..660ee6d5777 100644 --- a/app/views/shared/issuable/_sidebar_todo.html.haml +++ b/app/views/shared/issuable/_sidebar_todo.html.haml @@ -1,6 +1,6 @@ - is_collapsed = local_assigns.fetch(:is_collapsed, false) -- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark todo as done') -- todo_content = is_collapsed ? icon('plus-square') : _('Add todo') +- 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') %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'), diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml index 362569bfbaf..f62eed694d2 100644 --- a/app/views/shared/runners/show.html.haml +++ b/app/views/shared/runners/show.html.haml @@ -24,7 +24,7 @@ %td= @runner.active? ? 'Yes' : 'No' %tr %td Protected - %td= @runner.active? ? _('Yes') : _('No') + %td= @runner.ref_protected? ? 'Yes' : 'No' %tr %td Can run untagged jobs %td= @runner.run_untagged? ? 'Yes' : 'No' diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml index c8a5e199674..6bc748d346e 100644 --- a/app/views/snippets/new.html.haml +++ b/app/views/snippets/new.html.haml @@ -1,8 +1,9 @@ - @hide_top_links = true -- add_to_breadcrumbs "Snippets", dashboard_snippets_path -- breadcrumb_title "New" +- @hide_breadcrumbs = true - page_title "New Snippet" -%h3.page-title - New Snippet -%hr -= render "shared/snippets/form", url: snippets_path(@snippet) + +.page-title-holder + %h1.page-title= _('New Snippet') + +.prepend-top-default + = render "shared/snippets/form", url: snippets_path(@snippet) diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb index d9df42c9e17..2965f3b1150 100644 --- a/app/workers/namespaceless_project_destroy_worker.rb +++ b/app/workers/namespaceless_project_destroy_worker.rb @@ -32,7 +32,5 @@ class NamespacelessProjectDestroyWorker merge_requests = project.forked_from_project.merge_requests.opened.from_project(project) merge_requests.update_all(state: 'closed') - - project.forked_project_link.destroy end end diff --git a/bin/profile-url b/bin/profile-url index d8d09641624..9e8585aabba 100755 --- a/bin/profile-url +++ b/bin/profile-url @@ -48,7 +48,7 @@ require File.expand_path('../config/environment', File.dirname(__FILE__)) result = Gitlab::Profiler.profile(options[:url], logger: Logger.new(options[:sql_output]), post_data: options[:post_data], - user: User.find_by_username(options[:username]), + user: UserFinder.new(options[:username]).find_by_username, private_token: ENV['PRIVATE_TOKEN']) printer = RubyProf::CallStackPrinter.new(result) diff --git a/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml b/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml deleted file mode 100644 index 27c70318b92..00000000000 --- a/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix link handling for issue cards to avoid too sensitive drag events. -merge_request: 21910 -author: Johann Hubert Sonntagbauer -type: fixed diff --git a/changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml b/changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml deleted file mode 100644 index e71b09ec2e3..00000000000 --- a/changelogs/unreleased/-50928-stale-list-of-issues-on-board-after-click-back.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix stale issue boards after browser back -merge_request: 22006 -author: Johann Hubert Sonntagbauer -type: fixed diff --git a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml deleted file mode 100644 index 074cc9d642b..00000000000 --- a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show percentage of language detection on the language bar -merge_request: 22056 -author: Johann Hubert Sonntagbauer -type: added diff --git a/changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml b/changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml deleted file mode 100644 index 4ceaa7e3139..00000000000 --- a/changelogs/unreleased/1801-allow-event_filter-to-be-set-in-the-url.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Allow events filter to be set in the URL in addition to cookie" -merge_request: 21557 -author: Igor @igas -type: added diff --git a/changelogs/unreleased/21307-send-deployment-information-in-job-api.yml b/changelogs/unreleased/21307-send-deployment-information-in-job-api.yml deleted file mode 100644 index 8f9e24428be..00000000000 --- a/changelogs/unreleased/21307-send-deployment-information-in-job-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Send deployment information in job API -merge_request: 21307 -author: -type: other diff --git a/changelogs/unreleased/21617-initialize-projects-with-readme.yml b/changelogs/unreleased/21617-initialize-projects-with-readme.yml deleted file mode 100644 index 168f6af60c5..00000000000 --- a/changelogs/unreleased/21617-initialize-projects-with-readme.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds a initialize_with_readme parameter to POST /projects -merge_request: 21617 -author: Steve -type: added diff --git a/changelogs/unreleased/21970-fix-bamboo-results.yml b/changelogs/unreleased/21970-fix-bamboo-results.yml new file mode 100644 index 00000000000..2fbb354c477 --- /dev/null +++ b/changelogs/unreleased/21970-fix-bamboo-results.yml @@ -0,0 +1,5 @@ +--- +title: "Correctly process Bamboo API result array" +merge_request: 21970 +author: Alex Lossent +type: fixed
\ No newline at end of file diff --git a/changelogs/unreleased/22104-fix-environment-name-overlap.yml b/changelogs/unreleased/22104-fix-environment-name-overlap.yml deleted file mode 100644 index aaa1a1709c8..00000000000 --- a/changelogs/unreleased/22104-fix-environment-name-overlap.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it." -merge_request: 22104 -type: fixed diff --git a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml deleted file mode 100644 index e24c55e3bad..00000000000 --- a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add autocomplete drop down filter for project snippets -merge_request: 21458 -author: Fabian Schneider -type: added diff --git a/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml b/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml deleted file mode 100644 index a5ffc197a0c..00000000000 --- a/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Set a header for custom error pages to prevent them from being intercepted - by gitlab-workhorse -merge_request: 21870 -author: David Piegza -type: fixed diff --git a/changelogs/unreleased/23986-choose-commit-email.yml b/changelogs/unreleased/23986-choose-commit-email.yml deleted file mode 100644 index 1ebd62cd5b1..00000000000 --- a/changelogs/unreleased/23986-choose-commit-email.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow user to choose the email used for commits made through GitLab's UI. -merge_request: 21213 -author: Joshua Campbell -type: added diff --git a/changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml b/changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml deleted file mode 100644 index d835d25f39b..00000000000 --- a/changelogs/unreleased/24128-fix-comment-unresolve-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix resolved discussions being unresolved when commented on -merge_request: 21881 -author: -type: fixed diff --git a/changelogs/unreleased/26723-discussion-filters.yml b/changelogs/unreleased/26723-discussion-filters.yml new file mode 100644 index 00000000000..3abe95bf30d --- /dev/null +++ b/changelogs/unreleased/26723-discussion-filters.yml @@ -0,0 +1,5 @@ +--- +title: Filter notes by comments or activity for issues and merge requests +merge_request: +author: +type: added diff --git a/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml b/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml deleted file mode 100644 index 973dcd0496a..00000000000 --- a/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support Kubernetes RBAC for GitLab Managed Apps when creating new clusters -merge_request: 21401 -author: -type: changed diff --git a/changelogs/unreleased/31887-remove-images-from-todos.yml b/changelogs/unreleased/31887-remove-images-from-todos.yml deleted file mode 100644 index 36388f66514..00000000000 --- a/changelogs/unreleased/31887-remove-images-from-todos.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Images are no longer displayed in Todo descriptions -merge_request: 21704 -author: -type: fixed diff --git a/changelogs/unreleased/32959-update-todo-icon.yml b/changelogs/unreleased/32959-update-todo-icon.yml new file mode 100644 index 00000000000..f08fd6aa89f --- /dev/null +++ b/changelogs/unreleased/32959-update-todo-icon.yml @@ -0,0 +1,5 @@ +--- +title: Update Todo icons in collapsed sidebar for Issues and MRs +merge_request: 22534 +author: +type: changed diff --git a/changelogs/unreleased/35476-lazy-image-intersectionobserver.yml b/changelogs/unreleased/35476-lazy-image-intersectionobserver.yml deleted file mode 100644 index c2c760c0ee0..00000000000 --- a/changelogs/unreleased/35476-lazy-image-intersectionobserver.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Improve lazy image loading performance by using IntersectionObserver where - available -merge_request: 21565 -author: -type: performance diff --git a/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml b/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml deleted file mode 100644 index 04662a7cfe2..00000000000 --- a/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Adds support for Gitaly ListLastCommitsForTree RPC in order to make bulk-fetch - of commits more performant -merge_request: 21921 -author: -type: performance diff --git a/changelogs/unreleased/38304-username-API-call-case-sensitive.yml b/changelogs/unreleased/38304-username-API-call-case-sensitive.yml new file mode 100644 index 00000000000..b89778b6c23 --- /dev/null +++ b/changelogs/unreleased/38304-username-API-call-case-sensitive.yml @@ -0,0 +1,5 @@ +--- +title: "Use case insensitve username lookups" +merge_request: 21728 +author: William George +type: fixed
\ No newline at end of file diff --git a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml deleted file mode 100644 index 8131e2ff54f..00000000000 --- a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Rephrase 2FA and TOTP documentation and view -merge_request: 21998 -author: Marc Schwede -type: other diff --git a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml deleted file mode 100644 index 1ebad500e9f..00000000000 --- a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Instance Configuration page now displays correct SSH fingerprints -merge_request: 22081 -author: -type: fixed diff --git a/changelogs/unreleased/41040-long-webhook-url-problem.yml b/changelogs/unreleased/41040-long-webhook-url-problem.yml deleted file mode 100644 index 4057e1ff325..00000000000 --- a/changelogs/unreleased/41040-long-webhook-url-problem.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix long webhook URL overflow for custom integration. -merge_request: -author: Kukovskii Vladimir -type: fixed diff --git a/changelogs/unreleased/41205-fix-filtering-issues.yml b/changelogs/unreleased/41205-fix-filtering-issues.yml deleted file mode 100644 index ef1a11aad08..00000000000 --- a/changelogs/unreleased/41205-fix-filtering-issues.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Filter issues without an Assignee via the API -merge_request: 22009 -author: Eva Kadlecová -type: fixed diff --git a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml deleted file mode 100644 index 582d7824d27..00000000000 --- a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Simplify runner registration token resetting -merge_request: 21658 -author: -type: changed diff --git a/changelogs/unreleased/42611-removed-branch-link.yml b/changelogs/unreleased/42611-removed-branch-link.yml new file mode 100644 index 00000000000..03a206871b4 --- /dev/null +++ b/changelogs/unreleased/42611-removed-branch-link.yml @@ -0,0 +1,5 @@ +--- +title: Only render link to branch when branch still exists in pipeline page +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml b/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml deleted file mode 100644 index 171779817c8..00000000000 --- a/changelogs/unreleased/42861-move-include-external-files-in-gitlab-ci-yml-from-starter-to-libre.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move including external files in .gitlab-ci.yml from Starter to Libre -merge_request: 21603 -author: -type: changed diff --git a/changelogs/unreleased/43422-Update-images-in-group-docs.yml b/changelogs/unreleased/43422-Update-images-in-group-docs.yml new file mode 100644 index 00000000000..4c4146589ad --- /dev/null +++ b/changelogs/unreleased/43422-Update-images-in-group-docs.yml @@ -0,0 +1,5 @@ +--- +title: Update images in group docs +merge_request: 22031 +author: Marc Schwede +type: other diff --git a/changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml b/changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml deleted file mode 100644 index cea1436ae8b..00000000000 --- a/changelogs/unreleased/43832-adds-chdmod-to-commits-actions-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allows to chmod file with commits API -merge_request: 21866 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/44596-double-title-merge-request-message.yml b/changelogs/unreleased/44596-double-title-merge-request-message.yml deleted file mode 100644 index 714d16977fb..00000000000 --- a/changelogs/unreleased/44596-double-title-merge-request-message.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix double title in merge request chat messages. -merge_request: 21670 -author: Kukovskii Vladimir -type: fixed diff --git a/changelogs/unreleased/44627-add-link-md-editor.yml b/changelogs/unreleased/44627-add-link-md-editor.yml deleted file mode 100644 index 65551ce9c14..00000000000 --- a/changelogs/unreleased/44627-add-link-md-editor.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add link button to markdown editor toolbar -merge_request: 18579 -author: Jan Beckmann -type: added diff --git a/changelogs/unreleased/44768-lazy-load-xterm-css.yml b/changelogs/unreleased/44768-lazy-load-xterm-css.yml deleted file mode 100644 index 85f7b1984e0..00000000000 --- a/changelogs/unreleased/44768-lazy-load-xterm-css.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Lazy load xterm custom colors css -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml b/changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml deleted file mode 100644 index 4b398e9419d..00000000000 --- a/changelogs/unreleased/44998-split-admin-settings-into-multiple-sub-pages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Split admin settings into multiple sub pages -merge_request: 21467 -author: -type: other diff --git a/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml b/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml deleted file mode 100644 index a7f24742588..00000000000 --- a/changelogs/unreleased/45016-add-web-ide-commits-to-usage-ping.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds Web IDE commits to usage ping -merge_request: 22007 -author: -type: added diff --git a/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml b/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml deleted file mode 100644 index 64776abdc07..00000000000 --- a/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes modal button alignment -merge_request: 22024 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/45754-issue-mr-and-archived-projects.yml b/changelogs/unreleased/45754-issue-mr-and-archived-projects.yml deleted file mode 100644 index d81f47d9654..00000000000 --- a/changelogs/unreleased/45754-issue-mr-and-archived-projects.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Issue and MR count now ignores archived projects -merge_request: 21721 -author: -type: fixed diff --git a/changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml b/changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml deleted file mode 100644 index 34394396020..00000000000 --- a/changelogs/unreleased/45754-open-issues-from-archived-project-listed-in-group-issue-board.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: No longer show open issues from archived projects in group issue board -merge_request: 21721 -author: -type: fixed diff --git a/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml b/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml deleted file mode 100644 index dd230d5f35e..00000000000 --- a/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add GitLab version components to CI environment variables -merge_request: 21853 -author: -type: added diff --git a/changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml b/changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml deleted file mode 100644 index 07549781330..00000000000 --- a/changelogs/unreleased/46733-move-filter-dropdown-from-font-awesome-to-our-own-icons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Updated icons used in filtered search dropdowns -merge_request: 21694 -author: -type: changed diff --git a/changelogs/unreleased/46884-remove-card-title.yml b/changelogs/unreleased/46884-remove-card-title.yml new file mode 100644 index 00000000000..95f08a67638 --- /dev/null +++ b/changelogs/unreleased/46884-remove-card-title.yml @@ -0,0 +1,5 @@ +--- +title: Remove .card-title from .card-header for BS4 migration +merge_request: 19335 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml b/changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml deleted file mode 100644 index e0dc26301d4..00000000000 --- a/changelogs/unreleased/47398-user-is-unable-revoke-a-authorized-application-unless-user-oauth-applications-is-checked-in-admin-settings.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Allow user to revoke an authorized application even if User OAuth applications - setting is disabled in admin settings -merge_request: 21835 -author: -type: changed diff --git a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml deleted file mode 100644 index f70011ac827..00000000000 --- a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reduce queries needed to compute notification recipients -merge_request: 22050 -author: -type: performance diff --git a/changelogs/unreleased/48004-db-initialize-migrate.yml b/changelogs/unreleased/48004-db-initialize-migrate.yml deleted file mode 100644 index 0d691babeca..00000000000 --- a/changelogs/unreleased/48004-db-initialize-migrate.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support db migration and initialization for Auto DevOps -merge_request: 21955 -author: -type: added diff --git a/changelogs/unreleased/48222-fix-todos-status-button.yml b/changelogs/unreleased/48222-fix-todos-status-button.yml deleted file mode 100644 index 2f7c79a07d0..00000000000 --- a/changelogs/unreleased/48222-fix-todos-status-button.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix the state of the Done button when there is an error in the GitLab Todos - section -merge_request: -author: marcos8896 -type: fixed diff --git a/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml b/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml deleted file mode 100644 index 042731fb9be..00000000000 --- a/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Skip creating auto devops jobs for sast, container_scanning, dast, dependency_scanning - when not licensed -merge_request: 21959 -author: -type: performance diff --git a/changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml b/changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml new file mode 100644 index 00000000000..8fd1b48b00e --- /dev/null +++ b/changelogs/unreleased/48731-show-empty-state-on-wiki-only-projects.yml @@ -0,0 +1,6 @@ +--- +title: Update the empty state on wiki-only projects to display an empty state that + is more consistent with the rest of the system. +merge_request: 22262 +author: +type: changed diff --git a/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml b/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml deleted file mode 100644 index 75018d57e5b..00000000000 --- a/changelogs/unreleased/48902-fix-diff-vertical-alignment.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix vertical alignment of text in diffs -merge_request: 21573 -author: -type: fixed diff --git a/changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml b/changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml deleted file mode 100644 index e82bda6819f..00000000000 --- a/changelogs/unreleased/4907-improve-build-create-performance-for-license-management.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Dont create license_management build when not included in license -merge_request: 21958 -author: -type: performance diff --git a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml deleted file mode 100644 index 2c65c92dd8b..00000000000 --- a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Set user status from within user menu -merge_request: 21643 -author: -type: added diff --git a/changelogs/unreleased/49329-mr-show-commit-details.yml b/changelogs/unreleased/49329-mr-show-commit-details.yml deleted file mode 100644 index 23cfc0c675e..00000000000 --- a/changelogs/unreleased/49329-mr-show-commit-details.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show commit details for selected commit in MR diffs -merge_request: 21784 -author: -type: fixed diff --git a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml deleted file mode 100644 index 5e2be42c8b7..00000000000 --- a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds new 'Overview' tab on user profile page -merge_request: 21663 -author: -type: other diff --git a/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml b/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml deleted file mode 100644 index aa19b816b0b..00000000000 --- a/changelogs/unreleased/49943-resolve-filter-bar-height-changes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix filter bar height bug when a tag is added -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/49990-enable-omniauth-by-default.yml b/changelogs/unreleased/49990-enable-omniauth-by-default.yml deleted file mode 100644 index 0c08bdf6ece..00000000000 --- a/changelogs/unreleased/49990-enable-omniauth-by-default.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable omniauth by default -merge_request: 21700 -author: -type: changed diff --git a/changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml b/changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml deleted file mode 100644 index 438c847327a..00000000000 --- a/changelogs/unreleased/50111-improve-design-of-cluster-apps-to-handle-larger-quantity.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve install flow of Kubernetes cluster apps -merge_request: 21567 -author: -type: changed diff --git a/changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml b/changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml deleted file mode 100644 index e6dd78a7a19..00000000000 --- a/changelogs/unreleased/50161-hide-close-mr-button-when-merged.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hides Close Merge request btn on merged Merge request -merge_request: 21840 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml b/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml deleted file mode 100644 index cc7a79d25e5..00000000000 --- a/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix sorting by priority or popularity on group issues page, when also searching - issue content -merge_request: 21521 -author: -type: fixed diff --git a/changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml b/changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml deleted file mode 100644 index 7aee8720888..00000000000 --- a/changelogs/unreleased/50289-vendor-ci-yml-for-the-last-time.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update all gitlab CI templates from gitlab-org/gitlab-ci-yml -merge_request: 21929 -author: -type: added diff --git a/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml deleted file mode 100644 index 09ec4b8d73d..00000000000 --- a/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix timeout when running the RemoveRestrictedTodos background migration -merge_request: 21893 -author: -type: fixed diff --git a/changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml b/changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml deleted file mode 100644 index 539aea4f333..00000000000 --- a/changelogs/unreleased/50461-add-retried-builds-in-pipeline-stage-endpoint.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add retried jobs to pipeline stage -merge_request: 21558 -author: -type: other diff --git a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml deleted file mode 100644 index e3619149d2a..00000000000 --- a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix performance bar modal position -merge_request: 21577 -author: -type: fixed diff --git a/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml b/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml deleted file mode 100644 index 88a2ab802c8..00000000000 --- a/changelogs/unreleased/50677-fix-cherry-pick-branch-empty-name.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes 500 for cherry pick API with empty branch name -merge_request: 21501 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/50678-ignores-project-pending-delete.yml b/changelogs/unreleased/50678-ignores-project-pending-delete.yml deleted file mode 100644 index e4594abba99..00000000000 --- a/changelogs/unreleased/50678-ignores-project-pending-delete.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Excludes project marked from deletion to projects API -merge_request: 21542 -author: Jacopo Beschi @jacopo-beschi -type: changed diff --git a/changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml b/changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml new file mode 100644 index 00000000000..dfda65ef91b --- /dev/null +++ b/changelogs/unreleased/50728-re-arrange-help-related-user-menu-items-into-new-help-menu.yml @@ -0,0 +1,5 @@ +--- +title: Re-arrange help-related user menu items into new Help menu +merge_request: 22195 +author: +type: added diff --git a/changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml b/changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml deleted file mode 100644 index f9ed5683e63..00000000000 --- a/changelogs/unreleased/50808-choosing-initialize-repo-with-a-readme-breaks-project-created-from-template.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'create from template: hide checkbox for initializing repository with readme' -merge_request: 21646 -author: -type: other diff --git a/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml b/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml deleted file mode 100644 index 24e231ed88a..00000000000 --- a/changelogs/unreleased/50835-add-filtering-sorting-for-labels-on-labels-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add sorting for labels on labels page -merge_request: 21642 -author: -type: added diff --git a/changelogs/unreleased/50904-move-job-page-vue.yml b/changelogs/unreleased/50904-move-job-page-vue.yml deleted file mode 100644 index e907c6301ec..00000000000 --- a/changelogs/unreleased/50904-move-job-page-vue.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use Vue components and new API to render Artifacts, Trigger Variables and Commit blocks on Job page -merge_request: 21777 -author: -type: other diff --git a/changelogs/unreleased/50904-update-scroll-utils.yml b/changelogs/unreleased/50904-update-scroll-utils.yml deleted file mode 100644 index e301de1a40b..00000000000 --- a/changelogs/unreleased/50904-update-scroll-utils.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extracts scroll position check into reusable functions -merge_request: -author: -type: other diff --git a/changelogs/unreleased/50904-use-vuex-store-job.yml b/changelogs/unreleased/50904-use-vuex-store-job.yml deleted file mode 100644 index 5b1112a4f5b..00000000000 --- a/changelogs/unreleased/50904-use-vuex-store-job.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Uses Vuex store in job details page and removes old mediator pattern -merge_request: -author: -type: other diff --git a/changelogs/unreleased/50904-vuex-show-block.yml b/changelogs/unreleased/50904-vuex-show-block.yml deleted file mode 100644 index 5607ba3216f..00000000000 --- a/changelogs/unreleased/50904-vuex-show-block.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Renders Job show page in new Vue app -merge_request: -author: -type: other diff --git a/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml b/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml deleted file mode 100644 index 5c8c78b8de8..00000000000 --- a/changelogs/unreleased/50989-add-trigger-information-to-job-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add trigger information in job API -merge_request: 21495 -author: -type: other diff --git a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml deleted file mode 100644 index 99946b954ce..00000000000 --- a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove 'rbac_clusters' feature flag -merge_request: 22096 -author: -type: changed diff --git a/changelogs/unreleased/51021-more-attr-encrypted.yml b/changelogs/unreleased/51021-more-attr-encrypted.yml deleted file mode 100644 index 0e18c59f1bb..00000000000 --- a/changelogs/unreleased/51021-more-attr-encrypted.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Encrypt webhook tokens and URLs in the database -merge_request: 21645 -author: -type: security diff --git a/changelogs/unreleased/51050-fix.yml b/changelogs/unreleased/51050-fix.yml deleted file mode 100644 index b58f9ae34f8..00000000000 --- a/changelogs/unreleased/51050-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix broken styling when issue board is collapsed -merge_request: 21868 -author: Andrea Leone -type: fixed diff --git a/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml b/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml deleted file mode 100644 index fdc75e28824..00000000000 --- a/changelogs/unreleased/51112-add-status-illustration-in-job-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add empty state illustration information in job API -merge_request: 21532 -author: -type: other diff --git a/changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml b/changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml deleted file mode 100644 index df43f1dfbae..00000000000 --- a/changelogs/unreleased/51273-expose-runners-for-build-in-job-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose project runners in job API -merge_request: 21618 -author: -type: other diff --git a/changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml b/changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml deleted file mode 100644 index 1d2075ae549..00000000000 --- a/changelogs/unreleased/51282-link-to-user-snippets-on-new-user-snippet-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add link to User Snippets in breadcrumbs of New User Snippet page -merge_request: -author: J.D. Bean -type: add diff --git a/changelogs/unreleased/51386-broken-border-reports.yml b/changelogs/unreleased/51386-broken-border-reports.yml new file mode 100644 index 00000000000..720b4edc467 --- /dev/null +++ b/changelogs/unreleased/51386-broken-border-reports.yml @@ -0,0 +1,5 @@ +--- +title: Fixes broken borders for reports section in MR widget +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml b/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml deleted file mode 100644 index da02a91041b..00000000000 --- a/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Docs for Project/Groups members API with inherited members -merge_request: 21984 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/51450-vendor-refactor-registry-login.yml b/changelogs/unreleased/51450-vendor-refactor-registry-login.yml deleted file mode 100644 index 417f12b4955..00000000000 --- a/changelogs/unreleased/51450-vendor-refactor-registry-login.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Vendor Auto-DevOps.gitlab-ci.yml to refactor registry_login -merge_request: 21714 -author: Laurent Goderre @LaurentGoderre -type: changed diff --git a/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml b/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml deleted file mode 100644 index d4e4503508d..00000000000 --- a/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevents private profile help link from toggling checkbox -merge_request: 21757 -author: -type: other diff --git a/changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml b/changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml deleted file mode 100644 index fcc5aae2bda..00000000000 --- a/changelogs/unreleased/51509-remove-sidekiq-limit-fetch.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove background job throttling feature -merge_request: 21748 -author: -type: removed diff --git a/changelogs/unreleased/51564-fix-commit-email-usage.yml b/changelogs/unreleased/51564-fix-commit-email-usage.yml deleted file mode 100644 index 2f1b042ae8a..00000000000 --- a/changelogs/unreleased/51564-fix-commit-email-usage.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Respect the user commit email in more places -merge_request: 21773 -author: -type: fixed diff --git a/changelogs/unreleased/51569-performance-bar.yml b/changelogs/unreleased/51569-performance-bar.yml deleted file mode 100644 index ab62e7d3b3e..00000000000 --- a/changelogs/unreleased/51569-performance-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes performance bar looking for a key in a undefined prop -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml b/changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml deleted file mode 100644 index 50710ca0aa8..00000000000 --- a/changelogs/unreleased/51571-wrapper-rake-task-uploads-migrate-os.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add wrapper rake task to migrate all uploads to OS -merge_request: 21779 -author: -type: other diff --git a/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml b/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml deleted file mode 100644 index 6720430e5fc..00000000000 --- a/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Retroactively fill pipeline source for external pipelines. -merge_request: 21814 -author: -type: other diff --git a/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml new file mode 100644 index 00000000000..ad43c512ba3 --- /dev/null +++ b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml @@ -0,0 +1,5 @@ +--- +title: Introduce new model to persist specific cluster information +merge_request: 22404 +author: +type: added diff --git a/changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml b/changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml deleted file mode 100644 index b3caa119253..00000000000 --- a/changelogs/unreleased/51725-push-mirrors-default-branch-reset-to-master.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Doesn't synchronize the default branch for push mirrors -merge_request: 21861 -author: -type: fixed diff --git a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml deleted file mode 100644 index 30304e5a4ac..00000000000 --- a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allows to filter issues by Any milestone in the API -merge_request: 22080 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml deleted file mode 100644 index e67cc27f852..00000000000 --- a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Includes commit stats in POST project commits API -merge_request: 21968 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/51839-remove-sorting-on-project-tags.yml b/changelogs/unreleased/51839-remove-sorting-on-project-tags.yml deleted file mode 100644 index 38a7c06b34c..00000000000 --- a/changelogs/unreleased/51839-remove-sorting-on-project-tags.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Preserve order of project tags list -merge_request: 21897 -author: -type: changed diff --git a/changelogs/unreleased/51925-expose-has_trace-in-job-api.yml b/changelogs/unreleased/51925-expose-has_trace-in-job-api.yml deleted file mode 100644 index eade86d97ac..00000000000 --- a/changelogs/unreleased/51925-expose-has_trace-in-job-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose has_trace in job API -merge_request: 21950 -author: -type: other diff --git a/changelogs/unreleased/51942-auto-devops-local-tiller.yml b/changelogs/unreleased/51942-auto-devops-local-tiller.yml deleted file mode 100644 index 0088d6dc598..00000000000 --- a/changelogs/unreleased/51942-auto-devops-local-tiller.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use local tiller for Auto DevOps -merge_request: 22036 -author: -type: changed diff --git a/changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml b/changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml new file mode 100644 index 00000000000..63fa84d2d51 --- /dev/null +++ b/changelogs/unreleased/51955-change-single-item-breadcrumbs-to-page-titles.yml @@ -0,0 +1,5 @@ +--- +title: Change single-item breadcrumbs to page titles +merge_request: 22155 +author: +type: changed diff --git a/changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml b/changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml deleted file mode 100644 index d56c814b139..00000000000 --- a/changelogs/unreleased/52035-selecting-an-autofill-suggestion-for-project-name-will-not-update-the-project-slug.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update project path on project name autofill -merge_request: 22016 -author: -type: other diff --git a/changelogs/unreleased/52059-filter-milestone-by-none-any.yml b/changelogs/unreleased/52059-filter-milestone-by-none-any.yml new file mode 100644 index 00000000000..5511440c0b9 --- /dev/null +++ b/changelogs/unreleased/52059-filter-milestone-by-none-any.yml @@ -0,0 +1,5 @@ +--- +title: Added `Any` option to milestones filter +merge_request: 22351 +author: Heinrich Lee Yu +type: added diff --git a/changelogs/unreleased/52178-markdown-table-borders.yml b/changelogs/unreleased/52178-markdown-table-borders.yml deleted file mode 100644 index 965f21f2a97..00000000000 --- a/changelogs/unreleased/52178-markdown-table-borders.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add borders and white background to markdown tables -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml deleted file mode 100644 index d96c2bc7acd..00000000000 --- a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Trim whitespace when inviting a new user by email -merge_request: 22119 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml b/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml deleted file mode 100644 index 3fea6f33451..00000000000 --- a/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix incorrect parent path on group settings page -merge_request: 22142 -author: -type: fixed diff --git a/changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml new file mode 100644 index 00000000000..8ea0693037f --- /dev/null +++ b/changelogs/unreleased/52299-follow-up-from-resolve-add-status-message-from-within-user-menu.yml @@ -0,0 +1,5 @@ +--- +title: Fix size of emojis of user status in user menu +merge_request: 22194 +author: +type: fixed diff --git a/changelogs/unreleased/52361-fix-file-tree-mobile.yml b/changelogs/unreleased/52361-fix-file-tree-mobile.yml deleted file mode 100644 index fe978eeca2d..00000000000 --- a/changelogs/unreleased/52361-fix-file-tree-mobile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve MR file tree in smaller screens -merge_request: 22273 -author: -type: fixed diff --git a/changelogs/unreleased/52472-pipeline-endpoint-json.yml b/changelogs/unreleased/52472-pipeline-endpoint-json.yml deleted file mode 100644 index feff195beb8..00000000000 --- a/changelogs/unreleased/52472-pipeline-endpoint-json.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix caching issue with pipelines URL -merge_request: 22293 -author: -type: fixed diff --git a/changelogs/unreleased/52519-runners-link.yml b/changelogs/unreleased/52519-runners-link.yml deleted file mode 100644 index 5d904a8b340..00000000000 --- a/changelogs/unreleased/52519-runners-link.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes stuck block URL linking to documentation instead of settings page -merge_request: 22286 -author: -type: fixed diff --git a/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml b/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml deleted file mode 100644 index 9abad3d2cd8..00000000000 --- a/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow Issue and Merge Request sidebar to be toggled from collapsed state -merge_request: 22353 -author: -type: fixed diff --git a/changelogs/unreleased/52559-applications-api-get-delete.yml b/changelogs/unreleased/52559-applications-api-get-delete.yml new file mode 100644 index 00000000000..19f98a2bb56 --- /dev/null +++ b/changelogs/unreleased/52559-applications-api-get-delete.yml @@ -0,0 +1,5 @@ +--- +title: Add Applications API endpoints for listing and deleting entries. +merge_request: 22296 +author: Jean-Baptiste Vasseur +type: added diff --git a/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml b/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml deleted file mode 100644 index bddc1e16fab..00000000000 --- a/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hide pagination for personal projects on profile overview tab -merge_request: 22321 -author: -type: other diff --git a/changelogs/unreleased/52570-erased-block.yml b/changelogs/unreleased/52570-erased-block.yml deleted file mode 100644 index 6ec295bf81b..00000000000 --- a/changelogs/unreleased/52570-erased-block.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix erased block not being rendered when job was erased -merge_request: 22294 -author: -type: fixed diff --git a/changelogs/unreleased/52608-sidebar.yml b/changelogs/unreleased/52608-sidebar.yml deleted file mode 100644 index 9eca30f7b95..00000000000 --- a/changelogs/unreleased/52608-sidebar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hides sidebar for job page in mobile -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/52614-update-job-started-check.yml b/changelogs/unreleased/52614-update-job-started-check.yml deleted file mode 100644 index 60ea237dbf3..00000000000 --- a/changelogs/unreleased/52614-update-job-started-check.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes triggered/created labeled in job header -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml b/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml deleted file mode 100644 index fdbde709e77..00000000000 --- a/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Load correct stage in the stages dropdown -merge_request: 22317 -author: -type: fixed diff --git a/changelogs/unreleased/52669-fixes-quick-actions-preview.yml b/changelogs/unreleased/52669-fixes-quick-actions-preview.yml deleted file mode 100644 index 51b1425d04d..00000000000 --- a/changelogs/unreleased/52669-fixes-quick-actions-preview.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes close/reopen quick actions preview for issues and merge_requests -merge_request: 22343 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/52692-catch-redirect-loops.yml b/changelogs/unreleased/52692-catch-redirect-loops.yml new file mode 100644 index 00000000000..655124b8fb4 --- /dev/null +++ b/changelogs/unreleased/52692-catch-redirect-loops.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 error when testing webhooks with redirect loops +merge_request: 22447 +author: Heinrich Lee Yu +type: fixed diff --git a/changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml b/changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml new file mode 100644 index 00000000000..4451cdd78b5 --- /dev/null +++ b/changelogs/unreleased/52772-assign-me-quick-action-doesn-t-work-if-there-is-extra-white-space.yml @@ -0,0 +1,5 @@ +--- +title: Resolve assign-me quick action doesn't work if there is extra white space +merge_request: 22402 +author: +type: fixed diff --git a/changelogs/unreleased/52840-fix-runners-details-page.yml b/changelogs/unreleased/52840-fix-runners-details-page.yml new file mode 100644 index 00000000000..b061390fcf0 --- /dev/null +++ b/changelogs/unreleased/52840-fix-runners-details-page.yml @@ -0,0 +1,5 @@ +--- +title: Fix rendering of 'Protected' value on Runner details page +merge_request: 22459 +author: +type: fixed diff --git a/changelogs/unreleased/52886-fix-broken-master.yml b/changelogs/unreleased/52886-fix-broken-master.yml new file mode 100644 index 00000000000..c6488c83e3b --- /dev/null +++ b/changelogs/unreleased/52886-fix-broken-master.yml @@ -0,0 +1,5 @@ +--- +title: Fixes broken test in master +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/53013-duplicate-escape.yml b/changelogs/unreleased/53013-duplicate-escape.yml new file mode 100644 index 00000000000..c5ec2322fb5 --- /dev/null +++ b/changelogs/unreleased/53013-duplicate-escape.yml @@ -0,0 +1,5 @@ +--- +title: Remove duplicate escape in job sidebar +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml b/changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml new file mode 100644 index 00000000000..0377e10fe9e --- /dev/null +++ b/changelogs/unreleased/53023-endless-scroll-loader-is-visible-on-user-profile-overview-page.yml @@ -0,0 +1,4 @@ +title: Adds container to pager to enable scoping +merge_request: 22529 +? author +type: other diff --git a/changelogs/unreleased/5987-project-templates-api.yml b/changelogs/unreleased/5987-project-templates-api.yml deleted file mode 100644 index a627ba9f0de..00000000000 --- a/changelogs/unreleased/5987-project-templates-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow file templates to be requested at the project level -merge_request: 7776 -author: -type: added diff --git a/changelogs/unreleased/6717_extend_reports_for_security_products.yml b/changelogs/unreleased/6717_extend_reports_for_security_products.yml deleted file mode 100644 index 184c3212e54..00000000000 --- a/changelogs/unreleased/6717_extend_reports_for_security_products.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extend reports feature to support Security Products -merge_request: 21892 -author: -type: added diff --git a/changelogs/unreleased/_acet-fix-diff-file-header.yml b/changelogs/unreleased/_acet-fix-diff-file-header.yml deleted file mode 100644 index 2fb3293072d..00000000000 --- a/changelogs/unreleased/_acet-fix-diff-file-header.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix showing diff file header for renamed files -merge_request: 22089 -author: -type: fixed diff --git a/changelogs/unreleased/_acet-fix-placeholder-note.yml b/changelogs/unreleased/_acet-fix-placeholder-note.yml deleted file mode 100644 index 68f3d0085c9..00000000000 --- a/changelogs/unreleased/_acet-fix-placeholder-note.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix rendering placeholder notes -merge_request: 22078 -author: -type: fixed diff --git a/changelogs/unreleased/add-2fa-button.yml b/changelogs/unreleased/add-2fa-button.yml deleted file mode 100644 index 6cb71d52781..00000000000 --- a/changelogs/unreleased/add-2fa-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add button to download 2FA codes -merge_request: -author: Luke Picciau -type: added diff --git a/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml b/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml deleted file mode 100644 index 69432c0d20c..00000000000 --- a/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add markdown header toolbar button to insert table -merge_request: 18480 -author: George Tsiolis -type: added diff --git a/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml b/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml deleted file mode 100644 index 7c707cfe5a0..00000000000 --- a/changelogs/unreleased/add-clipboard-button-to-application-id-and-secret.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add copy to clipboard button for application id and secret -merge_request: 21978 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/add-gl-link-to-download-viewer.yml b/changelogs/unreleased/add-gl-link-to-download-viewer.yml deleted file mode 100644 index ce3d916f045..00000000000 --- a/changelogs/unreleased/add-gl-link-to-download-viewer.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add link component to DownloadViewer component -merge_request: 21987 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/add-gl-link-to-markdown-header.yml b/changelogs/unreleased/add-gl-link-to-markdown-header.yml new file mode 100644 index 00000000000..b8fe66ab52a --- /dev/null +++ b/changelogs/unreleased/add-gl-link-to-markdown-header.yml @@ -0,0 +1,5 @@ +--- +title: Change markdown header tab anchor links to buttons +merge_request: 21988 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml b/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml deleted file mode 100644 index ef87ef541dd..00000000000 --- a/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add link component to UserAvatarLink component -merge_request: 21986 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/add-installation-type-backup-information.yml b/changelogs/unreleased/add-installation-type-backup-information.yml deleted file mode 100644 index 24cf4cc21f4..00000000000 --- a/changelogs/unreleased/add-installation-type-backup-information.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add installation type to backup information file -merge_request: 22150 -author: -type: changed diff --git a/changelogs/unreleased/add-most-stars-for-filter-option.yml b/changelogs/unreleased/add-most-stars-for-filter-option.yml deleted file mode 100644 index be95d6db55f..00000000000 --- a/changelogs/unreleased/add-most-stars-for-filter-option.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allows to sort projects by most stars -merge_request: 21762 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml b/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml new file mode 100644 index 00000000000..87023ede020 --- /dev/null +++ b/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml @@ -0,0 +1,5 @@ +--- +title: Introduce new kubernetes helpers +merge_request: 22525 +author: +type: other diff --git a/changelogs/unreleased/add-role-binding-to-kubeclient.yml b/changelogs/unreleased/add-role-binding-to-kubeclient.yml new file mode 100644 index 00000000000..bc343116eb4 --- /dev/null +++ b/changelogs/unreleased/add-role-binding-to-kubeclient.yml @@ -0,0 +1,5 @@ +--- +title: Allow kubeclient to call RoleBinding methods +merge_request: 22524 +author: +type: other diff --git a/changelogs/unreleased/add_reliable_fetcher.yml b/changelogs/unreleased/add_reliable_fetcher.yml deleted file mode 100644 index c08c755e546..00000000000 --- a/changelogs/unreleased/add_reliable_fetcher.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use Reliable Sidekiq fetch -merge_request: 21715 -author: -type: fixed diff --git a/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml b/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml deleted file mode 100644 index db68ed10a7e..00000000000 --- a/changelogs/unreleased/align-collapsed-sidebar-avatar-container.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Align collapsed sidebar avatar container -merge_request: 22044 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/align-form-labels.yml b/changelogs/unreleased/align-form-labels.yml deleted file mode 100644 index fd781e3b910..00000000000 --- a/changelogs/unreleased/align-form-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Align form labels following Bootstrap 4 docs -merge_request: 21752 -author: -type: fixed diff --git a/changelogs/unreleased/auth.yml b/changelogs/unreleased/auth.yml deleted file mode 100644 index cd4bbf0059e..00000000000 --- a/changelogs/unreleased/auth.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add access control to GitLab pages and make it possible to enable/disable it in project settings -merge_request: 18589 -author: Tuomo Ala-Vannesluoma -type: added diff --git a/changelogs/unreleased/autodevops-timed-incremental-rollout.yml b/changelogs/unreleased/autodevops-timed-incremental-rollout.yml deleted file mode 100644 index 72c7b41177d..00000000000 --- a/changelogs/unreleased/autodevops-timed-incremental-rollout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add timed incremental rollout to Auto DevOps -merge_request: 22023 -author: -type: added diff --git a/changelogs/unreleased/blackst0ne-bump-mermaid.yml b/changelogs/unreleased/blackst0ne-bump-mermaid.yml new file mode 100644 index 00000000000..cb924ac8448 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-bump-mermaid.yml @@ -0,0 +1,5 @@ +--- +title: Bump mermaid to 8.0.0-rc.8 +merge_request: 22509 +author: "@blackst0ne" +type: changed diff --git a/changelogs/unreleased/bvl-remove-sha-from-help.yml b/changelogs/unreleased/bvl-remove-sha-from-help.yml deleted file mode 100644 index 37f797f98f4..00000000000 --- a/changelogs/unreleased/bvl-remove-sha-from-help.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Link to the tag for a version on the help page instead of to the commit -merge_request: 22015 -author: -type: changed diff --git a/changelogs/unreleased/bvl-show-pre-release-sha.yml b/changelogs/unreleased/bvl-show-pre-release-sha.yml deleted file mode 100644 index 524b3c374f7..00000000000 --- a/changelogs/unreleased/bvl-show-pre-release-sha.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show SHA for pre-release versions on the help page -merge_request: 22026 -author: -type: changed diff --git a/changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml b/changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml new file mode 100644 index 00000000000..d92c0e74c07 --- /dev/null +++ b/changelogs/unreleased/ccr-43034_issues_controller_100_queries.yml @@ -0,0 +1,5 @@ +--- +title: Add preload for routes and namespaces for issues controller. +merge_request: 21651 +author: +type: performance diff --git a/changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml b/changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml deleted file mode 100644 index f8fe50a2c48..00000000000 --- a/changelogs/unreleased/ccr-50483_add_filter_for_group_milestones.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Filter group milestones based on user membership. -merge_request: 21660 -author: -type: fixed diff --git a/changelogs/unreleased/ccr-wip_filter.yml b/changelogs/unreleased/ccr-wip_filter.yml deleted file mode 100644 index 07d85ec02ae..00000000000 --- a/changelogs/unreleased/ccr-wip_filter.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added search functionality for Work In Progress (WIP) merge requests -merge_request: 18119 -author: Chantal Rollison -type: added diff --git a/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml b/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml new file mode 100644 index 00000000000..0f46efb693f --- /dev/null +++ b/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml @@ -0,0 +1,5 @@ +--- +title: Change branch font type in tag creation +merge_request: 22454 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/clean-gitlab-git.yml b/changelogs/unreleased/clean-gitlab-git.yml deleted file mode 100644 index d7086b8eea0..00000000000 --- a/changelogs/unreleased/clean-gitlab-git.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove Rugged and shell code from Gitlab::Git -merge_request: 21488 -author: -type: other diff --git a/changelogs/unreleased/clone-nurtch-demo-repo.yml b/changelogs/unreleased/clone-nurtch-demo-repo.yml deleted file mode 100644 index c77138d27f0..00000000000 --- a/changelogs/unreleased/clone-nurtch-demo-repo.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Copy nurtch demo notebooks at Jupyter startup -merge_request: 21698 -author: Amit Rathi -type: added diff --git a/changelogs/unreleased/copy-changes-for-abuse-clarity.yml b/changelogs/unreleased/copy-changes-for-abuse-clarity.yml deleted file mode 100644 index 00d9fec5e42..00000000000 --- a/changelogs/unreleased/copy-changes-for-abuse-clarity.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increased retained event data by extending events pruner timeframe to 2 years -merge_request: 22145 -author: -type: changed diff --git a/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml b/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml deleted file mode 100644 index 723aa3eee8a..00000000000 --- a/changelogs/unreleased/da-synchronize-the-default-branch-when-updating-a-remote-mirror.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Synchronize the default branch when updating a remote mirror -merge_request: 21653 -author: -type: fixed diff --git a/changelogs/unreleased/dm-create-note-return-discussion.yml b/changelogs/unreleased/dm-create-note-return-discussion.yml deleted file mode 100644 index 49ab5c0ca14..00000000000 --- a/changelogs/unreleased/dm-create-note-return-discussion.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increase performance when creating discussions on diff -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml b/changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml deleted file mode 100644 index bfc1ff7b8af..00000000000 --- a/changelogs/unreleased/dm-fix-assign-unassign-quick-actions.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Don't ignore first action when assign and unassign quick actions are used in - the same comment -merge_request: 21749 -author: -type: fixed diff --git a/changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml b/changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml new file mode 100644 index 00000000000..4bece6459a0 --- /dev/null +++ b/changelogs/unreleased/drop-allow_overflow-option-duration_in_numbers.yml @@ -0,0 +1,5 @@ +--- +title: Drop `allow_overflow` option in `TimeHelper.duration_in_numbers` +merge_request: 52284 +author: +type: changed diff --git a/changelogs/unreleased/dz-labels-subscribe-filter.yml b/changelogs/unreleased/dz-labels-subscribe-filter.yml deleted file mode 100644 index 768c20c77c7..00000000000 --- a/changelogs/unreleased/dz-labels-subscribe-filter.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add subscribe filter to group and project labels pages -merge_request: 21965 -author: -type: added diff --git a/changelogs/unreleased/enable-force-write-auth-keys-restore.yml b/changelogs/unreleased/enable-force-write-auth-keys-restore.yml deleted file mode 100644 index f6c83cc7950..00000000000 --- a/changelogs/unreleased/enable-force-write-auth-keys-restore.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable the ability to use the force env for rebuilding authorized_keys during a restore -merge_request: 21896 -author: -type: fixed diff --git a/changelogs/unreleased/fa-handle_invalid_utf8_errors.yml b/changelogs/unreleased/fa-handle_invalid_utf8_errors.yml deleted file mode 100644 index 9cae193d858..00000000000 --- a/changelogs/unreleased/fa-handle_invalid_utf8_errors.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Render 412 when invalid UTF-8 parameters are passed to controller -merge_request: -author: -type: other diff --git a/changelogs/unreleased/fe-ac-review-app-changes-33418.yml b/changelogs/unreleased/fe-ac-review-app-changes-33418.yml new file mode 100644 index 00000000000..e4803683062 --- /dev/null +++ b/changelogs/unreleased/fe-ac-review-app-changes-33418.yml @@ -0,0 +1,5 @@ +--- +title: Adds filtered dropdown with changed files in review +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/feature-add-public-email-to-users-api.yml b/changelogs/unreleased/feature-add-public-email-to-users-api.yml deleted file mode 100644 index 1f5d3fb113d..00000000000 --- a/changelogs/unreleased/feature-add-public-email-to-users-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds the user's public_email attribute to the API -merge_request: 21909 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/feature-flags-mvc.yml b/changelogs/unreleased/feature-flags-mvc.yml deleted file mode 100644 index 6a709f7cf07..00000000000 --- a/changelogs/unreleased/feature-flags-mvc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid close icon leaving the modal header -merge_request: 21904 -author: -type: changed diff --git a/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml b/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml new file mode 100644 index 00000000000..67eb6b78096 --- /dev/null +++ b/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml @@ -0,0 +1,5 @@ +--- +title: Improve validation errors for external CI/CD configuration +merge_request: 22394 +author: +type: added diff --git a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml deleted file mode 100644 index 62676cdad62..00000000000 --- a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add support for pipeline only/except policy for modified paths -merge_request: 21981 -author: -type: added diff --git a/changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml b/changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml deleted file mode 100644 index b8112bd0813..00000000000 --- a/changelogs/unreleased/feature-runner-state-filter-for-admin-view.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add a filter bar to the admin runners view and add a state filter -merge_request: 19625 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml b/changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml deleted file mode 100644 index e7812cd0944..00000000000 --- a/changelogs/unreleased/feature-runner-type-filter-for-admin-view.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add a type filter to the admin runners view -merge_request: 19649 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/feature-set-public-email-through-api.yml b/changelogs/unreleased/feature-set-public-email-through-api.yml deleted file mode 100644 index 22fae71e9d8..00000000000 --- a/changelogs/unreleased/feature-set-public-email-through-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add support for setting the public email through the api -merge_request: 21938 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/features-unauth-access-ssh-keys.yml b/changelogs/unreleased/features-unauth-access-ssh-keys.yml deleted file mode 100644 index bae2bcfaabd..00000000000 --- a/changelogs/unreleased/features-unauth-access-ssh-keys.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable unauthenticated access to public SSH keys via the API -merge_request: 20118 -author: Ronald Claveau -type: changed diff --git a/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml b/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml deleted file mode 100644 index 4d85e1b9af2..00000000000 --- a/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow setting user's organization and location attributes through the API by adding them to the list of allowed parameters -merge_request: 21938 -author: Alexis Reigel -type: fixed diff --git a/changelogs/unreleased/fix-base64-encoded-file-uploads.yml b/changelogs/unreleased/fix-base64-encoded-file-uploads.yml new file mode 100644 index 00000000000..3dde2419fa1 --- /dev/null +++ b/changelogs/unreleased/fix-base64-encoded-file-uploads.yml @@ -0,0 +1,5 @@ +--- +title: Remove base64 encoding from files that contain plain text +merge_request: 22425 +author: +type: fixed diff --git a/changelogs/unreleased/fix-chat-notification-service-for-ee.yml b/changelogs/unreleased/fix-chat-notification-service-for-ee.yml deleted file mode 100644 index b69d08b95db..00000000000 --- a/changelogs/unreleased/fix-chat-notification-service-for-ee.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix activity titles for MRs in chat notification services -merge_request: 21834 -author: -type: fixed diff --git a/changelogs/unreleased/fix-committer-typo.yml b/changelogs/unreleased/fix-committer-typo.yml deleted file mode 100644 index 6033912b6c0..00000000000 --- a/changelogs/unreleased/fix-committer-typo.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix committer typo -merge_request: 21899 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/fix-events-finder-incomplete.yml b/changelogs/unreleased/fix-events-finder-incomplete.yml deleted file mode 100644 index f3a4e421d33..00000000000 --- a/changelogs/unreleased/fix-events-finder-incomplete.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Redact confidential events in the API -merge_request: -author: -type: security diff --git a/changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml b/changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml deleted file mode 100644 index 4ac192cd056..00000000000 --- a/changelogs/unreleased/fix-help-text-font-color-in-merge-request-creation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix wrong text color of help text in merge request creation -merge_request: -author: Gerard Montemayor -type: fixed diff --git a/changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml b/changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml deleted file mode 100644 index 38b2486a475..00000000000 --- a/changelogs/unreleased/fix-leading-slash-in-redirects-plus-rubocop.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix leading slash in redirects and add rubocop cop -merge_request: 21828 -author: Sanad Liaquat -type: fixed diff --git a/changelogs/unreleased/fix-mention-in-edit-mr.yml b/changelogs/unreleased/fix-mention-in-edit-mr.yml deleted file mode 100644 index a82b0ba9748..00000000000 --- a/changelogs/unreleased/fix-mention-in-edit-mr.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed mention autocomplete in edit merge request. -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/force-post-migration-dir-schema-load.yml b/changelogs/unreleased/force-post-migration-dir-schema-load.yml deleted file mode 100644 index 19119515929..00000000000 --- a/changelogs/unreleased/force-post-migration-dir-schema-load.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Ensure the schema is loaded with post_migrations included -merge_request: 21689 -author: -type: changed diff --git a/changelogs/unreleased/frozen-string-app-controller-more.yml b/changelogs/unreleased/frozen-string-app-controller-more.yml deleted file mode 100644 index ea2c81e7afc..00000000000 --- a/changelogs/unreleased/frozen-string-app-controller-more.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable more frozen string in app/controllers/ -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-app-controller.yml b/changelogs/unreleased/frozen-string-app-controller.yml deleted file mode 100644 index 95fea4eae63..00000000000 --- a/changelogs/unreleased/frozen-string-app-controller.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable frozen string in app/controllers/**/*.rb -merge_request: gfyoung -author: -type: performance diff --git a/changelogs/unreleased/frozen-string-app-controllers-much-more.yml b/changelogs/unreleased/frozen-string-app-controllers-much-more.yml deleted file mode 100644 index 6e32d5ba039..00000000000 --- a/changelogs/unreleased/frozen-string-app-controllers-much-more.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable even more frozen string in app/controllers -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-app-enforce.yml b/changelogs/unreleased/frozen-string-app-enforce.yml deleted file mode 100644 index 44686557c45..00000000000 --- a/changelogs/unreleased/frozen-string-app-enforce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Check frozen string in style builds -merge_request: -author: gfyoung -type: other diff --git a/changelogs/unreleased/frozen-string-app-finders-graphql.yml b/changelogs/unreleased/frozen-string-app-finders-graphql.yml deleted file mode 100644 index ea8c83f64d9..00000000000 --- a/changelogs/unreleased/frozen-string-app-finders-graphql.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable frozen string in app/graphql + app/finders -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-docs.yml b/changelogs/unreleased/frozen-string-docs.yml deleted file mode 100644 index 2eb8a446ee8..00000000000 --- a/changelogs/unreleased/frozen-string-docs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update docs regarding frozen string -merge_request: -author: gfyoung -type: other diff --git a/changelogs/unreleased/frozen-string-enable-app-helpers.yml b/changelogs/unreleased/frozen-string-enable-app-helpers.yml deleted file mode 100644 index 7f6805ccb5a..00000000000 --- a/changelogs/unreleased/frozen-string-enable-app-helpers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable frozen string for app/helpers/**/*.rb -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/frozen-string-enable-vestigial.yml b/changelogs/unreleased/frozen-string-enable-lib-gitlab.yml index 55313ff0fcc..d64278eb626 100644 --- a/changelogs/unreleased/frozen-string-enable-vestigial.yml +++ b/changelogs/unreleased/frozen-string-enable-lib-gitlab.yml @@ -1,5 +1,5 @@ --- -title: Enable frozen string in vestigial files +title: Enable frozen string for lib/gitlab/*.rb merge_request: author: gfyoung type: performance diff --git a/changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml b/changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml new file mode 100644 index 00000000000..2ba52e07324 --- /dev/null +++ b/changelogs/unreleased/gt-add-transparent-background-to-markdown-header-tabs.yml @@ -0,0 +1,5 @@ +--- +title: Add transparent background to markdown header tabs +merge_request: 22565 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml b/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml deleted file mode 100644 index c2e828eb697..00000000000 --- a/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove duplicate button from the markdown header toolbar -merge_request: 22192 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml b/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml new file mode 100644 index 00000000000..d2a65d48d8d --- /dev/null +++ b/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml @@ -0,0 +1,5 @@ +--- +title: Remove empty spec describe blocks +merge_request: 22451 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml b/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml deleted file mode 100644 index 7205c138777..00000000000 --- a/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update copy to clipboard button data for application secret -merge_request: 22268 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/ide-fetch-templates-pages.yml b/changelogs/unreleased/ide-fetch-templates-pages.yml deleted file mode 100644 index d4703e530f2..00000000000 --- a/changelogs/unreleased/ide-fetch-templates-pages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed file templates not fully being fetched in Web IDE -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/improve-empty-project-placeholder.yml b/changelogs/unreleased/improve-empty-project-placeholder.yml deleted file mode 100644 index 11fe21e7710..00000000000 --- a/changelogs/unreleased/improve-empty-project-placeholder.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve empty project placeholder for non-members and members without write access -merge_request: 21977 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/issue_50528.yml b/changelogs/unreleased/issue_50528.yml deleted file mode 100644 index 82d33bfa255..00000000000 --- a/changelogs/unreleased/issue_50528.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Log project services errors when executing async -merge_request: -author: -type: other diff --git a/changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml b/changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml new file mode 100644 index 00000000000..344997add74 --- /dev/null +++ b/changelogs/unreleased/jivl-fix-bar-char-transient-spec-failure.yml @@ -0,0 +1,5 @@ +--- +title: Fix transient spec error in the bar_chart component +merge_request: 22495 +author: +type: fixed diff --git a/changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml b/changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml deleted file mode 100644 index c21301bf6b3..00000000000 --- a/changelogs/unreleased/jivl-fix-monitoring-dashboard-resizing-navbar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix resizing of monitoring dashboard -merge_request: 21730 -author: -type: fixed diff --git a/changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml b/changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml deleted file mode 100644 index 9c23244d48b..00000000000 --- a/changelogs/unreleased/leipert-fix-mr-widget-header-margins.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix merge request header margins -merge_request: 21878 -author: -type: other diff --git a/changelogs/unreleased/lfs-project-attribute-alias.yml b/changelogs/unreleased/lfs-project-attribute-alias.yml new file mode 100644 index 00000000000..883869f651a --- /dev/null +++ b/changelogs/unreleased/lfs-project-attribute-alias.yml @@ -0,0 +1,5 @@ +--- +title: Resolve LFS not correctly showing enabled +merge_request: 22501 +author: +type: fixed diff --git a/changelogs/unreleased/lib-api-frozen-string-enable.yml b/changelogs/unreleased/lib-api-frozen-string-enable.yml deleted file mode 100644 index eb59f0bc2d1..00000000000 --- a/changelogs/unreleased/lib-api-frozen-string-enable.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable frozen string in lib/api and lib/backup -merge_request: -author: gfyoung -type: performance diff --git a/changelogs/unreleased/load_project_features.yml b/changelogs/unreleased/load_project_features.yml deleted file mode 100644 index 0cf7f0e3a74..00000000000 --- a/changelogs/unreleased/load_project_features.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Mitigate N+1 queries when parsing commit references in comments. -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/lock-unlock-quick-actions.yml b/changelogs/unreleased/lock-unlock-quick-actions.yml deleted file mode 100644 index 9322d60ba52..00000000000 --- a/changelogs/unreleased/lock-unlock-quick-actions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add /lock and /unlock quick actions -merge_request: 15197 -author: Mehdi Lahmam (@mehlah) -type: added diff --git a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml deleted file mode 100644 index d634d15946e..00000000000 --- a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively -merge_request: 22070 -author: -type: performance diff --git a/changelogs/unreleased/mk-asymmetric-exists-cache.yml b/changelogs/unreleased/mk-asymmetric-exists-cache.yml deleted file mode 100644 index b6eec7d1fc6..00000000000 --- a/changelogs/unreleased/mk-asymmetric-exists-cache.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 'Resolve "Geo: Does not mark repositories as missing on primary due to stale - cache"' -merge_request: 21789 -author: -type: fixed diff --git a/changelogs/unreleased/more-table-widths.yml b/changelogs/unreleased/more-table-widths.yml deleted file mode 100644 index 61956e7068e..00000000000 --- a/changelogs/unreleased/more-table-widths.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds an extra width to the responsive tables -merge_request: 21928 -author: -type: other diff --git a/changelogs/unreleased/mr-creation-source-project-filtering.yml b/changelogs/unreleased/mr-creation-source-project-filtering.yml new file mode 100644 index 00000000000..818101a6f1b --- /dev/null +++ b/changelogs/unreleased/mr-creation-source-project-filtering.yml @@ -0,0 +1,5 @@ +--- +title: Fixed source project not filtering in merge request creation compare form +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/mr-file-list.yml b/changelogs/unreleased/mr-file-list.yml new file mode 100644 index 00000000000..0a2a5e0c1cc --- /dev/null +++ b/changelogs/unreleased/mr-file-list.yml @@ -0,0 +1,5 @@ +--- +title: Switch between tree list & file list in diffs file browser +merge_request: 22191 +author: +type: added diff --git a/changelogs/unreleased/mr-file-tree-data.yml b/changelogs/unreleased/mr-file-tree-data.yml deleted file mode 100644 index a82087ea148..00000000000 --- a/changelogs/unreleased/mr-file-tree-data.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added tree of changed files to merge request diffs -merge_request: 21833 -author: -type: added diff --git a/changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml b/changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml new file mode 100644 index 00000000000..b61f47724fc --- /dev/null +++ b/changelogs/unreleased/mr-file-tree-inline-fluid-width-fix.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request fill tree toggling not respecting fluid width preference +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/mr-legacy-diff-notes.yml b/changelogs/unreleased/mr-legacy-diff-notes.yml deleted file mode 100644 index bca5ac8297f..00000000000 --- a/changelogs/unreleased/mr-legacy-diff-notes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Correctly show legacy diff notes in the merge request changes tab -merge_request: 21652 -author: -type: fixed diff --git a/changelogs/unreleased/mr-widget-discussion-state-fix.yml b/changelogs/unreleased/mr-widget-discussion-state-fix.yml deleted file mode 100644 index 562d78a7aa7..00000000000 --- a/changelogs/unreleased/mr-widget-discussion-state-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed merge request widget discussion state not updating after resolving discussions -merge_request: 21705 -author: -type: fixed diff --git a/changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml b/changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml deleted file mode 100644 index 03189d934a7..00000000000 --- a/changelogs/unreleased/osw-clean-up-phase-for-diff-files-removal.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add clean-up phase for ScheduleDiffFilesDeletion migration -merge_request: 21734 -author: -type: other diff --git a/changelogs/unreleased/osw-configurable-single-diff-file-limit.yml b/changelogs/unreleased/osw-configurable-single-diff-file-limit.yml deleted file mode 100644 index 55a4b305885..00000000000 --- a/changelogs/unreleased/osw-configurable-single-diff-file-limit.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make single diff patch limit configurable -merge_request: 21886 -author: -type: added diff --git a/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml b/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml deleted file mode 100644 index 5dde22d3158..00000000000 --- a/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix LFS uploaded images not being rendered -merge_request: 22092 -author: -type: fixed diff --git a/changelogs/unreleased/osw-gitaly-diff-stats-client.yml b/changelogs/unreleased/osw-gitaly-diff-stats-client.yml deleted file mode 100644 index 9f280162409..00000000000 --- a/changelogs/unreleased/osw-gitaly-diff-stats-client.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Gitaly diff stats RPC client -merge_request: 21732 -author: -type: changed diff --git a/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml b/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml deleted file mode 100644 index d4e2641daf5..00000000000 --- a/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes expensive dead code on main MR page request -merge_request: 22153 -author: -type: performance diff --git a/changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml b/changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml deleted file mode 100644 index c71d4e58d6f..00000000000 --- a/changelogs/unreleased/osw-use-diff-stats-rpc-on-comparison-views.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use stats RPC when comparing diffs -merge_request: 21778 -author: -type: fixed diff --git a/changelogs/unreleased/pedroms-master-patch-57786.yml b/changelogs/unreleased/pedroms-master-patch-57786.yml deleted file mode 100644 index cdf179046aa..00000000000 --- a/changelogs/unreleased/pedroms-master-patch-57786.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix blue, orange, and red color inconsistencies -merge_request: 21972 -author: -type: other diff --git a/changelogs/unreleased/pipeline-event-variables.yml b/changelogs/unreleased/pipeline-event-variables.yml deleted file mode 100644 index 90fd964efd5..00000000000 --- a/changelogs/unreleased/pipeline-event-variables.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: pipeline webhook event now contain pipeline variables -merge_request: 18171 -author: Pierre Tardy -type: added diff --git a/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml b/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml deleted file mode 100644 index 3a399bb836e..00000000000 --- a/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 'Rails5: fix artifacts controller download spec Rails5 has params[:file_type] - as '''' if file_type is included as nil in the request' -merge_request: 22123 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/rails5-fix-delete-blob.yml b/changelogs/unreleased/rails5-fix-delete-blob.yml new file mode 100644 index 00000000000..ee8304fbdf4 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-delete-blob.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails5: fix delete blob' +merge_request: 22456 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/rails5-fix-deployment-spec.yml b/changelogs/unreleased/rails5-fix-deployment-spec.yml new file mode 100644 index 00000000000..9e53c617a54 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-deployment-spec.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails5: fix deployment model spec' +merge_request: 22428 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/rails5-fix-issue-move-service.yml b/changelogs/unreleased/rails5-fix-issue-move-service.yml deleted file mode 100644 index 1e71544e587..00000000000 --- a/changelogs/unreleased/rails5-fix-issue-move-service.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 'Rails 5: fix issue move service In rails 5, the attributes method for an enum - returns the name instead of the database integer.' -merge_request: 21616 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/remove-sidekiq.yml b/changelogs/unreleased/remove-sidekiq.yml deleted file mode 100644 index c7bef974b89..00000000000 --- a/changelogs/unreleased/remove-sidekiq.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove sidekiq info from performance bar -merge_request: -author: -type: removed diff --git a/changelogs/unreleased/rename-local-variable.yml b/changelogs/unreleased/rename-local-variable.yml deleted file mode 100644 index 70281dfef08..00000000000 --- a/changelogs/unreleased/rename-local-variable.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Rename block scope local variable in table pagination spec -merge_request: 21969 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/rename-scheduled-label-badges.yml b/changelogs/unreleased/rename-scheduled-label-badges.yml new file mode 100644 index 00000000000..f9ee17a98a4 --- /dev/null +++ b/changelogs/unreleased/rename-scheduled-label-badges.yml @@ -0,0 +1,5 @@ +--- +title: Rename "scheduled" label/badge of delayed jobs to "delayed" +merge_request: 22245 +author: +type: changed diff --git a/changelogs/unreleased/rename-squash-before-merge-vue-component.yml b/changelogs/unreleased/rename-squash-before-merge-vue-component.yml deleted file mode 100644 index 66eeeb225dd..00000000000 --- a/changelogs/unreleased/rename-squash-before-merge-vue-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Rename squash before merge vue component -merge_request: 21851 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/rouge-3-3-0.yml b/changelogs/unreleased/rouge-3-3-0.yml deleted file mode 100644 index 7cf6c75920a..00000000000 --- a/changelogs/unreleased/rouge-3-3-0.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Update to Rouge 3.3.0 including frozen string literals for improved memory - usage -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/scheduled-manual-jobs.yml b/changelogs/unreleased/scheduled-manual-jobs.yml deleted file mode 100644 index fa3f5a6f461..00000000000 --- a/changelogs/unreleased/scheduled-manual-jobs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow pipelines to schedule delayed job runs -merge_request: 21767 -author: -type: added diff --git a/changelogs/unreleased/security-2697-code-highlight-timeout.yml b/changelogs/unreleased/security-2697-code-highlight-timeout.yml deleted file mode 100644 index 66ad9ff822b..00000000000 --- a/changelogs/unreleased/security-2697-code-highlight-timeout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Set timeout for syntax highlighting -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-acet-issue-details.yml b/changelogs/unreleased/security-acet-issue-details.yml deleted file mode 100644 index 64147a9d6e8..00000000000 --- a/changelogs/unreleased/security-acet-issue-details.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Sanitize JSON data properly to fix XSS on Issue details page -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml b/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml deleted file mode 100644 index e0231b7962f..00000000000 --- a/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Markdown API no longer displays confidential title references unless authorized -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml b/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml deleted file mode 100644 index 589d16c0c35..00000000000 --- a/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Properly filter private references from system notes -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml b/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml deleted file mode 100644 index 7520aa624c7..00000000000 --- a/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix stored XSS in merge requests from imported repository -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-osw-user-info-leak-discussions.yml b/changelogs/unreleased/security-osw-user-info-leak-discussions.yml deleted file mode 100644 index 5acbb80fc3d..00000000000 --- a/changelogs/unreleased/security-osw-user-info-leak-discussions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Filter user sensitive data from discussions JSON -merge_request: 2536 -author: -type: security diff --git a/changelogs/unreleased/security-package-json-xss.yml b/changelogs/unreleased/security-package-json-xss.yml deleted file mode 100644 index 6ab4854e44f..00000000000 --- a/changelogs/unreleased/security-package-json-xss.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix xss vulnerability sourced from package.json -merge_request: -author: -type: security diff --git a/changelogs/unreleased/sh-add-audit-logging-json-ce.yml b/changelogs/unreleased/sh-add-audit-logging-json-ce.yml new file mode 100644 index 00000000000..3c0a27da269 --- /dev/null +++ b/changelogs/unreleased/sh-add-audit-logging-json-ce.yml @@ -0,0 +1,5 @@ +--- +title: Add support for JSON logging for audit events +merge_request: 22471 +author: +type: added diff --git a/changelogs/unreleased/sh-allow-key-id-in-params.yml b/changelogs/unreleased/sh-allow-key-id-in-params.yml deleted file mode 100644 index 2be1cfb0ed3..00000000000 --- a/changelogs/unreleased/sh-allow-key-id-in-params.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Filter any parameters ending with "key" in logs -merge_request: 21688 -author: -type: changed diff --git a/changelogs/unreleased/sh-associate-rakefile-ruby.yml b/changelogs/unreleased/sh-associate-rakefile-ruby.yml new file mode 100644 index 00000000000..3e3fcb8d860 --- /dev/null +++ b/changelogs/unreleased/sh-associate-rakefile-ruby.yml @@ -0,0 +1,5 @@ +--- +title: Associate Rakefile with Ruby icon in diffs +merge_request: +author: +type: other diff --git a/changelogs/unreleased/sh-delete-tags-outside-transaction.yml b/changelogs/unreleased/sh-delete-tags-outside-transaction.yml deleted file mode 100644 index 974da70251e..00000000000 --- a/changelogs/unreleased/sh-delete-tags-outside-transaction.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Delete container repository tags outside of transaction -merge_request: 21679 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-issue-52009.yml b/changelogs/unreleased/sh-fix-issue-52009.yml deleted file mode 100644 index fc22a58a66a..00000000000 --- a/changelogs/unreleased/sh-fix-issue-52009.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent Error 500s with invalid relative links -merge_request: 22001 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml b/changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml deleted file mode 100644 index 994765bc1fd..00000000000 --- a/changelogs/unreleased/sh-fix-multipart-upload-signed-urls.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix object storage uploads not working with AWS v2 -merge_request: 21731 -author: -type: fixed diff --git a/changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml b/changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml deleted file mode 100644 index 7233f6f3d7b..00000000000 --- a/changelogs/unreleased/sh-guard-against-ldap-login-csrf-fail.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Guard against a login attempt with invalid CSRF token -merge_request: 21934 -author: -type: fixed diff --git a/changelogs/unreleased/sh-improve-container-tags-update-username.yml b/changelogs/unreleased/sh-improve-container-tags-update-username.yml deleted file mode 100644 index 1a21ae84314..00000000000 --- a/changelogs/unreleased/sh-improve-container-tags-update-username.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve logging when username update fails due to registry tags -merge_request: 22038 -author: -type: other diff --git a/changelogs/unreleased/sh-pages-eof-error.yml b/changelogs/unreleased/sh-pages-eof-error.yml new file mode 100644 index 00000000000..497a74c1458 --- /dev/null +++ b/changelogs/unreleased/sh-pages-eof-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix EOF detection with CI artifacts metadata +merge_request: 22479 +author: +type: fixed diff --git a/changelogs/unreleased/sh-strip-github-pat-whitespace.yml b/changelogs/unreleased/sh-strip-github-pat-whitespace.yml new file mode 100644 index 00000000000..ea26f57e8f0 --- /dev/null +++ b/changelogs/unreleased/sh-strip-github-pat-whitespace.yml @@ -0,0 +1,5 @@ +--- +title: Strip whitespace around GitHub personal access tokens +merge_request: 22432 +author: +type: fixed diff --git a/changelogs/unreleased/sh-support-adding-confirmed-emails.yml b/changelogs/unreleased/sh-support-adding-confirmed-emails.yml deleted file mode 100644 index 1b64a1c62dc..00000000000 --- a/changelogs/unreleased/sh-support-adding-confirmed-emails.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add ability to skip user email confirmation with API -merge_request: 21630 -author: -type: added diff --git a/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml b/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml deleted file mode 100644 index 2a27e37c053..00000000000 --- a/changelogs/unreleased/sh-upgrade-katex-0-9-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump KaTeX version to 0.9.0 -merge_request: 21625 -author: -type: fixed diff --git a/changelogs/unreleased/support-license-management-and-performance.yml b/changelogs/unreleased/support-license-management-and-performance.yml new file mode 100644 index 00000000000..2e65dba5e76 --- /dev/null +++ b/changelogs/unreleased/support-license-management-and-performance.yml @@ -0,0 +1,5 @@ +--- +title: Support licenses and performance +merge_request: +author: +type: added diff --git a/changelogs/unreleased/toon-copy-meta-data-fix.yml b/changelogs/unreleased/toon-copy-meta-data-fix.yml deleted file mode 100644 index f2f8a1a82a4..00000000000 --- a/changelogs/unreleased/toon-copy-meta-data-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow /copy_metadata for new issues and MRs -merge_request: 21953 -author: -type: changed diff --git a/changelogs/unreleased/update-operations-metrics-empty-state.yml b/changelogs/unreleased/update-operations-metrics-empty-state.yml deleted file mode 100644 index 51f3935b769..00000000000 --- a/changelogs/unreleased/update-operations-metrics-empty-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update operations metrics empty state -merge_request: 21974 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/update-readme-ruby-version.yml b/changelogs/unreleased/update-readme-ruby-version.yml new file mode 100644 index 00000000000..524b8112d4f --- /dev/null +++ b/changelogs/unreleased/update-readme-ruby-version.yml @@ -0,0 +1,5 @@ +--- +title: Update Ruby version in README +merge_request: 22466 +author: J.D. Bean +type: changed diff --git a/changelogs/unreleased/update-runner-chart-to-0-1-34.yml b/changelogs/unreleased/update-runner-chart-to-0-1-34.yml new file mode 100644 index 00000000000..ebd34bb86b8 --- /dev/null +++ b/changelogs/unreleased/update-runner-chart-to-0-1-34.yml @@ -0,0 +1,5 @@ +--- +title: Update used version of Runner Helm Chart to 0.1.34 +merge_request: 22274 +author: +type: other diff --git a/changelogs/unreleased/update-runner-chart-to-0-1-35.yml b/changelogs/unreleased/update-runner-chart-to-0-1-35.yml new file mode 100644 index 00000000000..3b8029c8d96 --- /dev/null +++ b/changelogs/unreleased/update-runner-chart-to-0-1-35.yml @@ -0,0 +1,5 @@ +--- +title: Update used version of Runner Helm Chart to 0.1.35 +merge_request: 22541 +author: +type: other diff --git a/changelogs/unreleased/use-raw-file-format.yml b/changelogs/unreleased/use-raw-file-format.yml new file mode 100644 index 00000000000..d86db51fea4 --- /dev/null +++ b/changelogs/unreleased/use-raw-file-format.yml @@ -0,0 +1,5 @@ +--- +title: Make all legacy security reports to use raw format +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml b/changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml deleted file mode 100644 index 11ebf567e9d..00000000000 --- a/changelogs/unreleased/vendor-auto-devops-gitlab-ci-fix-503-on-deploy.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Vendor Auto-DevOps.gitlab-ci.yml to fix bug where the deploy job does not wait - for Deployment to complete -merge_request: 21713 -author: -type: fixed diff --git a/changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml b/changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml deleted file mode 100644 index 98d0e24c00a..00000000000 --- a/changelogs/unreleased/vendor-gitlab-ci-auto-devops-yml.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make AutoDevOps work behind proxy -merge_request: 21775 -author: Sergej - @kinolaev -type: other diff --git a/changelogs/unreleased/winh-highlight-current-user.yml b/changelogs/unreleased/winh-highlight-current-user.yml deleted file mode 100644 index 125a5c08c4e..00000000000 --- a/changelogs/unreleased/winh-highlight-current-user.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Highlight current user in comments -merge_request: 21406 -author: -type: changed diff --git a/changelogs/unreleased/winh-page-title-margin.yml b/changelogs/unreleased/winh-page-title-margin.yml deleted file mode 100644 index f21f07d396b..00000000000 --- a/changelogs/unreleased/winh-page-title-margin.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change vertical margin of page titles to 16px -merge_request: 21888 -author: -type: changed diff --git a/changelogs/unreleased/zj-render-log-artifacts.yml b/changelogs/unreleased/zj-render-log-artifacts.yml deleted file mode 100644 index 82f29b62300..00000000000 --- a/changelogs/unreleased/zj-render-log-artifacts.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Render log artifact files in GitLab -merge_request: -author: -type: added diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 749cdd0f869..a4db125f831 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -772,9 +772,6 @@ test: default: path: tmp/tests/repositories/ gitaly_address: unix:tmp/tests/gitaly/gitaly.socket - broken: - path: tmp/tests/non-existent-repositories - gitaly_address: unix:tmp/tests/gitaly/gitaly.socket gitaly: client_path: tmp/tests/gitaly diff --git a/config/webpack.config.js b/config/webpack.config.js index 583f05f2fb7..9ecae9790fd 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -8,7 +8,7 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_PATH = path.resolve(__dirname, '..'); -const CACHE_PATH = path.join(ROOT_PATH, 'tmp/cache'); +const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache'); const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile index ad5f1c1e0f3..38ccbd94edb 100644 --- a/danger/database/Dangerfile +++ b/danger/database/Dangerfile @@ -39,8 +39,6 @@ def database_paths_requiring_review(files) to_review end -all_files = git.added_files + git.modified_files - non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/schema\.rb}).empty? geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).empty? @@ -55,7 +53,7 @@ if geo_migration_created && !geo_db_schema_updated warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'Geo migrations', schema: gitlab.html_link("ee/db/geo/schema.rb")) end -db_paths_to_review = database_paths_requiring_review(all_files) +db_paths_to_review = database_paths_requiring_review(helper.all_changed_files) unless db_paths_to_review.empty? message 'This merge request adds or changes files that require a ' \ diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile index d65bec123a9..fe819ee250c 100644 --- a/danger/documentation/Dangerfile +++ b/danger/documentation/Dangerfile @@ -11,9 +11,7 @@ def docs_paths_requiring_review(files) end end -all_files = git.added_files + git.modified_files - -docs_paths_to_review = docs_paths_requiring_review(all_files) +docs_paths_to_review = docs_paths_requiring_review(helper.all_changed_files) unless docs_paths_to_review.empty? message 'This merge request adds or changes files that require a ' \ diff --git a/danger/eslint/Dangerfile b/danger/eslint/Dangerfile index f78488cfd0a..4916cacfd7e 100644 --- a/danger/eslint/Dangerfile +++ b/danger/eslint/Dangerfile @@ -7,7 +7,7 @@ def get_eslint_files(files) end end -eslint_candidates = get_eslint_files(git.added_files + git.modified_files) +eslint_candidates = get_eslint_files(helper.all_changed_files) return if eslint_candidates.empty? diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb new file mode 100644 index 00000000000..f4eb9119266 --- /dev/null +++ b/danger/plugins/helper.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Danger + # Common helper functions for our danger scripts + # If we find ourselves repeating code in our danger files, we might as well put them in here. + class Helper < Plugin + # Returns a list of all files that have been added, modified or renamed. + # `git.modified_files` might contain paths that already have been renamed, + # so we need to remove them from the list. + # + # Considering these changes: + # + # - A new_file.rb + # - D deleted_file.rb + # - M modified_file.rb + # - R renamed_file_before.rb -> renamed_file_after.rb + # + # it will return + # ``` + # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ] + # ``` + # + # @return [Array<String>] + def all_changed_files + Set.new + .merge(git.added_files.to_a) + .merge(git.modified_files.to_a) + .merge(git.renamed_files.map { |x| x[:after] }) + .subtract(git.renamed_files.map { |x| x[:before] }) + .to_a + .sort + end + end +end diff --git a/danger/prettier/Dangerfile b/danger/prettier/Dangerfile index 86f9f6af475..37c4b78a213 100644 --- a/danger/prettier/Dangerfile +++ b/danger/prettier/Dangerfile @@ -6,7 +6,7 @@ def get_prettier_files(files) end end -prettier_candidates = get_prettier_files(git.added_files + git.modified_files) +prettier_candidates = get_prettier_files(helper.all_changed_files) return if prettier_candidates.empty? diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index 51e69879c79..089de211380 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -7,8 +7,8 @@ Sidekiq::Testing.inline! do 'https://gitlab.com/gitlab-org/gitlab-shell.git', 'https://gitlab.com/gnuwget/wget2.git', 'https://gitlab.com/Commit451/LabCoat.git', - 'https://github.com/documentcloud/underscore.git', - 'https://github.com/twitter/flight.git', + 'https://github.com/jashkenas/underscore.git', + 'https://github.com/flightjs/flight.git', 'https://github.com/twitter/typeahead.js.git', 'https://github.com/h5bp/html5-boilerplate.git', 'https://github.com/google/material-design-lite.git', @@ -20,18 +20,18 @@ Sidekiq::Testing.inline! do 'https://github.com/airbnb/javascript.git', 'https://github.com/tessalt/echo-chamber-js.git', 'https://github.com/atom/atom.git', - 'https://github.com/mattermost/platform.git', + 'https://github.com/mattermost/mattermost-server.git', 'https://github.com/purifycss/purifycss.git', 'https://github.com/facebook/nuclide.git', 'https://github.com/wbkd/awesome-d3.git', 'https://github.com/kilimchoi/engineering-blogs.git', 'https://github.com/gilbarbara/logos.git', - 'https://github.com/gaearon/redux.git', + 'https://github.com/reduxjs/redux.git', 'https://github.com/awslabs/s2n.git', 'https://github.com/arkency/reactjs_koans.git', 'https://github.com/twbs/bootstrap.git', 'https://github.com/chjj/ttystudio.git', - 'https://github.com/DrBoolean/mostly-adequate-guide.git', + 'https://github.com/MostlyAdequate/mostly-adequate-guide.git', 'https://github.com/octocat/Spoon-Knife.git', 'https://github.com/opencontainers/runc.git', 'https://github.com/googlesamples/android-topeka.git' diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 5535c4a14e5..5af77c49913 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -1,7 +1,7 @@ require './spec/support/sidekiq' class Gitlab::Seeder::Pipelines - STAGES = %w[build test deploy notify] + STAGES = %w[build test security deploy notify] BUILDS = [ # build stage { name: 'build:linux', stage: 'build', status: :success, @@ -31,6 +31,16 @@ class Gitlab::Seeder::Pipelines { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true, queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + # security stage + { name: 'dast', stage: 'security', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'sast', stage: 'security', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'dependency_scanning', stage: 'security', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'container_scanning', stage: 'security', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + # deploy stage { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, options: { environment: { action: 'start', on_stop: 'stop staging' } }, @@ -108,6 +118,11 @@ class Gitlab::Seeder::Pipelines setup_artifacts(build) setup_test_reports(build) + if build.ref == build.project.default_branch + setup_security_reports_file(build) + else + setup_security_reports_legacy_archive(build) + end setup_build_log(build) build.project.environments. @@ -143,6 +158,55 @@ class Gitlab::Seeder::Pipelines end end + def setup_security_reports_file(build) + return unless build.stage == "security" + + # we have two sources: master and feature-branch + branch_name = build.ref == build.project.default_branch ? + 'master' : 'feature-branch' + + artifacts_cache_file(security_reports_path(branch_name, build.name)) do |file| + build.job_artifacts.build( + project: build.project, + file_type: build.name, + file_format: :raw, + file: file) + end + end + + def setup_security_reports_legacy_archive(build) + return unless build.stage == "security" + + # we have two sources: master and feature-branch + branch_name = build.ref == build.project.default_branch ? + 'master' : 'feature-branch' + + artifacts_cache_file(security_reports_archive_path(branch_name)) do |file| + build.job_artifacts.build( + project: build.project, + file_type: :archive, + file_format: :zip, + file: file) + end + + # assign dummy metadata + artifacts_cache_file(artifacts_metadata_path) do |file| + build.job_artifacts.build( + project: build.project, + file_type: :metadata, + file_format: :gzip, + file: file) + end + + build.options = { + artifacts: { + paths: [ + Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(build.name.to_sym) + ] + } + } + end + def setup_build_log(build) if %w(running success failed).include?(build.status) build.trace.set(FFaker::Lorem.paragraphs(6).join("\n\n")) @@ -190,6 +254,15 @@ class Gitlab::Seeder::Pipelines Rails.root + 'spec/fixtures/junit/junit.xml.gz' end + def security_reports_archive_path(branch) + Rails.root.join('spec', 'fixtures', 'security-reports', branch + '.zip') + end + + def security_reports_path(branch, name) + file_name = Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(name.to_sym) + Rails.root.join('spec', 'fixtures', 'security-reports', branch, file_name) + end + def artifacts_cache_file(file_path) file = Tempfile.new("artifacts") file.close diff --git a/db/migrate/20180925200829_create_user_preferences.rb b/db/migrate/20180925200829_create_user_preferences.rb new file mode 100644 index 00000000000..755cabdabde --- /dev/null +++ b/db/migrate/20180925200829_create_user_preferences.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class CreateUserPreferences < ActiveRecord::Migration + DOWNTIME = false + + class UserPreference < ActiveRecord::Base + self.table_name = 'user_preferences' + + NOTES_FILTERS = { all_notes: 0, comments: 1 }.freeze + end + + def change + create_table :user_preferences do |t| + t.references :user, + null: false, + index: { unique: true }, + foreign_key: { on_delete: :cascade } + + t.integer :issue_notes_filter, + default: UserPreference::NOTES_FILTERS[:all_notes], + null: false, limit: 2 + + t.integer :merge_request_notes_filter, + default: UserPreference::NOTES_FILTERS[:all_notes], + null: false, + limit: 2 + + t.timestamps_with_timezone null: false + end + end +end diff --git a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb new file mode 100644 index 00000000000..a58c190e1d6 --- /dev/null +++ b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class CreateClustersKubernetesNamespaces < ActiveRecord::Migration + DOWNTIME = false + INDEX_NAME = 'kubernetes_namespaces_cluster_and_namespace' + + def change + create_table :clusters_kubernetes_namespaces, id: :bigserial do |t| + t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade } + t.references :project, index: true, foreign_key: { on_delete: :nullify } + t.references :cluster_project, index: true, foreign_key: { on_delete: :nullify } + + t.timestamps_with_timezone null: false + + t.string :encrypted_service_account_token_iv + t.string :namespace, null: false + t.string :service_account_name + + t.text :encrypted_service_account_token + + t.index [:cluster_id, :namespace], name: INDEX_NAME, unique: true + end + end +end diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb index 08d7f499eec..678876e886c 100644 --- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb +++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb @@ -113,7 +113,9 @@ class RenameReservedProjectNames < ActiveRecord::Migration begin # Because project path update is quite complex operation we can't safely # copy-paste all code from GitLab. As exception we use Rails code here - project.rename_repo if rename_project_row(project, path) + if rename_project_row(project, path) + Projects::AfterRenameService.new(project).execute + end rescue Exception => e # rubocop: disable Lint/RescueException Rails.logger.error "Exception when renaming project #{id}: #{e.message}" end @@ -123,6 +125,6 @@ class RenameReservedProjectNames < ActiveRecord::Migration def rename_project_row(project, path) project.respond_to?(:update_attributes) && project.update(path: path) && - project.respond_to?(:rename_repo) + defined?(Projects::AfterRenameService) end end diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb index 43a37667250..26a67b0f814 100644 --- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb +++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb @@ -55,7 +55,9 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration begin # Because project path update is quite complex operation we can't safely # copy-paste all code from GitLab. As exception we use Rails code here - project.rename_repo if rename_project_row(project, path) + if rename_project_row(project, path) + Projects::AfterRenameService.new(project).execute + end rescue Exception => e # rubocop: disable Lint/RescueException Rails.logger.error "Exception when renaming project #{id}: #{e.message}" end @@ -65,6 +67,6 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration def rename_project_row(project, path) project.respond_to?(:update_attributes) && project.update(path: path) && - project.respond_to?(:rename_repo) + defined?(Projects::AfterRenameService) end end diff --git a/db/schema.rb b/db/schema.rb index 3f3bec0ce04..ddfccbba678 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -691,6 +691,23 @@ ActiveRecord::Schema.define(version: 20181013005024) do add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree add_index "clusters_applications_runners", ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree + create_table "clusters_kubernetes_namespaces", id: :bigserial, force: :cascade do |t| + t.integer "cluster_id", null: false + t.integer "project_id" + t.integer "cluster_project_id" + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.text "encrypted_service_account_token" + t.string "encrypted_service_account_token_iv" + t.string "namespace", null: false + t.string "service_account_name" + end + + add_index "clusters_kubernetes_namespaces", ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", unique: true, using: :btree + add_index "clusters_kubernetes_namespaces", ["cluster_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_id", using: :btree + add_index "clusters_kubernetes_namespaces", ["cluster_project_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_project_id", using: :btree + add_index "clusters_kubernetes_namespaces", ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id", using: :btree + create_table "container_repositories", force: :cascade do |t| t.integer "project_id", null: false t.string "name", null: false @@ -2117,6 +2134,16 @@ ActiveRecord::Schema.define(version: 20181013005024) do add_index "user_interacted_projects", ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true, using: :btree add_index "user_interacted_projects", ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree + create_table "user_preferences", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "issue_notes_filter", limit: 2, default: 0, null: false + t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + end + + add_index "user_preferences", ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree + create_table "user_statuses", primary_key: "user_id", force: :cascade do |t| t.integer "cached_markdown_version" t.string "emoji", default: "speech_balloon", null: false @@ -2325,6 +2352,9 @@ ActiveRecord::Schema.define(version: 20181013005024) do add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade + add_foreign_key "clusters_kubernetes_namespaces", "cluster_projects", on_delete: :nullify + add_foreign_key "clusters_kubernetes_namespaces", "clusters", on_delete: :cascade + add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify add_foreign_key "container_repositories", "projects" add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade @@ -2440,6 +2470,7 @@ ActiveRecord::Schema.define(version: 20181013005024) do add_foreign_key "user_custom_attributes", "users", on_delete: :cascade add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade + add_foreign_key "user_preferences", "users", on_delete: :cascade add_foreign_key "user_statuses", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "users", "application_setting_terms", column: "accepted_term_id", name: "fk_789cd90b35", on_delete: :cascade diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 2feac1fd3b0..a1ea78b64bd 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -1,12 +1,10 @@ # Jobs artifacts administration ->**Notes:** ->- Introduced in GitLab 8.2 and GitLab Runner 0.7.0. ->- Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format - changed to `ZIP`. ->- Starting with GitLab 8.17, builds are renamed to jobs. ->- This is the administration documentation. For the user guide see - [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md). +> **Notes:** +> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0. +> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`. +> - Starting with GitLab 8.17, builds are renamed to jobs. +> - This is the administration documentation. For the user guide see [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md). Artifacts is a list of files and directories which are attached to a job after it completes successfully. This feature is enabled by default in all diff --git a/doc/administration/logs.md b/doc/administration/logs.md index a8cdd20581d..0e41a38b7e2 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -144,6 +144,20 @@ December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/ error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git' ``` +## `audit_json.log` + +This file lives in `/var/log/gitlab/gitlab-rails/audit_json.log` for +Omnibus GitLab packages or in `/home/git/gitlab/log/audit_json.log` for +installations from source. + +Changes to group or project settings are logged to this file. For example: + +```json +{"severity":"INFO","time":"2018-10-17T17:38:22.523Z","author_id":3,"entity_id":2,"entity_type":"Project","change":"visibility","from":"Private","to":"Public","author_name":"John Doe4","target_id":2,"target_type":"Project","target_details":"namespace2/project2"} +{"severity":"INFO","time":"2018-10-17T17:38:22.830Z","author_id":5,"entity_id":3,"entity_type":"Project","change":"name","from":"John Doe7 / project3","to":"John Doe7 / new name","author_name":"John Doe6","target_id":3,"target_type":"Project","target_details":"namespace3/project3"} +{"severity":"INFO","time":"2018-10-17T17:38:23.175Z","author_id":7,"entity_id":4,"entity_type":"Project","change":"path","from":"","to":"namespace4/newpath","author_name":"John Doe8","target_id":4,"target_type":"Project","target_details":"namespace4/newpath"} +``` + ## `sidekiq.log` This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 2952a98626a..d8345f2d6bd 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -242,6 +242,33 @@ verification requirement. Navigate to `Admin area ➔ Settings` and uncheck **Require users to prove ownership of custom domains** in the Pages section. This setting is enabled by default. +### Access control + +Access control was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) +in GitLab 11.5. It can be configured per-project, and allows access to a Pages +site to be controlled based on a user's membership to that project. + +Access control works by registering the Pages daemon as an OAuth application +with GitLab. Whenever a request to access a private Pages site is made by an +unauthenticated user, the Pages daemon redirects the user to GitLab. If +authentication is successful, the user is redirected back to Pages with a token, +which is persisted in a cookie. The cookies are signed with a secret key, so +tampering can be detected. + +Each request to view a resource in a private site is authenticated by Pages +using that token. For each request it receives, it makes a request to the GitLab +API to check that the user is authorized to read that site. + +Pages access control is currently disabled by default. To enable it, you must: + +1. Enable it in `/etc/gitlab/gitlab.rb` + + ```ruby + gitlab_pages['access_control'] = true + ``` + +1. [Reconfigure GitLab][reconfigure] + ## Activate verbose logging for daemon Verbose logging was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2533) in diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index 295905a7625..ddff54be575 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -391,6 +391,44 @@ the first one with a backslash (\). For example `pages.example.io` would be: server_name ~^.*\.pages\.example\.io$; ``` +## Access control + +Access control was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) +in GitLab 11.5. It can be configured per-project, and allows access to a Pages +site to be controlled based on a user's membership to that project. + +Access control works by registering the Pages daemon as an OAuth application +with GitLab. Whenever a request to access a private Pages site is made by an +unauthenticated user, the Pages daemon redirects the user to GitLab. If +authentication is successful, the user is redirected back to Pages with a token, +which is persisted in a cookie. The cookies are signed with a secret key, so +tampering can be detected. + +Each request to view a resource in a private site is authenticated by Pages +using that token. For each request it receives, it makes a request to the GitLab +API to check that the user is authorized to read that site. + +Pages access control is currently disabled by default. To enable it, you must: + +1. Modify your `config/gitlab.yml` file: + ```yaml + pages: + access_control: true + ``` +1. [Restart GitLab][restart] +1. Create a new [system OAuth application](../../integration/oauth_provider.md#adding-an-application-through-the-profile) + This should be called `GitLab Pages` and have a `Redirect URL` of + `https://projects.example.io/auth`. It does not need to be a "trusted" + application, but it does need the "api" scope. +1. Start the Pages daemon with the following additional arguments: + + ```shell + -auth-client-secret <OAuth code generated by GitLab> \ + -auth-redirect-uri http://projects.example.io/auth \ + -auth-secret <40 random hex characters> \ + -auth-server <URL of the GitLab instance> + ``` + ## Change storage path Follow the steps below to change the default path where GitLab Pages' contents diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index 715bc0cd08c..8b725e50f58 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -31,7 +31,7 @@ panel. If the repository check fails for some repository you should look up the error in `repocheck.log`: -- in the [admin panel](logs.md#repocheck.log) +- in the [admin panel](logs.md#repocheck-log) - or on disk, see: - `/var/log/gitlab/gitlab-rails` for Omnibus installations - `/home/git/gitlab/log` for installations from source diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md index bd758c49eba..9379944b250 100644 --- a/doc/administration/repository_storage_types.md +++ b/doc/administration/repository_storage_types.md @@ -41,11 +41,8 @@ Registry, etc. ## Hashed Storage -> **Warning:** Hashed storage is in **Beta**. For the latest updates, check the -> associated [issue](https://gitlab.com/gitlab-com/infrastructure/issues/3542) -> and please report any problems you encounter. -Hashed Storage is the new storage behavior we are rolling out with 10.0. Instead +Hashed Storage is the new storage behavior we rolled out with 10.0. Instead of coupling project URL and the folder structure where the repository will be stored on disk, we are coupling a hash, based on the project's ID. This makes the folder structure immutable, and therefore eliminates any requirement to diff --git a/doc/api/README.md b/doc/api/README.md index a351db99bbd..a620a13a3b3 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -69,6 +69,7 @@ following locations: - [System Hooks](system_hooks.md) - [Tags](tags.md) - [Todos](todos.md) +- [Triggering Pipelines](../ci/triggers/README.md) - [Users](users.md) - [Validate CI configuration](lint.md) - [V3 to V4](v3_to_v4.md) @@ -233,7 +234,10 @@ provided you are authenticated as an administrator with an OAuth or Personal Acc You need to pass the `sudo` parameter either via query string or a header with an ID/username of the user you want to perform the operation as. If passed as a header, the -header name must be `Sudo`. +header name must be `Sudo`. + +NOTE: **Note:** +Usernames are case insensitive. If a non administrative access token is provided, an error message will be returned with status code `403`: diff --git a/doc/api/applications.md b/doc/api/applications.md index 6d244594b71..d74a3cdf5c1 100644 --- a/doc/api/applications.md +++ b/doc/api/applications.md @@ -4,12 +4,12 @@ [ce-8160]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160 +Only admin user can use the Applications API. + ## Create a application Create a application by posting a JSON payload. -User must be admin to do that. - Returns `200` if the request succeeds. ``` @@ -30,8 +30,55 @@ Example response: ```json { + "id":1, "application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737", + "application_name": "MyApplication", "secret": "ee1dd64b6adc89cf7e2c23099301ccc2c61b441064e9324d963c46902a85ec34", "callback_url": "http://redirect.uri" } ``` + +## List all applications + +List all registered applications. + +``` +GET /applications +``` + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/applications +``` + +Example response: + +```json +[ + { + "id":1, + "application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737", + "application_name": "MyApplication", + "callback_url": "http://redirect.uri" + } +] +``` + +> Note: the `secret` value will not be exposed by this API. + +## Delete an application + +Delete a specific application. + +Returns `204` if the request succeeds. + +``` +DELETE /applications/:id +``` + +Parameters: + +- `id` (required) - The id of the application (not the application_id) + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/applications/:id +``` diff --git a/doc/api/groups.md b/doc/api/groups.md index be75c363a40..a9462fc413f 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -37,6 +37,7 @@ GET /groups "request_access_enabled": false, "full_name": "Foobar Group", "full_path": "foo-bar", + "file_template_project_id": 1, "parent_id": null } ] @@ -62,6 +63,7 @@ GET /groups?statistics=true "request_access_enabled": false, "full_name": "Foobar Group", "full_path": "foo-bar", + "file_template_project_id": 1, "parent_id": null, "statistics": { "storage_size" : 212, @@ -122,6 +124,7 @@ GET /groups/:id/subgroups "request_access_enabled": false, "full_name": "Foobar Group", "full_path": "foo-bar", + "file_template_project_id": 1, "parent_id": 123 } ] @@ -232,6 +235,7 @@ Example response: "request_access_enabled": false, "full_name": "Twitter", "full_path": "twitter", + "file_template_project_id": 1, "parent_id": null, "projects": [ { @@ -386,6 +390,7 @@ Example response: "request_access_enabled": false, "full_name": "Twitter", "full_path": "twitter", + "file_template_project_id": 1, "parent_id": null } ``` @@ -442,6 +447,7 @@ PUT /groups/:id | `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | | `request_access_enabled` | boolean | no | Allow users to request member access. | +| `file_template_project_id` | integer | no | **(Premium)** The ID of a project to load custom file templates from | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental" @@ -462,6 +468,7 @@ Example response: "request_access_enabled": false, "full_name": "Foobar Group", "full_path": "foo-bar", + "file_template_project_id": 1, "parent_id": null, "projects": [ { diff --git a/doc/api/notes.md b/doc/api/notes.md index 44940bdd9e5..9f6740ad86a 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -98,7 +98,7 @@ POST /projects/:id/issues/:issue_iid/notes Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) -- `issue_id` (required) - The IID of an issue +- `issue_iid` (required) - The IID of an issue - `body` (required) - The content of a note - `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) diff --git a/doc/api/project_templates.md b/doc/api/project_templates.md index ebdfa975849..ef98205cd68 100644 --- a/doc/api/project_templates.md +++ b/doc/api/project_templates.md @@ -1,19 +1,23 @@ # Project templates API -This API is a project-specific implementation of these endpoints: +This API is a project-specific version of these endpoints: - [Dockerfile templates](templates/dockerfiles.md) - [Gitignore templates](templates/gitignores.md) - [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [Open source license templates](templates/licenses.md) -It deprecates those endpoints, which will be removed for API version 5. +It deprecates these endpoints, which will be removed for API version 5. -Project-specific templates will be added to this API in time. This includes, but -is not limited to: +In addition to templates common to the entire instance, project-specific +templates are also available from this API endpoint. -- [Issue and Merge Request templates](../user/project/description_templates.html) -- [Group level file templates](https://gitlab.com/gitlab-org/gitlab-ee/issues/5987) **(Premium)** +Support will be added for [Issue and Merge Request templates](../user/project/description_templates.md) +in a future release. + +Support for [Group-level file templates](../user/group/index.md#group-level-file-templates-premium) +**[PREMIUM]** was [added](https://gitlab.com/gitlab-org/gitlab-ee/issues/5987) +in GitLab 11.5 ## Get all templates of a particular type diff --git a/doc/api/repositories.md b/doc/api/repositories.md index f5ac3816fe5..5dbf6cb0760 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -14,7 +14,7 @@ GET /projects/:id/repository/tree Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user -- `path` (optional) - The path inside repository. Used to get contend of subdirectories +- `path` (optional) - The path inside repository. Used to get content of subdirectories - `ref` (optional) - The name of a repository branch or tag or if not given the default branch - `recursive` (optional) - Boolean value used to get a recursive tree (false by default) diff --git a/doc/api/runners.md b/doc/api/runners.md index 0bcbd0aebf0..071c13f41cb 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -340,8 +340,7 @@ Example response: ## List project's runners List all runners (specific and shared) available in the project. Shared runners -are listed if at least one shared runner is defined **and** shared runners -usage is enabled in the project's settings. +are listed if at least one shared runner is defined. ``` GET /projects/:id/runners diff --git a/doc/api/users.md b/doc/api/users.md index 07f03f9c827..ee24aa09156 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -59,6 +59,9 @@ GET /users?active=true GET /users?blocked=true ``` +NOTE: **Note:** +Username search is case insensitive. + ### For admins ``` diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 0b64c8caba7..3b41036cd14 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -395,8 +395,67 @@ If you're running multiple Runners you will have to modify all configuration fil > login to GitLab's Container Registry. Once you've built a Docker image, you can push it up to the built-in -[GitLab Container Registry](../../user/project/container_registry.md). For example, -if you're using docker-in-docker on your runners, this is how your `.gitlab-ci.yml` +[GitLab Container Registry](../../user/project/container_registry.md). +Some things you should be aware of: + +- You must [log in to the container registry](#authenticating-to-the-container-registry) + before running commands. You can do this in the `before_script` if multiple + jobs depend on it. +- Using `docker build --pull` fetches any changes to base + images before building just in case your cache is stale. It takes slightly + longer, but means you don’t get stuck without security patches to base images. +- Doing an explicit `docker pull` before each `docker run` fetches + the latest image that was just built. This is especially important if you are + using multiple runners that cache images locally. Using the git SHA in your + image tag makes this less necessary since each job will be unique and you + shouldn't ever have a stale image. However, it's still possible to have a + stale image if you re-build a given commit after a dependency has changed. +- You don't want to build directly to `latest` tag in case there are multiple jobs + happening simultaneously. + +### Authenticating to the Container Registry + +There are three ways to authenticate to the Container Registry via GitLab CI/CD +and depend on the visibility of your project. + +For all projects, mostly suitable for public ones: + +- **Using the special `gitlab-ci-token` user**: This user is created for you in order to + push to the Registry connected to your project. Its password is automatically + set with the `$CI_JOB_TOKEN` variable. This allows you to automate building and deploying + your Docker images and has read/write access to the Registry. This is ephemeral, + so it's only valid for one job. You can use the following example as-is: + + ```sh + docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY + ``` + +For private and internal projects: + +- **Using a personal access token**: You can create and use a + [personal access token](../../user/profile/personal_access_tokens.md) + in case your project is private: + - For read (pull) access, the scope should be `read_registry`. + - For read/write (pull/push) access, use `api`. + Replace the `<username>` and `<access_token>` in the following example: + + ```sh + docker login -u <username> -p <access_token> $CI_REGISTRY + ``` + +- **Using the GitLab Deploy Token**: You can create and use a + [special deploy token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token) + with your private projects. It provides read-only (pull) access to the Registry. + Once created, you can use the special environment variables, and GitLab CI/CD + will fill them in for you. You can use the following example as-is: + + ```sh + docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY + ``` + +### Container Registry examples + +If you're using docker-in-docker on your Runners, this is how your `.gitlab-ci.yml` could look like: ```yaml @@ -414,11 +473,6 @@ could look like: - docker push registry.example.com/group/project/image:latest ``` -You have to use the special `gitlab-ci-token` user created for you in order to -push to the Registry connected to your project. Its password is provided in the -`$CI_JOB_TOKEN` variable. This allows you to automate building and deployment -of your Docker images. - You can also make use of [other variables](../variables/README.md) to avoid hardcoding: ```yaml @@ -508,22 +562,6 @@ deploy: - master ``` -Some things you should be aware of when using the Container Registry: - -- You must log in to the container registry before running commands. Putting - this in `before_script` will run it before each job. -- Using `docker build --pull` makes sure that Docker fetches any changes to base - images before building just in case your cache is stale. It takes slightly - longer, but means you don’t get stuck without security patches to base images. -- Doing an explicit `docker pull` before each `docker run` makes sure to fetch - the latest image that was just built. This is especially important if you are - using multiple runners that cache images locally. Using the git SHA in your - image tag makes this less necessary since each job will be unique and you - shouldn't ever have a stale image, but it's still possible if you re-build a - given commit after a dependency has changed. -- You don't want to build directly to `latest` in case there are multiple jobs - happening simultaneously. - [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities [2fa]: ../../user/profile/account/two_factor_authentication.md diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md index 7d4f28e1f47..66f0d429165 100644 --- a/doc/ci/docker/using_kaniko.md +++ b/doc/ci/docker/using_kaniko.md @@ -39,7 +39,7 @@ few important details: In the following example, kaniko is used to build a Docker image and then push it to [GitLab Container Registry](../../user/project/container_registry.md). The job will run only when a tag is pushed. A `config.json` file is created under -`/root/.docker` with the needed GitLab Container Registry credentials taken from the +`/kaniko/.docker` with the needed GitLab Container Registry credentials taken from the [environment variables](../variables/README.md#predefined-variables-environment-variables) GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the root directory of the project, builds the Docker image and pushes it to the @@ -52,8 +52,7 @@ build: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: - - mkdir -p /root/.docker - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /root/.docker/config.json + - 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:$CI_COMMIT_TAG only: - tags diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md index b3961ec91f3..66bfa41cad9 100644 --- a/doc/ci/examples/test-scala-application.md +++ b/doc/ci/examples/test-scala-application.md @@ -25,7 +25,7 @@ before_script: - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 642AC823 - apt-get update -y - apt-get install sbt -y - - sbt sbt-version + - sbt sbtVersion test: stage: test diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 83e0fa34ad6..2a179bfbbf0 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -312,7 +312,7 @@ We're always looking for contributions that can mitigate these If you think that registration token for a Project was revealed, you should reset them. It's recommended because such token can be used to register another -Runner to thi Project. It may be next used to obtain the values of secret +Runner to the Project. It may be next used to obtain the values of secret variables or clone the project code, that normally may be unavailable for the attacker. diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 24d60a0cdcc..4b2a6ccc7e4 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -681,7 +681,7 @@ Delayed job are for executing scripts after a certain period. This is useful if you want to avoid jobs entering `pending` state immediately. You can set the period with `start_in` key. The value of `start_in` key is an elapsed time in seconds, unless a unit is -provided. `start_key` must be less than or equal to one hour. Examples of valid values include: +provided. `start_in` key must be less than or equal to one hour. Examples of valid values include: - `10 seconds` - `30 minutes` @@ -2031,3 +2031,5 @@ CI with various languages. [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/
\ No newline at end of file diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 4bbcdc6329f..3fe79943fdc 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -1,55 +1,103 @@ # Code Review Guidelines -## Getting your merge request reviewed, approved, and merged +This guide contains advice and best practices for performing code review, and +having your code reviewed. + +All merge requests for GitLab CE and EE, whether written by a GitLab team member +or a volunteer contributor, must go through a code review process to ensure the +code is effective, understandable, and maintainable. -There are a few rules to get your merge request accepted: +## Getting your merge request reviewed, approved, and merged -1. Your merge request should only be **merged by a [maintainer][team]**. - 1. If your merge request includes only backend changes [^1], it must be - **approved by a [backend maintainer][projects]**. - 1. If your merge request includes only frontend changes [^1], it must be - **approved by a [frontend maintainer][projects]**. - 1. If your merge request includes UX changes [^1], it must - be **approved by a [UX team member][team]**. +You are strongly encouraged to get your code **reviewed** by a +[reviewer](https://about.gitlab.com/handbook/engineering/#reviewer) as soon as +there is any code to review, to get a second opinion on the chosen solution and +implementation, and an extra pair of eyes looking for bugs, logic problems, or +uncovered edge cases. The reviewer can be from a different team, but it is +recommended to pick someone who knows the domain well. You can read more about the +importance of involving reviewer(s) in the section on the responsibility of the author below. + +If you need some guidance (e.g. it's your first merge request), feel free to ask +one of the [Merge request coaches][team]. + +Depending on the areas your merge request touches, it must be **approved** by one +or more [maintainers](https://about.gitlab.com/handbook/engineering/#maintainer): + + 1. If your merge request includes backend changes [^1], it must be + **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**. + 1. If your merge request includes frontend changes [^1], it must be + **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**. + 1. If your merge request includes UX changes [^1], it must be + **approved by a [UX team member][team]**. 1. If your merge request includes adding a new JavaScript library [^1], it must be **approved by a [frontend lead][team]**. 1. If your merge request includes adding a new UI/UX paradigm [^1], it must be **approved by a [UX lead][team]**. - 1. If your merge request includes frontend and backend changes [^1], it must - be **approved by a [frontend and a backend maintainer][projects]**. - 1. If your merge request includes UX and frontend changes [^1], it must - be **approved by a [UX team member and a frontend maintainer][team]**. - 1. If your merge request includes UX, frontend and backend changes [^1], it must - be **approved by a [UX team member, a frontend and a backend maintainer][team]**. - 1. If your merge request includes a new dependency or a filesystem change, it must - be *approved by a [Distribution team member][team]*. See how to work with the [Distribution team for more details.](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) -1. To lower the amount of merge requests maintainers need to review, you can - ask or assign any [reviewers][projects] for a first review. - 1. If you need some guidance (e.g. it's your first merge request), feel free - to ask one of the [Merge request coaches][team]. - 1. It is recommended that you assign a maintainer that is from a different team than your own. - This ensures that all code across GitLab is consistent and can be easily understood by all contributors. - -1. Keep in mind that maintainers are also going to perform a final code review. - The ideal scenario is that the reviewer has already addressed any concerns - the maintainer would have found, and the maintainer only has to perform the - merge, but be prepared for further review comments. - -For more guidance, see [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md). + 1. If your merge request includes a new dependency or a filesystem change, it must be + **approved by a [Distribution team member][team]**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) for more details. -## Best practices +Getting your merge request **merged** also requires a maintainer. If it requires +more than one approval, the last maintainer to review and approve it will also merge it. -This guide contains advice and best practices for performing code review, and -having your code reviewed. +As described in the section on the responsibility of the maintainer below, you +are recommended to get your merge request approved and merged by maintainer(s) +from other teams than your own. -All merge requests for GitLab CE and EE, whether written by a GitLab team member -or a volunteer contributor, must go through a code review process to ensure the -code is effective, understandable, and maintainable. +### The responsibility of the merge request author -Any developer can, and is encouraged to, perform code review on merge requests -of colleagues and contributors. However, the final decision to accept a merge -request is up to one the project's maintainers, denoted on the -[engineering projects][projects]. +The responsibility to find the best solution and implement it lies with the +merge request author. + +Before assigning a merge request to a maintainer for approval and merge, they +should be confident that it actually solves the problem it was meant to solve, +that it does so in the most appropriate way, that it satisfies all requirements, +and that there are no remaining bugs, logical problems, or uncovered edge cases. +The merge request should also have a completed task list in its description and +a passing CI pipeline to avoid unnecessary back and forth. + +To reach the required level of confidence in their solution, an author is expected +to involve other people in the investigation and implementation processes as +appropriate. + +They are encouraged to reach out to domain experts to discuss different solutions +or get an implementation reviewed, to product managers and UX designers to clear +up confusion or verify that the end result matches what they had in mind, to +database specialists to get input on the data model or specific queries, or to +any other developer to get an in-depth review of the solution. + +If an author is unsure if a merge request needs a domain expert's opinion, that's +usually a pretty good sign that it does, since without it the required level of +confidence in their solution will not have been reached. + +### The responsibility of the maintainer + +Maintainers are responsible for the overall health, quality, and consistency of +the GitLab codebase, across domains and product areas. + +Consequently, their reviews will focus primarily on things like overall +architecture, code organization, separation of concerns, tests, DRYness, +consistency, and readability. + +Since a maintainer's job only depends on their knowledge of the overall GitLab +codebase, and not that of any specific domain, they can review, approve and merge +merge requests from any team and in any product area. + +In fact, authors are recommended to get their merge requests merged by maintainers +from other teams than their own, to ensure that all code across GitLab is consistent +and can be easily understood by all contributors, from both inside and outside the +company, without requiring team-specific expertise. + +Maintainers will do their best to also review the specifics of the chosen solution +before merging, but as they are not necessarily domain experts, they may be poorly +placed to do so without an unreasonable investment of time. In those cases, they +will defer to the judgment of the author and earlier reviewers and involved domain +experts, in favor of focusing on their primary responsibilities. + +If a developer who happens to also be a maintainer was involved in a merge request +as a domain expert and/or reviewer, it is recommended that they are not also picked +as the maintainer to ultimately approve and merge it. + +## Best practices ### Everyone diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md index 29af8dcb9bb..9da4c66933c 100644 --- a/doc/development/contributing/index.md +++ b/doc/development/contributing/index.md @@ -3,10 +3,12 @@ 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. + For a first-time step-by-step guide to the contribution process, please see ["Contributing to GitLab"](https://about.gitlab.com/contributing/). -Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute). +Looking for something to work on? Look for issues with the label [`Accepting merge requests`](#i-want-to-contribute). GitLab comes in two flavors, GitLab Community Edition (CE) our free and open source edition, and GitLab Enterprise Edition (EE) which is our commercial @@ -30,77 +32,8 @@ vulnerabilities. ## Code of conduct -### Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -### Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -### Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -### Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -### Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at conduct@gitlab.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -### Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org +Our code of conduct can be found on the +["Contributing to GitLab"](https://about.gitlab.com/contributing/) page. ## Closing policy for issues and merge requests @@ -133,10 +66,10 @@ the remaining issues on the GitHub issue tracker. ## I want to contribute! -If you want to contribute to GitLab, [issues in the `Backlog (Accepting merge requests)` milestone][accepting-mrs-weight] -are a great place to start. Issues with a lower weight (1 or 2) are deemed -suitable for beginners. These issues will be of reasonable size and challenge, -for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to +If you want to contribute to GitLab, +[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors) +are a great place to start. +If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel please consider we favor [asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution! @@ -198,4 +131,3 @@ This [documentation](style_guides.md) outlines the current style guidelines. [team]: https://about.gitlab.com/team/ [getting-help]: https://about.gitlab.com/getting-help/ [codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq -[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20(Accepting%20merge%20requests) diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index 3077422ae0a..4661d11b29e 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -181,10 +181,10 @@ Severity levels can be applied further depending on the facet of the impact; e.g Issues that are beneficial to our users, 'nice to haves', that we currently do not have the capacity for or want to give the priority to, are labeled as -~"Accepting Merge Requests", so the community can make a contribution. +~"Accepting merge requests", so the community can make a contribution. Community contributors can submit merge requests for any issue they want, but -the ~"Accepting Merge Requests" label has a special meaning. It points to +the ~"Accepting merge requests" label has a special meaning. It points to changes that: 1. We already agreed on, @@ -192,26 +192,26 @@ changes that: 1. Are likely to get accepted by a maintainer. We want to avoid a situation when a contributor picks an -~"Accepting Merge Requests" issue and then their merge request gets closed, +~"Accepting merge requests" issue and then their merge request gets closed, because we realize that it does not fit our vision, or we want to solve it in a different way. -We add the ~"Accepting Merge Requests" label to: +We add the ~"Accepting merge requests" label to: - Low priority ~bug issues (i.e. we do not add it to the bugs that we want to solve in the ~"Next Patch Release") - Small ~"feature proposal" - Small ~"technical debt" issues -After adding the ~"Accepting Merge Requests" label, we try to estimate the +After adding the ~"Accepting merge requests" label, we try to estimate the [weight](#issue-weight) of the issue. We use issue weight to let contributors know how difficult the issue is. Additionally: -- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs] +- We advertise [`Accepting merge requests` issues with weight < 5][up-for-grabs] as suitable for people that have never contributed to GitLab before on the [Up For Grabs campaign](http://up-for-grabs.net) - We encourage people that have never contributed to any open source project to - look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers] + look for [`Accepting merge requests` issues with a weight of 1][firt-timers] If you've decided that you would like to work on an issue, please @-mention the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what) @@ -220,12 +220,12 @@ members to further discuss scope, design, and technical considerations. This wil ensure that your contribution is aligned with the GitLab product and minimize any rework and delay in getting it merged into master. -GitLab team members who apply the ~"Accepting Merge Requests" label to an issue +GitLab team members who apply the ~"Accepting merge requests" label to an issue should update the issue description with a responsible product manager, inviting any potential community contributor to @-mention per above. -[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened -[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1 +[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight +[firt-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight&weight=1 ## Issue triaging @@ -356,6 +356,45 @@ for a release by the appropriate person. Make sure to mention the merge request that the ~"technical debt" issue or ~"UX debt" issue is associated with in the description of the issue. +## Technical debt in follow-up issues + +It's common to discover technical debt during development of a new feature. In +the spirit of "minimum viable change", resolution is often deferred to a +follow-up issue. However, this cannot be used as an excuse to merge poor-quality +code that would otherwise not pass review, or to overlook trivial matters that +don't deserve the be scheduled independently, and would be best resolved in the +original merge request - or not tracked at all! + +The overheads of scheduling, and rate of change in the GitLab codebase, mean +that the cost of a trivial technical debt issue can quickly exceed the value of +tracking it. This generally means we should resolve these in the original merge +request - or simply not create a follow-up issue at all. + +For example, a typo in a comment that is being copied between files is worth +fixing in the same MR, but not worth creating a follow-up issue for. Renaming a +method that is used in many places to make its intent slightly clearer may be +worth fixing, but it should not happen in the same MR, and is generally not +worth the overhead of having an issue of its own. These issues would invariably +be labelled `~P4 ~S4` if we were to create them. + +More severe technical debt can have implications for development velocity. If +it isn't addressed in a timely manner, the codebase becomes needlessly difficult +to change, new features become difficult to add, and regressions abound. + +Discoveries of this kind of technical debt should be treated seriously, and +while resolution in a follow-up issue may be appropriate, maintainers should +generally obtain a scheduling commitment from the author of the original MR, or +the engineering or product manager for the relevant area. This may take the form +of appropriate Priority / Severity labels on the issue, or an explicit milestone +and assignee. + +The maintainer must always agree before an outstanding discussion is resolved in +this manner, and will be the one to create the issue. The title and description +should be of the same quality as those created +[in the usual manner](#technical-and-ux-debt) - in particular, the issue title +**must not** begin with `Follow-up`! The creating maintainer should also expect +to be involved in some capacity when work begins on the follow-up issue. + ## Stewardship For issues related to the open source stewardship of GitLab, diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index cc7d8a1e1db..1764e2d8b21 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -2,10 +2,9 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The issues that are specifically suitable for -community contributions are listed with the -[`Backlog (Accepting merge requests)` milestone in the CE issue tracker][accepting-mrs-ce] -and [EE issue tracker][accepting-mrs-ee], but you are free to contribute to any other issue -you want. +community contributions are listed with +[the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors), +but you are free to contribute to any other issue you want. Please note that if an issue is marked for the current milestone either before or while you are working on it, a team member may take over the merge request @@ -25,8 +24,6 @@ some potentially easy issues. To start with GitLab development download the [GitLab Development Kit][gdk] and see the [Development section](../../README.md) for some guidelines. -[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_title=Backlog%20(Accepting%20merge%20requests) -[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?milestone_title=Backlog%20(Accepting%20merge%20requests) [gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests [gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 1dcdf788a3e..51f5ddfc1e0 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -451,16 +451,6 @@ preview the changes. The docs URL can be found in two places: - In the output of the `review-docs-deploy*` job, which also includes the triggered pipeline so that you can investigate whether something went wrong -In case the Review App URL returns 404, follow these steps to debug: - -1. **Did you follow the URL from the merge request widget?** If yes, then check if - the link is the same as the one in the job output. -1. **Did you follow the URL from the job output?** If yes, then it means that - either the site is not yet deployed or something went wrong with the remote - pipeline. Give it a few minutes and it should appear online, otherwise you - can check the status of the remote pipeline from the link in the job output. - If the pipeline failed or got stuck, drop a line in the `#docs` chat channel. - TIP: **Tip:** Someone that has no merge rights to the CE/EE projects (think of forks from contributors) will not be able to run the manual job. In that case, you can @@ -472,6 +462,18 @@ working on. If you don't, the remote docs branch won't be removed either, and the server where the Review Apps are hosted will eventually be out of disk space. +### Troubleshooting review apps + +In case the review app URL returns 404, follow these steps to debug: + +1. **Did you follow the URL from the merge request widget?** If yes, then check if + the link is the same as the one in the job output. +1. **Did you follow the URL from the job output?** If yes, then it means that + either the site is not yet deployed or something went wrong with the remote + pipeline. Give it a few minutes and it should appear online, otherwise you + can check the status of the remote pipeline from the link in the job output. + If the pipeline failed or got stuck, drop a line in the `#docs` chat channel. + ### Technical aspects If you want to know the hot details, here's what's really happening: diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md index 01068e23082..607ad21d459 100644 --- a/doc/development/documentation/structure.md +++ b/doc/development/documentation/structure.md @@ -142,9 +142,49 @@ fancy words. The docs should be clear and easy to understand. <!-- ## Troubleshooting Add a troubleshooting guide when possible/applicable. --> -``` + +--- Notes: -- (1): Apply the [tier badges](styleguide.md#product-badges) accordingly -- (2): Apply the correct format for the [GitLab version introducing the feature](styleguide.md#gitlab-versions-and-tiers) +- (1): Apply the [tier badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) accordingly +- (2): Apply the correct format for the [GitLab version introducing the feature](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers) +``` + +## Help and feedback section + +The "help and feedback" section (introduced by [!319](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/319)) displayed at the end of each document +can be omitted from the doc by adding a key into the its frontmatter: + +```yaml +--- +feedback: false +--- +``` + +The default is to leave it there. If you want to omit it from a document, +you must check with a technical writer before doing so. + +### Disqus + +We also have integrated the docs site with Disqus (introduced by +[!151](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/151)), +allowing our users to post comments. + +To omit only the comments from the feedback section, use the following +key on the frontmatter: + +```yaml +--- +comments: false +--- +``` + +We are only hiding comments in main index pages, such as [the main documentation index](../../README.md), since its content is too broad to comment on. Before omitting Disqus, +you must check with a technical writer. + +Note that once `feedback: false` is added to the frontmatter, it will automatically omit +Disqus, therefore, don't add both keys to the same document. + +The click events in the feedback section are tracked with Google Tag Manager. The +conversions can be viewed on Google Analytics by navigating to **Behavior > Events > Top events > docs**. diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md index 0f1f079bdb4..350593cc813 100644 --- a/doc/development/feature_flags.md +++ b/doc/development/feature_flags.md @@ -112,3 +112,8 @@ feature flag. You can stub a feature flag as follows: ```ruby stub_feature_flags(my_feature_flag: false) ``` + +## Enabling a feature flag + +Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md). + diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 5e13c725e15..f58d79fccf1 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -20,6 +20,7 @@ are very appreciative of the work done by translators and proofreaders! - French - Davy Defaud - [GitLab](https://gitlab.com/DevDef), [Crowdin](https://crowdin.com/profile/DevDef) - German + - Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah) - Indonesian - Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm) - Italian @@ -74,6 +75,8 @@ are very appreciative of the work done by translators and proofreaders! have previously translated. 1. Your request to become a proofreader will be considered on the merits of - your previous translations. + your previous translations by [GitLab team members](https://about.gitlab.com/team/) + or [Core team members](https://about.gitlab.com/core-team/) who are fluent in + the language or current proofreaders. [proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index acbfa1850b4..7727bd74c3c 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -209,6 +209,130 @@ it 'is overdue' do end ``` +### Pristine test environments + +The code exercised by a single GitLab test may access and modify many items of +data. Without careful preparation before a test runs, and cleanup afterward, +data can be changed by a test in such a way that it affects the behaviour of +following tests. This should be avoided at all costs! Fortunately, the existing +test framework handles most cases already. + +When the test environment does get polluted, a common outcome is +[flaky tests](flaky_tests.md). Pollution will often manifest as an order +dependency: running spec A followed by spec B will reliably fail, but running +spec B followed by spec A will reliably succeed. In these cases, you can use +`rspec --bisect` (or a manual pairwise bisect of spec files) to determine which +spec is at fault. Fixing the problem requires some understanding of how the test +suite ensures the environment is pristine. Read on to discover more about each +data store! + +#### SQL database + +This is managed for us by the `database_cleaner` gem. Each spec is surrounded in +a transaction, which is rolled back once the test completes. Certain specs will +instead issue `DELETE FROM` queries against every table after completion; this +allows the created rows to be viewed from multiple database connections, which +is important for specs that run in a browser, or migration specs, among others. + +One consequence of using these strategies, instead of the well-known +`TRUNCATE TABLES` approach, is that primary keys and other sequences are **not** +reset across specs. So if you create a project in spec A, then create a project +in spec B, the first will have `id=1`, while the second will have `id=2`. + +This means that specs should **never** rely on the value of an ID, or any other +sequence-generated column. To avoid accidental conflicts, specs should also +avoid manually specifying any values in these kinds of columns. Instead, leave +them unspecified, and look up the value after the row is created. + +#### Redis + +GitLab stores two main categories of data in Redis: cached items, and sidekiq +jobs. + +In most specs, the Rails cache is actually an in-memory store. This is replaced +between specs, so calls to `Rails.cache.read` and `Rails.cache.write` are safe. +However, if a spec makes direct Redis calls, it should mark itself with the +`:clean_gitlab_redis_cache`, `:clean_gitlab_redis_shared_state` or +`:clean_gitlab_redis_queues` traits as appropriate. + +Sidekiq jobs are typically not run in specs, but this behaviour can be altered +in each spec through the use of `Sidekiq::Testing.inline!` blocks. Any spec that +causes Sidekiq jobs to be pushed to Redis should use the `:sidekiq` trait, to +ensure that they are removed once the spec completes. + +#### Filesystem + +Filesystem data can be roughly split into "repositories", and "everything else". +Repositories are stored in `tmp/tests/repositories`. This directory is emptied +before a test run starts, and after the test run ends. It is not emptied between +specs, so created repositories accumulate within this directory over the +lifetime of the process. Deleting them is expensive, but this could lead to +pollution unless carefully managed. + +To avoid this, [hashed storage](../../administration/repository_storage_types.md) +is enabled in the test suite. This means that repositories are given a unique +path that depends on their project's ID. Since the project IDs are not reset +between specs, this guarantees that each spec gets its own repository on disk, +and prevents changes from being visible between specs. + +If a spec manually specifies a project ID, or inspects the state of the +`tmp/tests/repositories/` directory directly, then it should clean up the +directory both before and after it runs. In general, these patterns should be +completely avoided. + +Other classes of file linked to database objects, such as uploads, are generally +managed in the same way. With hashed storage enabled in the specs, they are +written to disk in locations determined by ID, so conflicts should not occur. + +Some specs disable hashed storage by passing the `:legacy_storage` trait to the +`projects` factory. Specs that do this must **never** override the `path` of the +project, or any of its groups. The default path includes the project ID, so will +not conflict; but if two specs create a `:legacy_storage` project with the same +path, they will use the same repository on disk and lead to test environment +pollution. + +Other files must be managed manually by the spec. If you run code that creates a +`tmp/test-file.csv` file, for instance, the spec must ensure that the file is +removed as part of cleanup. + +#### Persistent in-memory application state + +All the specs in a given `rspec` run share the same Ruby process, which means +they can affect each other by modifying Ruby objects that are accessible between +specs. In practice, this means global variables, and constants (which includes +Ruby classes, modules, etc). + +Global variables should generally not be modified. If absolutely necessary, a +block like this can be used to ensure the change is rolled back afterwards: + +```ruby +around(:each) do |example| + old_value = $0 + + begin + $0 = "new-value" + example.run + ensure + $0 = old_value + end +end +``` + +If a spec needs to modify a constant, it should use the `stub_const` helper to +ensure the change is rolled back. + +If you need to modify the contents of the `ENV` constant, you can use the +`stub_env` helper method instead. + +While most Ruby **instances** are not shared between specs, **classes** +and **modules** generally are. Class and module instance variables, accessors, +class variables, and other stateful idioms, should be treated in the same way as +global variables - don't modify them unless you have to! In particular, prefer +using expectations, or dependency injection along with stubs, to avoid the need +for modifications. If you have no other choice, an `around` block similar to the +example for global variables, above, can be used, but this should be avoided if +at all possible. + ### Table-based / Parameterized tests This style of testing is used to exercise one piece of code with a comprehensive @@ -348,6 +472,37 @@ GitLab uses [factory_bot] as a test fixture replacement. All fixtures should be be placed under `spec/fixtures/`. +### Repositories + +Testing some functionality, e.g., merging a merge request, requires a git +repository with a certain state to be present in the test environment. GitLab +maintains the [gitlab-test](https://gitlab.com/gitlab-org/gitlab-test) +repository for certain common cases - you can ensure a copy of the repository is +used with the `:repository` trait for project factories: + +```ruby +let(:project) { create(:project, :repository) } +``` + +Where you can, consider using the `:custom_repo` trait instead of `:repository`. +This allows you to specify exactly what files will appear in the `master` branch +of the project's repository. For example: + +```ruby +let(:project) do + create( + :project, :custom_repo, + files: { + 'README.md' => 'Content here', + 'foo/bar/baz.txt' => 'More content here' + } + ) +end +``` + +This will create a repository containing two files, with default permissions and +the specified content. + ### Config RSpec config files are files that change the RSpec config (i.e. diff --git a/doc/install/installation.md b/doc/install/installation.md index 9e2e58657f1..1210ac58499 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -460,11 +460,11 @@ GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is o ### Install Gitaly # Fetch Gitaly source with Git and compile with Go - sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production + sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production You can specify a different Git repository by providing it as an extra parameter: - sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,https://example.com/gitaly.git]" RAILS_ENV=production + sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production Next, make sure gitaly configured: diff --git a/doc/integration/saml.md b/doc/integration/saml.md index e2eea57d694..a7470d27b4b 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -339,6 +339,23 @@ args: { } ``` +### `uid_attribute` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/43806) in GitLab 10.7. + +By default, the `uid` is set as the `name_id` in the SAML response. If you'd like to designate a unique attribute for the `uid`, you can set the `uid_attribute`. In the example below, the value of `uid` attribute in the SAML response is set as the `uid_attribute`. + +```yaml +args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + idp_sso_target_url: 'https://login.example.com/idp', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + uid_attribute: 'uid' +} +``` + ## Troubleshooting ### 500 error after login diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index a4f17746b69..2e8380aa5d8 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -83,7 +83,7 @@ sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workh ```bash cd /home/git/gitlab -sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production +sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production ``` ### 6. Update gitlab-shell to the corresponding version diff --git a/doc/user/group/img/access_requests_management.png b/doc/user/group/img/access_requests_management.png Binary files differindex 36deaa89a70..7de6a1c0a5e 100644 --- a/doc/user/group/img/access_requests_management.png +++ b/doc/user/group/img/access_requests_management.png diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png Binary files differindex 99b8e52ea13..4431c9fbe0b 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_group_info.png b/doc/user/group/img/create_new_group_info.png Binary files differindex 1ac26fb08d9..c2e6ed43c5b 100644 --- a/doc/user/group/img/create_new_group_info.png +++ b/doc/user/group/img/create_new_group_info.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 553cd0759aa..b6286ac7800 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/group/img/group_settings.png b/doc/user/group/img/group_settings.png Binary files differindex 1705bf4ce8e..f3a75f1bde8 100644 --- a/doc/user/group/img/group_settings.png +++ b/doc/user/group/img/group_settings.png diff --git a/doc/user/group/img/request_access_button.png b/doc/user/group/img/request_access_button.png Binary files differindex 54b490a3bb2..4d73990ec7e 100644 --- a/doc/user/group/img/request_access_button.png +++ b/doc/user/group/img/request_access_button.png diff --git a/doc/user/group/img/select_group_dropdown.png b/doc/user/group/img/select_group_dropdown.png Binary files differindex 79eca5d94d5..8c03ffffbde 100644 --- a/doc/user/group/img/select_group_dropdown.png +++ b/doc/user/group/img/select_group_dropdown.png diff --git a/doc/user/group/img/withdraw_access_request_button.png b/doc/user/group/img/withdraw_access_request_button.png Binary files differindex 4365f7fa788..a5fe78eb090 100644 --- a/doc/user/group/img/withdraw_access_request_button.png +++ b/doc/user/group/img/withdraw_access_request_button.png diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 7d01c6f2bf6..d673fa4d21a 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -252,6 +252,13 @@ level of members in group. Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock). +#### Group-level file templates **[PREMIUM]** + +Group-level file templates allow you to share a set of templates for common file +types with every project in a group. + +Learn more about [Group-level file templates](https://docs.gitlab.com/ee/user/group/index.html#group-level-file-templates-premium). + ### Advanced settings - **Projects**: view all projects within that group, add members to each project, diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index bc6ecdf4f32..64219737d61 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -2,8 +2,8 @@ Two-factor Authentication (2FA) provides an additional level of security to your GitLab account. Once enabled, in addition to supplying your username and -password to login, you'll be prompted for a code generated by your one time password -authenticator. For example, a password manager on one of your devices. +password to login, you'll be prompted for a code generated by your one time password +authenticator. For example, a password manager on one of your devices. By enabling 2FA, the only way someone other than you can log into your account is to know your username and password *and* have access to your one time password secret. @@ -83,9 +83,11 @@ Click on **Register U2F Device** to complete the process. Recovery codes are not generated for U2F devices. Should you ever lose access to your one time password authenticator, you can use one of the ten provided -backup codes to login to your account. We suggest copying or printing them for -storage in a safe place. **Each code can be used only once** to log in to your -account. +backup codes to login to your account. We suggest copying them, printing them, or downloading them using +the **Download codes** button for storage in a safe place. + +CAUTION: **Caution:** +Each code can be used only once to log in to your account. If you lose the recovery codes or just want to generate new ones, you can do so [using SSH](#generate-new-recovery-codes-using-ssh). diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 25d6c34409c..7d55048c994 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -45,16 +45,14 @@ the following table. | Scope | Description | | ----- | ----------- | |`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). | -| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. | -| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). | +| `api` | Grants complete access to the API and Container Registry (read/write) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. | +| `read_registry` | Allows to read (pull) [container registry] images if a project is private and authorization is required ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 9.3). | | `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). | -| `read_repository` | Allows read-access to the repository through git clone. | +| `read_repository` | Allows read-access (pull) to the repository through git clone. | [2fa]: ../account/two_factor_authentication.md [api]: ../../api/README.md [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 -[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 -[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845 [ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838 [container registry]: ../project/container_registry.md [users]: ../../api/users.md diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 3ec17806490..48004471f0a 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -215,7 +215,7 @@ twice, which can lead to confusion during deployments. | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) | | [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | -| [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 [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with [Rubix](https://github.com/amit1rrr/rubix). **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/) | +| [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 [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image 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 at [Nurtch Documentation](http://docs.nurtch.com/en/latest). **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/) | ## Getting the external IP address diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md index 2709ebb6f05..1b1827a2658 100644 --- a/doc/user/project/container_registry.md +++ b/doc/user/project/container_registry.md @@ -119,12 +119,17 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do > Project Deploy Tokens were [introduced][ce-17894] in GitLab 10.7 If a project is private, credentials will need to be provided for authorization. -The preferred way to do this, is either by using a [personal access tokens][pat] or a [project deploy token][pdt]. +There are two ways to do this: + +- By using a [personal access token](../profile/personal_access_tokens.md). +- By using a [deploy token](../project/deploy_tokens/index.md). + The minimal scope needed for both of them is `read_registry`. -Example of using a personal access token: -``` -docker login registry.example.com -u <your_username> -p <your_access_token> +Example of using a token: + +```sh +docker login registry.example.com -u <username> -p <token> ``` ## Troubleshooting the GitLab Container Registry diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index ff647b2f0a2..dc73194309c 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -9,7 +9,7 @@ at midnight UTC and that they can be only managed by [maintainers](https://docs. ## Creating a Deploy Token -You can create as many deploy tokens as you like from the settings of your project: +You can create as many deploy tokens as you like from the settings of your project: 1. Log in to your GitLab account. 1. Go to the project you want to create Deploy Tokens for. @@ -49,14 +49,13 @@ To download a repository using a Deploy Token, you just need to: 2. Take note of your `username` and `token` 3. `git clone` the project using the Deploy Token: + ```sh + git clone http://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git + ``` -```bash -git clone https://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git -``` - -Just replace `<username>` and `<deploy_token>` with the proper values +Replace `<username>` and `<deploy_token>` with the proper values. -### Read container registry images +### Read Container Registry images To read the container registry images, you'll need to: @@ -64,21 +63,29 @@ To read the container registry images, you'll need to: 2. Take note of your `username` and `token` 3. Log in to GitLab’s Container Registry using the deploy token: -``` +```sh docker login registry.example.com -u <username> -p <deploy_token> ``` -Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply +Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply pull images from your Container Registry. ### GitLab Deploy Token > [Introduced][ce-18414] in GitLab 10.8. -There's a special case when it comes to Deploy Tokens, if a user creates one -named `gitlab-deploy-token`, the username and token of the Deploy Token will be -automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and -`CI_DEPLOY_PASSWORD`, respectively. +There's a special case when it comes to Deploy Tokens. If a user creates one +named `gitlab-deploy-token`, the username and token of the Deploy Token will be +automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and +`CI_DEPLOY_PASSWORD`, respectively. With the GitLab Deploy Token, the +`read_registry` scope is implied. + +After you create the token, you can login to the Container Registry using +those variables: + +```sh +docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY +``` [ce-17894]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894 [ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845 diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index 631f511b5fa..d78721f8658 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -118,9 +118,12 @@ is the `project-name`, and `xxx` is the issue number. #### 13. @mentions -- Mentions: you can either `@mention` a user or a group present in your -GitLab instance and they will be notified via todos and email, unless that -person has disabled all notifications in their profile settings. +- You can either `@mention` a user or a group present in your + GitLab instance and they will be notified via todos and email, unless that + person has disabled all notifications in their profile settings. +- Mentions for yourself (the current logged in user),will be distinctly highlighted + in a different color, allowing you to easily see which comments involve you, + helping you focus on them quickly. To change your [notification settings](../../../workflow/notifications.md) navigate to **Profile Settings** > **Notifications** > **Global notification level** diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index 632253db94c..3cf46231a9d 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -68,7 +68,8 @@ From [project issue boards](../issue_board.md), you can filter by both group mil When filtering by milestone, in addition to choosing a specific project milestone or group milestone, you can choose a special milestone filter. -- **No Milestone**: Show issues or merge requests with no assigned milestone. +- **None**: Show issues or merge requests with no assigned milestone. +- **Any**: Show issues or merge requests that have an assigned milestone. - **Upcoming**: Show issues or merge requests that have been assigned the open milestone that has the next upcoming due date (i.e. nearest due date in the future). - **Started**: Show issues or merge requests that have an assigned milestone with a start date that is before today. diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index c2f53540089..0a4542b71ed 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -24,6 +24,7 @@ discussions, and descriptions: | `/reopen` | Reopen | ✓ | ✓ | | `/title <New title>` | Change title | ✓ | ✓ | | `/award :emoji:` | Toggle emoji award | ✓ | ✓ | +| `/assign me` | Assign yourself | ✓ | ✓ | | `/assign @user` | Assign one user | ✓ | ✓ | | `/assign @user1 @user2` | Assign multiple users **[STARTER]** | ✓ | | | `/unassign` | Remove assignee(s) | ✓ | ✓ | diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md index e1d8345f415..783081cec26 100644 --- a/doc/user/project/repository/branches/index.md +++ b/doc/user/project/repository/branches/index.md @@ -30,12 +30,12 @@ to learn more. ## Delete merged branches -> [Introduced][ce-6449] in GitLab 8.14. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449) in GitLab 8.14. ![Delete merged branches](img/delete_merged_branches.png) This feature allows merged branches to be deleted in bulk. Only branches that -have been merged and [are not protected][protected] will be deleted as part of +have been merged and [are not protected](../../protected_branches.md) will be deleted as part of this operation. It's particularly useful to clean up old branches that were not deleted @@ -44,7 +44,7 @@ automatically when a merge request was merged. ## Branch filter search box -> [Introduced][https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166] in GitLab 11.5. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166) in GitLab 11.5. ![Branch filter search box](img/branch_filter_search_box.png) @@ -57,6 +57,3 @@ Sometimes when you have hundreds of branches you may want a more flexible matchi - `^feature` will only match branch names that begin with 'feature'. - `feature$` will only match branch names that end with 'feature'. - -[ce-6449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449 "Add button to delete all merged branches" -[protected]: ../../protected_branches.md diff --git a/doc/workflow/img/repository_mirroring_force_update.png b/doc/workflow/img/repository_mirroring_force_update.png Binary files differnew file mode 100644 index 00000000000..8ba715d1ba3 --- /dev/null +++ b/doc/workflow/img/repository_mirroring_force_update.png diff --git a/doc/workflow/img/repository_mirroring_pull_settings_lower.png b/doc/workflow/img/repository_mirroring_pull_settings_lower.png Binary files differnew file mode 100644 index 00000000000..a3e0b74ddf8 --- /dev/null +++ b/doc/workflow/img/repository_mirroring_pull_settings_lower.png diff --git a/doc/workflow/img/repository_mirroring_pull_settings_upper.png b/doc/workflow/img/repository_mirroring_pull_settings_upper.png Binary files differnew file mode 100644 index 00000000000..c60354fdca7 --- /dev/null +++ b/doc/workflow/img/repository_mirroring_pull_settings_upper.png diff --git a/doc/workflow/img/repository_mirroring_push_settings.png b/doc/workflow/img/repository_mirroring_push_settings.png Binary files differnew file mode 100644 index 00000000000..21a6aca4526 --- /dev/null +++ b/doc/workflow/img/repository_mirroring_push_settings.png diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md index 8c4e6ea8eab..4225d1aa31d 100644 --- a/doc/workflow/repository_mirroring.md +++ b/doc/workflow/repository_mirroring.md @@ -1,351 +1,394 @@ # Repository mirroring -Repository mirroring is a way to mirror repositories from external sources. -It can be used to mirror all branches, tags, and commits that you have -in your repository. +Repository mirroring allows for mirroring of repositories to and from external sources. It can be +used to mirror branches, tags, and commits between repositories. -Your mirror at GitLab will be updated automatically. You can -also manually trigger an update at most once every 5 minutes. +A repository mirror at GitLab will be updated automatically. You can also manually trigger an update +at most once every 5 minutes. ## Overview -Repository mirroring is very useful when, for some reason, you must use a -project from another source. +Repository mirroring is useful when you want to use a repository outside of GitLab. -There are two kinds of repository mirroring features supported by GitLab: -**push** and **pull**, the latter being only available in GitLab Enterprise Edition. -The **push** method mirrors the repository in GitLab to another location. +There are two kinds of repository mirroring supported by GitLab: -Once the mirror repository is updated, all new branches, -tags, and commits will be visible in the project's activity feed. -Users with at least [developer access][perms] to the project can also force an -immediate update with the click of a button. This button will not be available if -the mirror is already being updated or 5 minutes still haven't passed since its last update. +- Push: for mirroring a GitLab repository to another location. +- Pull: for mirroring a repository from another location to GitLab. **[STARTER]** -A few things/limitations to consider: +When the mirror repository is updated, all new branches, tags, and commits will be visible in the +project's activity feed. -- The repository must be accessible over `http://`, `https://`, `ssh://` or `git://`. -- If your HTTP repository is not publicly accessible, add authentication - information to the URL, like: `https://username@gitlab.company.com/group/project.git`. - In some cases, you might need to use a personal access token instead of a - password, e.g., you want to mirror to GitHub and have 2FA enabled. -- The import will time out after 15 minutes. For repositories that take longer - use a clone/push combination. -- The Git LFS objects will not be synced. You'll need to push/pull them - manually. +Users with at least [developer access](../user/permissions.md) to the project can also force an +immediate update, unless: + +- The mirror is already being updated. +- 5 minutes haven't elapsed since its last update. ## Use cases -- You migrated to GitLab but still need to keep your project in another source. - In that case, you can simply set it up to mirror to GitLab (pull) and all the - essential history of commits, tags and branches will be available in your - GitLab instance. -- You have old projects in another source that you don't use actively anymore, - but don't want to remove for archiving purposes. In that case, you can create - a push mirror so that your active GitLab repository can push its changes to the - old location. +The following are some possible use cases for repository mirroring: -## Pulling from a remote repository **[STARTER]** +- You migrated to GitLab but still need to keep your project in another source. In that case, you + can simply set it up to mirror to GitLab (pull) and all the essential history of commits, tags, + and branches will be available in your GitLab instance. **[STARTER]** +- You have old projects in another source that you don't use actively anymore, but don't want to + remove for archiving purposes. In that case, you can create a push mirror so that your active + GitLab repository can push its changes to the old location. ->[Introduced][ee-51] in GitLab Enterprise Edition 8.2. +## Pushing to a remote repository **[CORE]** -You can set up a repository to automatically have its branches, tags, and commits -updated from an upstream repository. This is useful when a repository you're -interested in is located on a different server, and you want to be able to -browse its content and its activity using the familiar GitLab interface. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise +> Edition 8.7. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8. -When creating a new project, you can enable repository mirroring when you choose -to import the repository from "Any repo by URL". Enter the full URL of the Git -repository to pull from and click on the **Mirror repository** checkbox. +For an existing project, you can set up push mirroring as follows: -![New project](repository_mirroring/repository_mirroring_new_project.png) +1. Navigate to your project's **Settings > Repository** and expand the **Mirroring repositories** section. +1. Enter a repository URL. +1. Select **Push** from the **Mirror direction** dropdown. +1. Select an authentication method from the **Authentication method** dropdown, if necessary. +1. Check the **Only mirror protected branches** box, if necessary. +1. Click the **Mirror repository** button to save the configuration. -For an existing project, you can set up mirror pulling by visiting your project's -**Settings ➔ Repository** and searching for the "Pull from a remote repository" -section. Check the "Mirror repository" box and hit **Save changes** at the bottom. -You have a few options to choose from one being the user who will be the author -of all events in the activity feed that are the result of an update. This user -needs to have at least [master access][perms] to the project. Another option is -whether you want to trigger builds for mirror updates. +![Repository mirroring push settings screen](img/repository_mirroring_push_settings.png) -![Pull settings](repository_mirroring/repository_mirroring_pull_settings.png) +When push mirroring is enabled, only push commits directly to the mirrored repository to prevent the +mirror diverging. All changes will end up in the mirrored repository whenever: -Since the repository on GitLab functions as a mirror of the upstream repository, -you are advised not to push commits directly to the repository on GitLab. -Instead, any commits should be pushed to the upstream repository, and will end -up in the GitLab repository automatically within a certain period of time -or when a [forced update](#forcing-an-update) is initiated. +- Commits are pushed to GitLab. +- A [forced update](#forcing-an-update) is initiated. -If you do manually update a branch in the GitLab repository, the branch will -become diverged from upstream, and GitLab will no longer automatically update -this branch to prevent any changes from being lost. +Changes pushed to files in the repository are automatically pushed to the remote mirror at least: -![Diverged branch](repository_mirroring/repository_mirroring_diverged_branch.png) +- Within five minutes of being received. +- Within one minute if **Only mirror protected branches** is enabled. -### Trigger update using API **[STARTER]** +In the case of a diverged branch, you will see an error indicated at the **Mirroring repositories** +section. + +### Push only protected branches **[CORE]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350) in +> [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8. ->[Introduced][ee-3453] in GitLab Enterprise Edition 10.3. +You can choose to only push your protected branches from GitLab to your remote repository. -Pull mirroring uses polling to detect new branches and commits added upstream, -often many minutes afterwards. If you notify GitLab by [API][pull-api], updates -will be pulled immediately. +To use this option, check the **Only mirror protected branches** box when creating a repository +mirror. -Read the [Pull Mirror Trigger API docs][pull-api]. +## Setting up a push mirror from GitLab to GitHub **[CORE]** -### Pull only protected branches **[STARTER]** +To set up a mirror from GitLab to GitHub, you need to follow these steps: ->[Introduced][ee-3326] in GitLab Enterprise Edition 10.3. +1. Create a [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with the `public_repo` box checked. +1. Fill in the **Git repository URL** field, with the personal access token instead of a password. + For example: `https://<GitHubUsername>:<GitHubPersonalAccessToken>@github.com/group/project.git`. +1. Click the **Mirror repository** button. +1. Wait, or click the update button. -You can choose to only pull the protected branches from your remote repository to GitLab. +## Pulling from a remote repository **[STARTER]** -To use this option go to your project's repository settings page under pull mirror. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2. -### Overwrite diverged branches **[STARTER]** +You can set up a repository to automatically have its branches, tags, and commits updated from an +upstream repository. ->[Introduced][ee-4559] in GitLab Enterprise Edition 10.6. +This is useful when a repository you're interested in is located on a different server, and you want +to be able to browse its content and its activity using the familiar GitLab interface. -You can choose to always update your local branch with the remote version even -if your local version has diverged from the remote. +To configure mirror pulling for an existing project: -To use this option go to your project's repository settings page under pull mirror. +1. Navigate to your project's **Settings > Repository** and expand the **Mirroring repositories** + section. +1. Enter a repository URL. +1. Select **Pull** from the **Mirror direction** dropdown. +1. Select an authentication method from the **Authentication method** dropdown, if necessary. +1. If necessary, check the following boxes: + - **Overwrite diverged branches**. + - **Trigger pipelines for mirror updates**. + - **Only mirror protected branches**. +1. Click the **Mirror repository** button to save the configuration. -### Hard failure **[STARTER]** +![Repository mirroring pull settings screen - upper part](img/repository_mirroring_pull_settings_upper.png) ->[Introduced][ee-3117] in GitLab Enterprise Edition 10.2. +--- -Once a mirror gets retried 14 times in a row, it will get marked as hard failed, -this will become visible in either the project main dashboard or in the -pull mirror settings page. +![Repository mirroring pull settings screen - lower part](img/repository_mirroring_pull_settings_lower.png) -![Hard failed mirror main notice](repository_mirroring/repository_mirroring_hard_failed_main.png) +Because GitLab is now set to pull changes from the upstream repository, you should not push commits +directly to the repository on GitLab. Instead, any commits should be pushed to the upstream repository. +Changes pushed to the upstream repository will be pulled into the GitLab repository, either: -![Hard failed mirror settings notice](repository_mirroring/repository_mirroring_hard_failed_settings.png) +- Automatically within a certain period of time. +- When a [forced update](#forcing-an-update) is initiated. -When a project is hard failed, it will no longer get picked up for mirroring. -A user can resume the project mirroring again by either [forcing an update](#forcing-an-update) -or by changing the import URL in repository settings. +CAUTION: **Caution:** +If you do manually update a branch in the GitLab repository, the branch will become diverged from +upstream and GitLab will no longer automatically update this branch to prevent any changes from being lost. + +### How it works + +Once you activate the pull mirroring feature, the mirror will be inserted into a queue. A scheduler +will start every minute and schedule a fixed number of mirrors for update, based on the configured maximum capacity. + +If the mirror updates successfully, it will be enqueued once again with a small backoff period. + +If the mirror fails (for example, a branch diverged from upstream), the project's backoff period is +increased each time it fails, up to a maximum amount of time. ### SSH authentication **[STARTER]** -> [Introduced][ee-2551] in GitLab Starter 9.5 +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5. -If you're mirroring over SSH (i.e., an `ssh://` URL), you can authenticate using -password-based authentication, just as over HTTPS, but you can also use public -key authentication. This is often more secure than password authentication, -especially when the source repository supports [Deploy Keys][deploy-key]. +SSH authentication is mutual: -To get started, navigate to **Settings ➔ Repository ➔ Pull from a remote repository**, -enable mirroring (if not already enabled) and enter an `ssh://` URL. +- You have to prove to the server that you're allowed to access the repository. +- The server also has to prove to *you* that it's who it claims to be. -> **NOTE**: SCP-style URLs, e.g., `git@example.com:group/project.git`, are not -supported at this time. +You provide your credentials as a password or public key. The server that the source repository +resides on provides its credentials as a "host key", the fingerprint of which needs to be verified manually. -Entering the URL adds two features to the page - `Fingerprints` and -`SSH public key authentication`: +If you're mirroring over SSH (that is, using an `ssh://` URL), you can authenticate using: -![Pull settings for SSH](repository_mirroring/repository_mirroring_pull_settings_for_ssh.png) +- Password-based authentication, just as over HTTPS. +- Public key authentication. This is often more secure than password authentication, especially when + the source repository supports [Deploy Keys](../ssh/README.md#deploy-keys). -SSH authentication is mutual. You have to prove to the server that you're -allowed to access the repository, but the server also has to prove to *you* that -it's who it claims to be. You provide your credentials as a password or public -key. The server that the source repository resides on provides its credentials -as a "host key", the fingerprint of which needs to be verified manually. +To get started: -Press the `Detect host keys` button. GitLab will fetch the host keys from the -server, and display the fingerprints to you: +1. Navigate to your project's **Settings > Repository** and expand the **Mirroring repositories** section. +1. Enter an `ssh://` URL for mirroring. -![Detect SSH host keys](repository_mirroring/repository_mirroring_detect_host_keys.png) +NOTE: **Note:** +SCP-style URLs (that is, `git@example.com:group/project.git`) are not supported at this time. + +Entering the URL adds two buttons to the page: + +- **Detect host keys**. +- **Input host keys manually**. + +If you click the: + +- **Detect host keys** button, GitLab will fetch the host keys from the server and display the fingerprints. +- **Input host keys manually** button, a field is displayed where you can paste in host keys. You now need to verify that the fingerprints are those you expect. GitLab.com and other code hosting sites publish their fingerprints in the open for you to check: -* [AWS CodeCommit](http://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints) -* [Bitbucket](https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints) -* [GitHub](https://help.github.com/articles/github-s-ssh-key-fingerprints/) -* [GitLab.com](https://about.gitlab.com/gitlab-com/settings/#ssh-host-keys-fingerprints) -* [Launchpad](https://help.launchpad.net/SSHFingerprints) -* [Savannah](http://savannah.gnu.org/maintenance/SshAccess/) -* [SourceForge](https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/) +- [AWS CodeCommit](http://docs.aws.amazon.com/codecommit/latest/userguide/regions.html#regions-fingerprints) +- [Bitbucket](https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints) +- [GitHub](https://help.github.com/articles/github-s-ssh-key-fingerprints/) +- [GitLab.com](https://about.gitlab.com/gitlab-com/settings/#ssh-host-keys-fingerprints) +- [Launchpad](https://help.launchpad.net/SSHFingerprints) +- [Savannah](http://savannah.gnu.org/maintenance/SshAccess/) +- [SourceForge](https://sourceforge.net/p/forge/documentation/SSH%20Key%20Fingerprints/) -Other providers will vary. If you're running on-premises GitLab, or otherwise +Other providers will vary. If you're running self-managed GitLab, or otherwise have access to the source server, you can securely gather the key fingerprints: -``` +```sh $ cat /etc/ssh/ssh_host*pub | ssh-keygen -E md5 -l -f - 256 MD5:f4:28:9f:23:99:15:21:1b:bf:ed:1f:8e:a0:76:b2:9d root@example.com (ECDSA) 256 MD5:e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73 root@example.com (ED25519) 2048 MD5:3f:72:be:3d:62:03:5c:62:83:e8:6e:14:34:3a:85:1d root@example.com (RSA) ``` -(You may need to exclude `-E md5` for some older versions of SSH). +NOTE: **Note:** +You may need to exclude `-E md5` for some older versions of SSH. -If you're an SSH expert and already have a `known_hosts` file you'd like to use -unaltered, then you can skip these steps. Just press the "Show advanced" button -and paste in the file contents: +When pulling changes from the source repository, GitLab will now check that at least one of the stored +host keys matches before connecting. This can prevent malicious code from being injected into your +mirror, or your password being stolen. -![Advanced SSH host key management](repository_mirroring/repository_mirroring_pull_advanced_host_keys.png) +### SSH public key authentication -Once you've **carefully verified** that all the fingerprints match your trusted -source, you can press `Save changes`. This will record the host keys, along with -the person who verified them (you!) and the date: +To use SSH public key authentication, you'll also need to choose that option from the **Authentication method** +dropdown. GitLab will generate a 4096-bit RSA key and display the public component of that key to you. -![SSH host keys submitted](repository_mirroring/repository_mirroring_ssh_host_keys_verified.png) +You then need to add the public SSH key to the source repository configuration. If: -When pulling changes from the source repository, GitLab will now check that at -least one of the stored host keys matches before connecting. This can prevent -malicious code from being injected into your mirror, or your password being -stolen! +- The source is hosted on GitLab, you should add the public SSH key as a [Deploy Key](../ssh/README.md#deploy-keys). +- The source is hosted elsewhere, you may need to add the key to your user's `authorized_keys` file. + Paste the entire public SSH key into the file on its own line and save it. -To use SSH public key authentication, you'll also need to choose that option -from the authentication methods dropdown. GitLab will generate a 4096-bit RSA -key and display the public component of that key to you: +Once the public key is set up on the source repository, click the **Mirror repository** button and +your mirror will begin working. -![SSH public key authentication](repository_mirroring/repository_mirroring_ssh_public_key_authentication.png) +If you need to change the key at any time, you can click the **Regenerate key** button to do so. You'll have to update the source repository with the new key to keep the mirror running. -You then need to add the public SSH key to the source repository configuration. -If the source is hosted on GitLab, you should add it as a [Deploy Key][deploy-key]. -Other sources may require you to add the key to your user's `authorized_keys` -file - just paste the entire `ssh-rsa AAA.... user@host` block into the file on -its own line and save it. - -Once the public key is set up on the source repository, press `Save changes` and your -mirror will begin working. - -If you need to change the key at any time, you can press the `Regenerate key` -button to do so. You'll have to update the source repository with the new key -to keep the mirror running. - -### How it works - -Once you activate the pull mirroring feature, the mirror will be inserted into -a queue. A scheduler will start every minute and schedule a fixed amount of -mirrors for update, based on the configured maximum capacity. - -If the mirror successfully updates it will be enqueued once again with a small -backoff period. - -If the mirror fails (eg: branch diverged from upstream), the project's backoff -period will be penalized each time it fails up to a maximum amount of time. - -## Pushing to a remote repository - ->[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in -GitLab Enterprise Edition 8.7. [Moved to GitLab Community Edition][ce-18715] in 10.8. - -For an existing project, you can set up push mirror from your project's -**Settings ➔ Repository** and searching for the "Push to a remote repository" -section. Check the "Remote mirror repository" box and fill in the Git URL of -the repository to push to. Click **Save changes** for the changes to take -effect. +### Overwrite diverged branches **[STARTER]** -![Push settings](repository_mirroring/repository_mirroring_push_settings.png) +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559) in +> [GitLab Starter](https://about.gitlab.com/pricing/) 10.6. -When push mirroring is enabled, you are advised not to push commits directly -to the mirrored repository to prevent the mirror diverging. -All changes will end up in the mirrored repository whenever commits -are pushed to GitLab, or when a [forced update](#forcing-an-update) is -initiated. +You can choose to always update your local branches with remote versions, even if they have +diverged from the remote. -Pushes into GitLab are automatically pushed to the remote mirror at least once -every 5 minutes after they are received or once every minute if **push only -protected branches** is enabled. +CAUTION: **Caution:** +For mirrored branches, enabling this option results in the loss of local changes. -In case of a diverged branch, you will see an error indicated at the **Mirror -repository** settings. +To use this option, check the **Overwrite diverged branches** box when creating a repository mirror. -![Diverged branch]( -repository_mirroring/repository_mirroring_diverged_branch_push.png) +### Only mirror protected branches **[STARTER]** -### Push only protected branches +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326) in +> [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. ->[Introduced][ee-3350] in GitLab Enterprise Edition 10.3. [Moved to GitLab Community Edition][ce-18715] in 10.8. +You can choose to pull mirror only the protected branches from your remote repository to GitLab. +Non-protected branches are not mirrored and can diverge. -You can choose to only push your protected branches from GitLab to your remote repository. +To use this option, check the **Only mirror protected branches** box when creating a repository mirror. -To use this option go to your project's repository settings page under push mirror. +### Hard failure **[STARTER]** -## Setting up a push mirror from GitLab to GitHub +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117) in +> [GitLab Starter](https://about.gitlab.com/pricing/) 10.2. -To set up a mirror from GitLab to GitHub, you need to follow these steps: +Once the mirroring process is unsuccessfully retried 14 times in a row, it will get marked as hard +failed. This will become visible in either the: -1. Create a [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with the "public_repo" box checked: +- Project's main dashboard. +- Pull mirror settings page. - ![edit personal access token GitHub](repository_mirroring/repository_mirroring_github_edit_personal_access_token.png) +When a project is hard failed, it will no longer get picked up for mirroring. A user can resume the +project mirroring again by [Forcing an update](#forcing-an-update). -1. Fill in the "Git repository URL" with the personal access token replacing the password `https://GitHubUsername:GitHubPersonalAccessToken@github.com/group/project.git`: +### Trigger update using API **[STARTER]** - ![push to remote repo](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png) +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453) in +[GitLab Starter](https://about.gitlab.com/pricing/) 10.3. -1. Save -1. And either wait or trigger the "Update Now" button: +Pull mirroring uses polling to detect new branches and commits added upstream, often minutes +afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project), +updates will be pulled immediately. + +For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project). + +## Forcing an update **[CORE]** + +While mirrors are scheduled to update automatically, you can always force an update by using the +update button which is available on the **Mirroring repositories** section of the **Repository Settings** page. + +![Repository mirroring force update user interface](img/repository_mirroring_force_update.png) + +## Bidirectional mirroring **[STARTER]** + +CAUTION: **Caution:** +Bidirectional mirroring may cause conflicts. + +If you configure a GitLab repository to both pull from, and push to, the same remote source, there +is no guarantee that either repository will update correctly. If you set up a repository for +bidirectional mirroring, you should prepare for the likely conflicts by deciding who will resolve +them and how they will be resolved. + +Rewriting any mirrored commit on either remote will cause conflicts and mirroring to fail. This can +be prevented by: + +- [Pulling only protected branches](#pull-only-protected-branches). +- [Pushing only protected branches](#push-only-protected-branches). + +You should [protect the branches](../user/project/protected_branches.md) you wish to mirror on both +remotes to prevent conflicts caused by rewriting history. + +Bidirectional mirroring also creates a race condition where commits made close together to the same +branch causes conflicts. The race condition can be mitigated by reducing the mirroring delay by using +a [Push event webhook](../user/project/integrations/webhooks.md#push-events) to trigger an immediate +pull to GitLab. Push mirroring from GitLab is rate limited to once per minute when only push mirroring +protected branches. + +### Preventing conflicts using a `pre-receive` hook + +> **Warning:** The solution proposed will negatively impact the performance of +> Git push operations because they will be proxied to the upstream Git +> repository. + +A server-side `pre-receive` hook can be used to prevent the race condition +described above by only accepting the push after first pushing the commit to +the upstream Git repository. In this configuration one Git repository acts as +the authoritative upstream, and the other as downstream. The `pre-receive` hook +will be installed on the downstream repository. + +Read about [configuring custom Git hooks](../administration/custom_hooks.md) on the GitLab server. + +A sample `pre-receive` hook is provided below. + +```bash +#!/usr/bin/env bash + +# --- Assume only one push mirror target +# Push mirroring remotes are named `remote_mirror_<id>`, this finds the first remote and uses that. +TARGET_REPO=$(git remote | grep -m 1 remote_mirror) + +proxy_push() +{ + # --- Arguments + OLDREV=$(git rev-parse $1) + NEWREV=$(git rev-parse $2) + REFNAME="$3" + + # --- Pattern of branches to proxy pushes + whitelisted=$(expr "$branch" : "\(master\)") + + case "$refname" in + refs/heads/*) + branch=$(expr "$refname" : "refs/heads/\(.*\)") + + if [ "$whitelisted" = "$branch" ]; then + error="$(git push --quiet $TARGET_REPO $NEWREV:$REFNAME 2>&1)" + fail=$? + + if [ "$fail" != "0" ]; then + echo >&2 "" + echo >&2 " Error: updates were rejected by upstream server" + echo >&2 " This is usually caused by another repository pushing changes" + echo >&2 " to the same ref. You may want to first integrate remote changes" + echo >&2 "" + return + fi + fi + ;; + esac +} + +# Allow dual mode: run from the command line just like the update hook, or +# if no arguments are given then run as a hook script +if [ -n "$1" -a -n "$2" -a -n "$3" ]; then + # Output to the terminal in command line mode - if someone wanted to + # resend an email; they could redirect the output to sendmail + # themselves + PAGER= proxy_push $2 $3 $1 +else + # Push is proxied upstream one ref at a time. Because of this it is possible + # for some refs to succeed, and others to fail. This will result in a failed + # push. + while read oldrev newrev refname + do + proxy_push $oldrev $newrev $refname + done +fi +``` - ![update now](repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png) +### Mirroring with Perforce Helix via Git Fusion **[STARTER]** -## Forcing an update +CAUTION: **Warning:** +Bidirectional mirroring should not be used as a permanent configuration. Refer to +[Migrating from Perforce Helix](../user/project/import/perforce.md) for alternative migration approaches. -While mirrors are scheduled to update automatically, you can always force an update -by using the **Update now** button which is exposed in various places: +[Git Fusion](https://www.perforce.com/video-tutorials/git-fusion-overview) provides a Git interface +to [Perforce Helix](https://www.perforce.com/products) which can be used by GitLab to bidirectionally +mirror projects with GitLab. This may be useful in some situations when migrating from Perforce Helix +to GitLab where overlapping Perforce Helix workspaces cannot be migrated simultaneously to GitLab. -- in the commits page -- in the branches page -- in the tags page -- in the **Mirror repository** settings page +If using mirroring with Perforce Helix, you should only mirror protected branches. Perforce Helix +will reject any pushes that rewrite history. Only the fewest number of branches should be mirrored +due to the performance limitations of Git Fusion. -## Bidirectional mirroring +When configuring mirroring with Perforce Helix via Git Fusion, the following Git Fusion +settings are recommended: -CAUTION: **Warning:** -There is no bidirectional support without conflicts. If you -configure a repository to pull and push to a second remote, there is no -guarantee that it will update correctly on both remotes. If you configure -a repository for bidirectional mirroring, you should consider when conflicts -occur who and how they will be resolved. - -Rewriting any mirrored commit on either remote will cause conflicts and -mirroring to fail. This can be prevented by [only pulling protected branches]( -#pull-only-protected-branches) and [only pushing protected branches]( -#push-only-protected-branches). You should protect the branches you wish to -mirror on both remotes to prevent conflicts caused by rewriting history. - -Bidirectional mirroring also creates a race condition where commits to the same -branch in close proximity will cause conflicts. The race condition can be -mitigated by reducing the mirroring delay by using a Push event webhook to -trigger an immediate pull to GitLab. Push mirroring from GitLab is rate limited -to once per minute when only push mirroring protected branches. - -It may be possible to implement a locking mechanism using the server-side -`pre-receive` hook to prevent the race condition. Read about [configuring -custom Git hooks][hooks] on the GitLab server. - -### Mirroring with Perforce via GitFusion +- `change-pusher` should be disabled. Otherwise, every commit will be rewritten as being committed + by the mirroring account, rather than being mapped to existing Perforce Helix users or the `unknown_git` user. +- `unknown_git` user will be used as the commit author if the GitLab user does not exist in + Perforce Helix. -CAUTION: **Warning:** -Bidirectional mirroring should not be used as a permanent -configuration. There is no bidirectional mirroring without conflicts. -Refer to [Migrating from Perforce Helix][perforce] for alternative migration -approaches. - -GitFusion provides a Git interface to Perforce which can be used by GitLab to -bidirectionally mirror projects with GitLab. This may be useful in some -situations when migrating from Perforce to GitLab where overlapping Perforce -workspaces cannot be migrated simultaneously to GitLab. - -If using mirroring with Perforce you should only mirror protected branches. -Perforce will reject any pushes that rewrite history. It is recommended that -only the fewest number of branches are mirrored due to the performance -limitations of GitFusion. - -[ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51 -[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551 -[ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117 -[ee-3326]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326 -[ee-3350]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350 -[ee-3453]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453 -[ee-4559]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559 -[ce-18715]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715 -[perms]: ../user/permissions.md -[hooks]: ../administration/custom_hooks.md -[deploy-key]: ../ssh/README.md#deploy-keys -[webhook]: ../user/project/integrations/webhooks.md#push-events -[pull-api]: ../api/projects.md#start-the-pull-mirroring-process-for-a-project -[perforce]: ../user/project/import/perforce.md +Read about [Git Fusion settings on Perforce.com](https://www.perforce.com/perforce/doc.current/manuals/git-fusion/Content/Git-Fusion/section_zdp_zz1_3l.html). diff --git a/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png b/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png Binary files differdeleted file mode 100644 index 2377a4a6516..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_detect_host_keys.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png b/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png Binary files differdeleted file mode 100644 index 45c9bce0889..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.png b/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.png Binary files differdeleted file mode 100644 index 786bd23eee6..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_diverged_branch_push.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.png b/doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.png Binary files differdeleted file mode 100644 index 139de42d8db..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_github_edit_personal_access_token.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png b/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png Binary files differdeleted file mode 100644 index ccbc1d92329..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png b/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png Binary files differdeleted file mode 100644 index b16b3d2828e..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_gitlab_push_to_a_remote_repository_update_now.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png Binary files differdeleted file mode 100644 index d8af5ce129e..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_main.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png Binary files differdeleted file mode 100644 index a10102e97ac..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_hard_failed_settings.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_new_project.png b/doc/workflow/repository_mirroring/repository_mirroring_new_project.png Binary files differdeleted file mode 100644 index 43bf304838f..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_new_project.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png Binary files differdeleted file mode 100644 index 1f1b3e1d5fb..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_pull_advanced_host_keys.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png Binary files differdeleted file mode 100644 index b8dfddb3d02..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png b/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png Binary files differdeleted file mode 100644 index 8f1de1d3003..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_pull_settings_for_ssh.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_push_settings.png b/doc/workflow/repository_mirroring/repository_mirroring_push_settings.png Binary files differdeleted file mode 100644 index f8199aa7c0f..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_push_settings.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png b/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png Binary files differdeleted file mode 100644 index 930d10a0822..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_ssh_host_keys_verified.png +++ /dev/null diff --git a/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png b/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png Binary files differdeleted file mode 100644 index adc1eedac44..00000000000 --- a/doc/workflow/repository_mirroring/repository_mirroring_ssh_public_key_authentication.png +++ /dev/null diff --git a/lib/api/applications.rb b/lib/api/applications.rb index f29cd7fc003..92717e04543 100644 --- a/lib/api/applications.rb +++ b/lib/api/applications.rb @@ -24,6 +24,22 @@ module API render_validation_error! application end end + + desc 'Get applications' do + success Entities::Application + end + get do + applications = ApplicationsFinder.new.execute + present applications, with: Entities::Application + end + + desc 'Delete an application' + delete ':id' do + application = ApplicationsFinder.new(params).execute + application.destroy + + status 204 + end end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5a4b85f98cf..18c30723d73 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -260,7 +260,7 @@ module API super(projects_relation).preload(:group) .preload(project_group_links: :group, fork_network: :root_project, - forked_project_link: :forked_from_project, + fork_network_member: :forked_from_project, forked_from_project: [:route, :forks, :tags, namespace: :route]) end # rubocop: enable CodeReuse/ActiveRecord @@ -1412,7 +1412,9 @@ module API end class Application < Grape::Entity + expose :id expose :uid, as: :application_id + expose :name, as: :application_name expose :redirect_uri, as: :callback_url end diff --git a/lib/api/features.rb b/lib/api/features.rb index 6f2422af13a..1331248699f 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -20,7 +20,7 @@ module API def gate_targets(params) targets = [] targets << Feature.group(params[:feature_group]) if params[:feature_group] - targets << User.find_by_username(params[:user]) if params[:user] + targets << UserFinder.new(params[:user]).find_by_username if params[:user] targets end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a7ba8066233..60bf977f0e4 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -96,15 +96,9 @@ module API LabelsFinder.new(current_user, search_params).execute end - # rubocop: disable CodeReuse/ActiveRecord def find_user(id) - if id =~ /^\d+$/ - User.find_by(id: id) - else - User.find_by(username: id) - end + UserFinder.new(id).find_by_id_or_username end - # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord def find_project(id) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 6a264c4cc6d..4dd6b19e353 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -40,7 +40,7 @@ module API elsif params[:user_id] User.find_by(id: params[:user_id]) elsif params[:username] - User.find_by_username(params[:username]) + UserFinder.new(params[:username]).find_by_username end protocol = params[:protocol] @@ -154,7 +154,7 @@ module API elsif params[:user_id] user = User.find_by(id: params[:user_id]) elsif params[:username] - user = User.find_by(username: params[:username]) + user = UserFinder.new(params[:username]).find_by_username end present user, with: Entities::UserSafe diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index fa992b9a440..697555c9605 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -151,7 +151,7 @@ module API present build, with: Entities::Job end - desc 'Trigger a actionable job (manual, scheduled, etc)' do + desc 'Trigger a actionable job (manual, delayed, etc)' do success Entities::Job detail 'This feature was added in GitLab 8.11' end diff --git a/lib/api/users.rb b/lib/api/users.rb index 501c5cf1df3..47382b09207 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -155,7 +155,6 @@ module API requires :username, type: String, desc: 'The username of the user' use :optional_attributes end - # rubocop: disable CodeReuse/ActiveRecord post do authenticated_as_admin! @@ -166,17 +165,16 @@ module API present user, with: Entities::UserPublic, current_user: current_user else conflict!('Email has already been taken') if User - .where(email: user.email) - .count > 0 + .by_any_email(user.email.downcase) + .any? conflict!('Username has already been taken') if User - .where(username: user.username) - .count > 0 + .by_username(user.username) + .any? render_validation_error!(user) end end - # rubocop: enable CodeReuse/ActiveRecord desc 'Update a user. Available only for admins.' do success Entities::UserPublic @@ -198,11 +196,11 @@ module API not_found!('User') unless user conflict!('Email has already been taken') if params[:email] && - User.where(email: params[:email]) + User.by_any_email(params[:email].downcase) .where.not(id: user.id).count > 0 conflict!('Username has already been taken') if params[:username] && - User.where(username: params[:username]) + User.by_username(params[:username]) .where.not(id: user.id).count > 0 user_params = declared_params(include_missing: false) diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index afdc6f383c1..a0434a66ef1 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -164,7 +164,7 @@ module Backup def tar_version tar_version, _ = Gitlab::Popen.popen(%w(tar --version)) - tar_version.force_encoding('locale').split("\n").first + tar_version.dup.force_encoding('locale').split("\n").first end def skipped?(item) diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index b170145f013..ec090aea784 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Gitlab::Access module # # Define allowed roles that can be used diff --git a/lib/gitlab/action_rate_limiter.rb b/lib/gitlab/action_rate_limiter.rb index 4cd3bdefda3..c442211e073 100644 --- a/lib/gitlab/action_rate_limiter.rb +++ b/lib/gitlab/action_rate_limiter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # This class implements a simple rate limiter that can be used to throttle # certain actions. Unlike Rack Attack and Rack::Throttle, which operate at diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb index 45c2b01dd8f..4518c8a862c 100644 --- a/lib/gitlab/allowable.rb +++ b/lib/gitlab/allowable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Allowable def can?(*args) diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb index dddcb2538f9..5edec8b3efe 100644 --- a/lib/gitlab/app_logger.rb +++ b/lib/gitlab/app_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class AppLogger < Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index 62c41801d75..df8f0470063 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'asciidoctor' require 'asciidoctor/converter/html5' require "asciidoctor-plantuml" diff --git a/lib/gitlab/audit_json_logger.rb b/lib/gitlab/audit_json_logger.rb new file mode 100644 index 00000000000..12e0645f3e4 --- /dev/null +++ b/lib/gitlab/audit_json_logger.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Gitlab + class AuditJsonLogger < Gitlab::JsonLogger + def self.file_name_noext + 'audit_json' + end + end +end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index a36d551d1d7..d2029a141e7 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Auth MissingPersonalAccessTokenError = Class.new(StandardError) diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb index 36c85dec544..d72befce571 100644 --- a/lib/gitlab/background_migration.rb +++ b/lib/gitlab/background_migration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module BackgroundMigration def self.queue diff --git a/lib/gitlab/base_doorkeeper_controller.rb b/lib/gitlab/base_doorkeeper_controller.rb index e4227af25d2..b78993aba30 100644 --- a/lib/gitlab/base_doorkeeper_controller.rb +++ b/lib/gitlab/base_doorkeeper_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This is a base controller for doorkeeper. # It adds the `can?` helper used in the views. module Gitlab diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb index 169aac79854..0d79594363e 100644 --- a/lib/gitlab/blame.rb +++ b/lib/gitlab/blame.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Blame attr_accessor :blob, :commit diff --git a/lib/gitlab/blob_helper.rb b/lib/gitlab/blob_helper.rb index 9b3b383b0c8..488c1d85387 100644 --- a/lib/gitlab/blob_helper.rb +++ b/lib/gitlab/blob_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This has been extracted from https://github.com/github/linguist/blob/master/lib/linguist/blob_helper.rb module Gitlab module BlobHelper diff --git a/lib/gitlab/build_access.rb b/lib/gitlab/build_access.rb index 08a8f846ca5..37e79413541 100644 --- a/lib/gitlab/build_access.rb +++ b/lib/gitlab/build_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class BuildAccess < UserAccess attr_accessor :user, :project diff --git a/lib/gitlab/changes_list.rb b/lib/gitlab/changes_list.rb index 9c9e6668e6f..fb75a78a978 100644 --- a/lib/gitlab/changes_list.rb +++ b/lib/gitlab/changes_list.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ChangesList include Enumerable diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb index e63e5437331..8b3c5dc9e8b 100644 --- a/lib/gitlab/chat_name_token.rb +++ b/lib/gitlab/chat_name_token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' module Gitlab diff --git a/lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb b/lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb new file mode 100644 index 00000000000..ee3647f24fd --- /dev/null +++ b/lib/gitlab/ci/build/artifacts/adapters/gzip_stream.rb @@ -0,0 +1,48 @@ +module Gitlab + module Ci + module Build + module Artifacts + module Adapters + class GzipStream + attr_reader :stream + + InvalidStreamError = Class.new(StandardError) + + def initialize(stream) + raise InvalidStreamError, "Stream is required" unless stream + + @stream = stream + end + + def each_blob + stream.seek(0) + + until stream.eof? + gzip(stream) do |gz| + yield gz.read, gz.orig_name + unused = gz.unused&.length.to_i + # pos has already reached to EOF at the moment + # We rewind the pos to the top of unused files + # to read next gzip stream, to support multistream archives + # https://golang.org/src/compress/gzip/gunzip.go#L117 + stream.seek(-unused, IO::SEEK_CUR) + end + end + end + + private + + def gzip(stream, &block) + gz = Zlib::GzipReader.new(stream) + yield(gz) + rescue Zlib::Error => e + raise InvalidStreamError, e.message + ensure + gz&.finish + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb b/lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb new file mode 100644 index 00000000000..fa6842cf36a --- /dev/null +++ b/lib/gitlab/ci/build/artifacts/adapters/raw_stream.rb @@ -0,0 +1,27 @@ +module Gitlab + module Ci + module Build + module Artifacts + module Adapters + class RawStream + attr_reader :stream + + InvalidStreamError = Class.new(StandardError) + + def initialize(stream) + raise InvalidStreamError, "Stream is required" unless stream + + @stream = stream + end + + def each_blob + stream.seek(0) + + yield(stream.read, 'raw') unless stream.eof? + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb b/lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb deleted file mode 100644 index 65f65cdce08..00000000000 --- a/lib/gitlab/ci/build/artifacts/gzip_file_adapter.rb +++ /dev/null @@ -1,46 +0,0 @@ -module Gitlab - module Ci - module Build - module Artifacts - class GzipFileAdapter - attr_reader :stream - - InvalidStreamError = Class.new(StandardError) - - def initialize(stream) - raise InvalidStreamError, "Stream is required" unless stream - - @stream = stream - end - - def each_blob - stream.seek(0) - - until stream.eof? - gzip(stream) do |gz| - yield gz.read, gz.orig_name - unused = gz.unused&.length.to_i - # pos has already reached to EOF at the moment - # We rewind the pos to the top of unused files - # to read next gzip stream, to support multistream archives - # https://golang.org/src/compress/gzip/gunzip.go#L117 - stream.seek(-unused, IO::SEEK_CUR) - end - end - end - - private - - def gzip(stream, &block) - gz = Zlib::GzipReader.new(stream) - yield(gz) - rescue Zlib::Error => e - raise InvalidStreamError, e.message - ensure - gz&.finish - end - end - end - end - end -end diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb index 375d8bc1ff5..551d4f4473e 100644 --- a/lib/gitlab/ci/build/artifacts/metadata.rb +++ b/lib/gitlab/ci/build/artifacts/metadata.rb @@ -59,9 +59,12 @@ module Gitlab until gz.eof? begin - path = read_string(gz).force_encoding('UTF-8') - meta = read_string(gz).force_encoding('UTF-8') + path = read_string(gz)&.force_encoding('UTF-8') + meta = read_string(gz)&.force_encoding('UTF-8') + # We might hit an EOF while reading either value, so we should + # abort if we don't get any data. + next unless path && meta next unless path.valid_encoding? && meta.valid_encoding? next unless path =~ match_pattern next if path =~ INVALID_PATH_PATTERN diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index fe98d25af29..fedaf18ef30 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -13,10 +13,10 @@ module Gitlab @global = Entry::Global.new(@config) @global.compose! - rescue Loader::FormatError, Extendable::ExtensionError => e + rescue Loader::FormatError, + Extendable::ExtensionError, + External::Processor::IncludeError => e raise Config::ConfigError, e.message - rescue ::Gitlab::Ci::External::Processor::FileError => e - raise ::Gitlab::Ci::YamlProcessor::ValidationError, e.message end def valid? @@ -81,7 +81,7 @@ module Gitlab def process_external_files(config, project, opts) sha = opts.fetch(:sha) { project.repository.root_ref_sha } - ::Gitlab::Ci::External::Processor.new(config, project, sha).perform + Config::External::Processor.new(config, project, sha).perform end end end diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb index 98f12c226b3..3ac2a6fa777 100644 --- a/lib/gitlab/ci/config/entry/reports.rb +++ b/lib/gitlab/ci/config/entry/reports.rb @@ -11,7 +11,7 @@ module Gitlab include Validatable include Attributable - ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast].freeze + ALLOWED_KEYS = %i[junit codequality sast dependency_scanning container_scanning dast performance license_management].freeze attributes ALLOWED_KEYS @@ -26,6 +26,8 @@ module Gitlab validates :dependency_scanning, array_of_strings_or_string: true validates :container_scanning, array_of_strings_or_string: true validates :dast, array_of_strings_or_string: true + validates :performance, array_of_strings_or_string: true + validates :license_management, array_of_strings_or_string: true end end diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb new file mode 100644 index 00000000000..15ca47ef60e --- /dev/null +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module External + module File + class Base + include Gitlab::Utils::StrongMemoize + + attr_reader :location, :opts, :errors + + YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze + + def initialize(location, opts = {}) + @location = location + @opts = opts + @errors = [] + + validate! + end + + def invalid_extension? + !::File.basename(location).match(YAML_WHITELIST_EXTENSION) + end + + def valid? + errors.none? + end + + def error_message + errors.first + end + + def content + raise NotImplementedError, 'subclass must implement fetching raw content' + end + + def to_hash + @hash ||= Ci::Config::Loader.new(content).load! + rescue Ci::Config::Loader::FormatError + nil + end + + protected + + def validate! + validate_location! + validate_content! if errors.none? + validate_hash! if errors.none? + end + + def validate_location! + if invalid_extension? + errors.push("Included file `#{location}` does not have YAML extension!") + end + end + + def validate_content! + if content.blank? + errors.push("Included file `#{location}` is empty or does not exist!") + end + end + + def validate_hash! + if to_hash.blank? + errors.push("Included file `#{location}` does not have valid YAML syntax!") + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/external/file/local.rb b/lib/gitlab/ci/config/external/file/local.rb new file mode 100644 index 00000000000..2a256aff65c --- /dev/null +++ b/lib/gitlab/ci/config/external/file/local.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module External + module File + class Local < Base + include Gitlab::Utils::StrongMemoize + + attr_reader :project, :sha + + def initialize(location, opts = {}) + @project = opts.fetch(:project) + @sha = opts.fetch(:sha) + + super + end + + def content + strong_memoize(:content) { fetch_local_content } + end + + private + + def validate_content! + if content.nil? + errors.push("Local file `#{location}` does not exist!") + elsif content.blank? + errors.push("Local file `#{location}` is empty!") + end + end + + def fetch_local_content + project.repository.blob_data_at(sha, location) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/external/file/remote.rb b/lib/gitlab/ci/config/external/file/remote.rb new file mode 100644 index 00000000000..86fa5ad8800 --- /dev/null +++ b/lib/gitlab/ci/config/external/file/remote.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module External + module File + class Remote < Base + include Gitlab::Utils::StrongMemoize + + def content + strong_memoize(:content) { fetch_remote_content } + end + + private + + def validate_location! + super + + unless ::Gitlab::UrlSanitizer.valid?(location) + errors.push("Remote file `#{location}` does not have a valid address!") + end + end + + def fetch_remote_content + begin + response = Gitlab::HTTP.get(location) + rescue SocketError + errors.push("Remote file `#{location}` could not be fetched because of a socket error!") + rescue Timeout::Error + errors.push("Remote file `#{location}` could not be fetched because of a timeout error!") + rescue Gitlab::HTTP::Error + errors.push("Remote file `#{location}` could not be fetched because of HTTP error!") + rescue Gitlab::HTTP::BlockedUrlError => e + errors.push("Remote file could not be fetched because #{e}!") + end + + if response&.code.to_i >= 400 + errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!") + end + + response.to_s if errors.none? + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb new file mode 100644 index 00000000000..def3563e505 --- /dev/null +++ b/lib/gitlab/ci/config/external/mapper.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module External + class Mapper + def initialize(values, project, sha) + @locations = Array(values.fetch(:include, [])) + @project = project + @sha = sha + end + + def process + locations.map { |location| build_external_file(location) } + end + + private + + attr_reader :locations, :project, :sha + + def build_external_file(location) + if ::Gitlab::UrlSanitizer.valid?(location) + External::File::Remote.new(location) + else + External::File::Local.new(location, project: project, sha: sha) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb new file mode 100644 index 00000000000..eae0bdeb644 --- /dev/null +++ b/lib/gitlab/ci/config/external/processor.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module External + class Processor + IncludeError = Class.new(StandardError) + + def initialize(values, project, sha) + @values = values + @external_files = External::Mapper.new(values, project, sha).process + @content = {} + end + + def perform + return @values if @external_files.empty? + + validate_external_files! + merge_external_files! + append_inline_content! + remove_include_keyword! + end + + private + + def validate_external_files! + @external_files.each do |file| + raise IncludeError, file.error_message unless file.valid? + end + end + + def merge_external_files! + @external_files.each do |file| + @content.deep_merge!(file.to_hash) + end + end + + def append_inline_content! + @content.deep_merge!(@values) + end + + def remove_include_keyword! + @content.tap { @content.delete(:include) } + end + end + end + end + end +end diff --git a/lib/gitlab/ci/external/file/base.rb b/lib/gitlab/ci/external/file/base.rb deleted file mode 100644 index f4da07b0b02..00000000000 --- a/lib/gitlab/ci/external/file/base.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module External - module File - class Base - YAML_WHITELIST_EXTENSION = /(yml|yaml)$/i.freeze - - def initialize(location, opts = {}) - @location = location - end - - def valid? - location.match(YAML_WHITELIST_EXTENSION) && content - end - - def content - raise NotImplementedError, 'content must be implemented and return a string or nil' - end - - def error_message - raise NotImplementedError, 'error_message must be implemented and return a string' - end - end - end - end - end -end diff --git a/lib/gitlab/ci/external/file/local.rb b/lib/gitlab/ci/external/file/local.rb deleted file mode 100644 index 1aa7f687507..00000000000 --- a/lib/gitlab/ci/external/file/local.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module External - module File - class Local < Base - attr_reader :location, :project, :sha - - def initialize(location, opts = {}) - super - - @project = opts.fetch(:project) - @sha = opts.fetch(:sha) - end - - def content - @content ||= fetch_local_content - end - - def error_message - "Local file '#{location}' is not valid." - end - - private - - def fetch_local_content - project.repository.blob_data_at(sha, location) - end - end - end - end - end -end diff --git a/lib/gitlab/ci/external/file/remote.rb b/lib/gitlab/ci/external/file/remote.rb deleted file mode 100644 index 59bb3e8999e..00000000000 --- a/lib/gitlab/ci/external/file/remote.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module External - module File - class Remote < Base - include Gitlab::Utils::StrongMemoize - attr_reader :location - - def content - return @content if defined?(@content) - - @content = strong_memoize(:content) do - begin - Gitlab::HTTP.get(location) - rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Gitlab::HTTP::BlockedUrlError - nil - end - end - end - - def error_message - "Remote file '#{location}' is not valid." - end - end - end - end - end -end diff --git a/lib/gitlab/ci/external/mapper.rb b/lib/gitlab/ci/external/mapper.rb deleted file mode 100644 index 58bd6a19acf..00000000000 --- a/lib/gitlab/ci/external/mapper.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module External - class Mapper - def initialize(values, project, sha) - @locations = Array(values.fetch(:include, [])) - @project = project - @sha = sha - end - - def process - locations.map { |location| build_external_file(location) } - end - - private - - attr_reader :locations, :project, :sha - - def build_external_file(location) - if ::Gitlab::UrlSanitizer.valid?(location) - Gitlab::Ci::External::File::Remote.new(location) - else - options = { project: project, sha: sha } - Gitlab::Ci::External::File::Local.new(location, options) - end - end - end - end - end -end diff --git a/lib/gitlab/ci/external/processor.rb b/lib/gitlab/ci/external/processor.rb deleted file mode 100644 index 76cf3ce89f9..00000000000 --- a/lib/gitlab/ci/external/processor.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module External - class Processor - FileError = Class.new(StandardError) - - def initialize(values, project, sha) - @values = values - @external_files = Gitlab::Ci::External::Mapper.new(values, project, sha).process - @content = {} - end - - def perform - return values if external_files.empty? - - external_files.each do |external_file| - validate_external_file(external_file) - @content.deep_merge!(content_of(external_file)) - end - - append_inline_content - remove_include_keyword - end - - private - - attr_reader :values, :external_files, :content - - def validate_external_file(external_file) - unless external_file.valid? - raise FileError, external_file.error_message - end - end - - def content_of(external_file) - Gitlab::Ci::Config::Loader.new(external_file.content).load! - end - - def append_inline_content - @content.deep_merge!(@values) - end - - def remove_include_keyword - content.delete(:include) - content - end - end - end - end -end diff --git a/lib/gitlab/ci/status/build/scheduled.rb b/lib/gitlab/ci/status/build/scheduled.rb index eebb3f761c5..62ad9083616 100644 --- a/lib/gitlab/ci/status/build/scheduled.rb +++ b/lib/gitlab/ci/status/build/scheduled.rb @@ -7,7 +7,7 @@ module Gitlab { image: 'illustrations/illustrations_scheduled-job_countdown.svg', size: 'svg-394', - title: _("This is a scheduled to run in ") + " #{execute_in}", + title: _("This is a delayed to run in ") + " #{execute_in}", content: _("This job will automatically run after it's timer finishes. " \ "Often they are used for incremental roll-out deploys " \ "to production environments. When unscheduled it converts " \ @@ -16,7 +16,7 @@ module Gitlab end def status_tooltip - "scheduled manual action (#{execute_in})" + "delayed manual action (#{execute_in})" end def self.matches?(build, user) @@ -29,7 +29,7 @@ module Gitlab def execute_in remaining_seconds = [0, subject.scheduled_at - Time.now].max - duration_in_numbers(remaining_seconds, true) + duration_in_numbers(remaining_seconds) end end end diff --git a/lib/gitlab/ci/status/pipeline/scheduled.rb b/lib/gitlab/ci/status/pipeline/delayed.rb index 9ec6994bd2f..12736861c89 100644 --- a/lib/gitlab/ci/status/pipeline/scheduled.rb +++ b/lib/gitlab/ci/status/pipeline/delayed.rb @@ -2,9 +2,9 @@ module Gitlab module Ci module Status module Pipeline - class Scheduled < Status::Extended + class Delayed < Status::Extended def text - s_('CiStatusText|scheduled') + s_('CiStatusText|delayed') end def label diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb index 00d8f01cbdc..0adf83fa197 100644 --- a/lib/gitlab/ci/status/pipeline/factory.rb +++ b/lib/gitlab/ci/status/pipeline/factory.rb @@ -5,7 +5,7 @@ module Gitlab class Factory < Status::Factory def self.extended_statuses [[Status::SuccessWarning, - Status::Pipeline::Scheduled, + Status::Pipeline::Delayed, Status::Pipeline::Blocked]] end diff --git a/lib/gitlab/ci/status/scheduled.rb b/lib/gitlab/ci/status/scheduled.rb index 542100e41da..3adcfa36af2 100644 --- a/lib/gitlab/ci/status/scheduled.rb +++ b/lib/gitlab/ci/status/scheduled.rb @@ -3,11 +3,11 @@ module Gitlab module Status class Scheduled < Status::Core def text - s_('CiStatusText|scheduled') + s_('CiStatusText|delayed') end def label - s_('CiStatusLabel|scheduled') + s_('CiStatusLabel|delayed') end def icon diff --git a/lib/gitlab/ci_access.rb b/lib/gitlab/ci_access.rb index def1373d8cf..d5d3eb804ae 100644 --- a/lib/gitlab/ci_access.rb +++ b/lib/gitlab/ci_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # For backwards compatibility, generic CI (which is a build without a user) is # allowed to :build_download_code without any other checks. diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index 7e7aaeeaa17..4ba921569ad 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ClosingIssueExtractor ISSUE_CLOSING_REGEX = begin diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb index 9c4664df903..a5e4065cf09 100644 --- a/lib/gitlab/color_schemes.rb +++ b/lib/gitlab/color_schemes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Module containing GitLab's syntax color scheme definitions and helper # methods for accessing them. diff --git a/lib/gitlab/config_helper.rb b/lib/gitlab/config_helper.rb index 41880069e4c..b7aa03384b7 100644 --- a/lib/gitlab/config_helper.rb +++ b/lib/gitlab/config_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab::ConfigHelper def gitlab_config_features Gitlab.config.gitlab.default_projects_features diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 1ffc2639237..c819bffdfd6 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ContributionsCalendar attr_reader :contributor diff --git a/lib/gitlab/contributor.rb b/lib/gitlab/contributor.rb index c41e92b620f..d74d5a86aa0 100644 --- a/lib/gitlab/contributor.rb +++ b/lib/gitlab/contributor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Contributor attr_accessor :email, :name, :commits, :additions, :deletions diff --git a/lib/gitlab/cross_project_access.rb b/lib/gitlab/cross_project_access.rb index 6eaed51b64c..4ddc7e02d1b 100644 --- a/lib/gitlab/cross_project_access.rb +++ b/lib/gitlab/cross_project_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class CrossProjectAccess class << self diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index de7c959e706..477f9101e98 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CurrentSettings class << self diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb index bd14c7eece3..6d5fc4219fb 100644 --- a/lib/gitlab/daemon.rb +++ b/lib/gitlab/daemon.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Daemon def self.initialize_instance(*args) diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 42f9605f5ac..68ed53cf64a 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database # The max value of INTEGER type is the same between MySQL and PostgreSQL: @@ -99,11 +101,11 @@ module Gitlab order = "#{field} #{direction}" if postgresql? - order << ' NULLS LAST' + order = "#{order} NULLS LAST" else # `field IS NULL` will be `0` for non-NULL columns and `1` for NULL # columns. In the (default) ascending order, `0` comes first. - order.prepend("#{field} IS NULL, ") if direction == 'ASC' + order = "#{field} IS NULL, #{order}" if direction == 'ASC' end order @@ -113,11 +115,11 @@ module Gitlab order = "#{field} #{direction}" if postgresql? - order << ' NULLS FIRST' + order = "#{order} NULLS FIRST" else # `field IS NULL` will be `0` for non-NULL columns and `1` for NULL # columns. In the (default) ascending order, `0` comes first. - order.prepend("#{field} IS NULL, ") if direction == 'DESC' + order = "#{field} IS NULL, #{order}" if direction == 'DESC' end order @@ -184,7 +186,7 @@ module Gitlab EOF if return_ids - sql << 'RETURNING id' + sql = "#{sql}RETURNING id" end result = connection.execute(sql) diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 30541ee3553..a17f27a3147 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -937,7 +937,7 @@ database (#{dbname}) using a super user and running: For MySQL you instead need to run: - GRANT ALL PRIVILEGES ON *.* TO #{user}@'%' + GRANT ALL PRIVILEGES ON #{dbname}.* TO #{user}@'%' Both queries will grant the user super user permissions, ensuring you don't run into similar problems in the future (e.g. when new tables are created). diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb index 3192bf6f667..c63d9e5bb71 100644 --- a/lib/gitlab/dependency_linker.rb +++ b/lib/gitlab/dependency_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker LINKERS = [ diff --git a/lib/gitlab/downtime_check.rb b/lib/gitlab/downtime_check.rb index 941244694e2..31bb6810391 100644 --- a/lib/gitlab/downtime_check.rb +++ b/lib/gitlab/downtime_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Checks if a set of migrations requires downtime or not. class DowntimeCheck diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index ee604e66154..5d9ecd651a0 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop: disable Rails/Output module Gitlab # Checks if a set of migrations requires downtime or not. @@ -284,7 +286,7 @@ module Gitlab end def patch_name_from_branch(branch_name) - branch_name.parameterize << '.patch' + "#{branch_name.parameterize}.patch" end def patch_url @@ -432,9 +434,11 @@ module Gitlab end def conflicting_files_msg - failed_files.reduce("The conflicts detected were as follows:\n") do |memo, file| - memo << "\n - #{file}" - end + header = "The conflicts detected were as follows:\n" + separator = "\n - " + failed_items = failed_files.join(separator) + + "#{header}#{separator}#{failed_items}" end end end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index 89cf659bce4..ce1dfb0753c 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Emoji extend self diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb index 0f336fbaa10..a4a154c80f7 100644 --- a/lib/gitlab/encoding_helper.rb +++ b/lib/gitlab/encoding_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module EncodingHelper extend self diff --git a/lib/gitlab/environment.rb b/lib/gitlab/environment.rb index 5e0dd6e7859..b1a9603d3a5 100644 --- a/lib/gitlab/environment.rb +++ b/lib/gitlab/environment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Environment def self.hostname diff --git a/lib/gitlab/environment_logger.rb b/lib/gitlab/environment_logger.rb index 407cc572656..862a516ca71 100644 --- a/lib/gitlab/environment_logger.rb +++ b/lib/gitlab/environment_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class EnvironmentLogger < Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb index 12b5e240962..d466d2a514c 100644 --- a/lib/gitlab/exclusive_lease.rb +++ b/lib/gitlab/exclusive_lease.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' module Gitlab diff --git a/lib/gitlab/exclusive_lease_helpers.rb b/lib/gitlab/exclusive_lease_helpers.rb index e998548cff9..4aaf2474763 100644 --- a/lib/gitlab/exclusive_lease_helpers.rb +++ b/lib/gitlab/exclusive_lease_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # This module provides helper methods which are intregrated with GitLab::ExclusiveLease module ExclusiveLeaseHelpers diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb index 2c827265d8c..db1aeeea8d3 100644 --- a/lib/gitlab/fake_application_settings.rb +++ b/lib/gitlab/fake_application_settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This class extends an OpenStruct object by adding predicate methods to mimic # ActiveRecord access. We rely on the initial values being true or false to # determine whether to define a predicate method because for a newly-added diff --git a/lib/gitlab/favicon.rb b/lib/gitlab/favicon.rb index 050a1ad3a0b..1ae2f9dfd93 100644 --- a/lib/gitlab/favicon.rb +++ b/lib/gitlab/favicon.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Favicon class << self diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index da62ed2fb16..4d89ee5a669 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' module Gitlab diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb index af8270c8db8..b4db3f93c9c 100644 --- a/lib/gitlab/file_finder.rb +++ b/lib/gitlab/file_finder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This class finds files in a repository by name and content # the result is joined and sorted by file name module Gitlab diff --git a/lib/gitlab/file_markdown_link_builder.rb b/lib/gitlab/file_markdown_link_builder.rb index 5386656efe7..180140e7da2 100644 --- a/lib/gitlab/file_markdown_link_builder.rb +++ b/lib/gitlab/file_markdown_link_builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Builds the markdown link of a file # It needs the methods filename and secure_url (final destination url) to be defined. module Gitlab @@ -8,7 +10,7 @@ module Gitlab return unless name = markdown_name markdown = "[#{name.gsub(']', '\\]')}](#{secure_url})" - markdown.prepend("!") if image_or_video? || dangerous? + markdown = "!#{markdown}" if image_or_video? || dangerous? markdown end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 2913a3e416d..c4aac228b2f 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_dependency 'gitlab/encoding_helper' module Gitlab diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 240a0d7d1b8..827c04ae035 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Check a user's access to perform a git action. All public methods in this # class return an instance of `GitlabAccessStatus` module Gitlab diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index a5b3902ebf4..3f24001e4ee 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class GitAccessWiki < GitAccess ERROR_MESSAGES = { diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb index 9e02ccc0f44..dac4ddd320f 100644 --- a/lib/gitlab/git_logger.rb +++ b/lib/gitlab/git_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class GitLogger < Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb index 40636fb204e..a90b69ff42b 100644 --- a/lib/gitlab/git_ref_validator.rb +++ b/lib/gitlab/git_ref_validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Gitaly note: JV: does not need to be migrated, works without a repo. module Gitlab diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 4ec87f6a3e7..d99a9f15371 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'base64' require 'gitaly' @@ -23,7 +25,7 @@ module Gitlab stacks = most_invoked_stack.join('\n') if most_invoked_stack msg = "GitalyClient##{call_site} called #{invocation_count} times from single request. Potential n+1?" - msg << "\nThe following call site called into Gitaly #{max_call_stack} times:\n#{stacks}\n" if stacks + msg = "#{msg}\nThe following call site called into Gitaly #{max_call_stack} times:\n#{stacks}\n" if stacks super(msg) end diff --git a/lib/gitlab/github_import.rb b/lib/gitlab/github_import.rb index d40b06f969f..14a6d6443ec 100644 --- a/lib/gitlab/github_import.rb +++ b/lib/gitlab/github_import.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module GithubImport def self.refmap diff --git a/lib/gitlab/gl_id.rb b/lib/gitlab/gl_id.rb index a53d156b41f..1ed842c2264 100644 --- a/lib/gitlab/gl_id.rb +++ b/lib/gitlab/gl_id.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module GlId def self.gl_id(user) diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb index b54e45de4fe..435b74806e7 100644 --- a/lib/gitlab/gl_repository.rb +++ b/lib/gitlab/gl_repository.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module GlRepository def self.gl_repository(project, is_wiki) diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index c1726659a90..860c39feb64 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop:disable Metrics/AbcSize module Gitlab diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 94c15739231..0c08c0fedaa 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -102,7 +102,7 @@ module Gitlab if username.start_with?("@") username = username[1..-1] - if user = User.find_by(username: username) + if user = UserFinder.new(username).find_by_username assignee_id = user.id end end diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb index 8a91e034377..e53c2d00743 100644 --- a/lib/gitlab/gpg.rb +++ b/lib/gitlab/gpg.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Gpg extend self diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb index 04a89432230..74c04e5380e 100644 --- a/lib/gitlab/graphql.rb +++ b/lib/gitlab/graphql.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Graphql StandardGraphqlError = Class.new(StandardError) diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/group_hierarchy.rb index 8fbfa1a86bf..c940ea7305e 100644 --- a/lib/gitlab/group_hierarchy.rb +++ b/lib/gitlab/group_hierarchy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Retrieving of parent or child groups based on a base ActiveRecord relation. # diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 0b6cc893db1..83095acc528 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Highlight TIMEOUT_BACKGROUND = 30.seconds diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb index 9aca3b0fb26..bcd9e2be35f 100644 --- a/lib/gitlab/http.rb +++ b/lib/gitlab/http.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This class is used as a proxy for all outbounding http connection # coming from callbacks, services and hooks. The direct use of the HTTParty # is discouraged because it can lead to several security problems, like SSRF @@ -5,9 +7,16 @@ module Gitlab class HTTP BlockedUrlError = Class.new(StandardError) + RedirectionTooDeep = Class.new(StandardError) include HTTParty # rubocop:disable Gitlab/HTTParty connection_adapter ProxyHTTPConnectionAdapter + + def self.perform_request(http_method, path, options, &block) + super + rescue HTTParty::RedirectionTooDeep + raise RedirectionTooDeep + end end end diff --git a/lib/gitlab/http_io.rb b/lib/gitlab/http_io.rb index ce24817db54..9d7763fc5ac 100644 --- a/lib/gitlab/http_io.rb +++ b/lib/gitlab/http_io.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # This class is compatible with IO class (https://ruby-doc.org/core-2.3.1/IO.html) # source: https://gitlab.com/snippets/1685610 @@ -73,8 +75,8 @@ module Gitlab end end - def read(length = nil, outbuf = "") - out = "" + def read(length = nil, outbuf = nil) + out = [] length ||= size - tell @@ -90,17 +92,18 @@ module Gitlab length -= chunk_data.bytesize end + out = out.join + # If outbuf is passed, we put the output into the buffer. This supports IO.copy_stream functionality if outbuf - outbuf.slice!(0, outbuf.bytesize) - outbuf << out + outbuf.replace(out) end out end def readline - out = "" + out = [] until eof? data = get_chunk @@ -116,7 +119,7 @@ module Gitlab end end - out + out.join end def write(data) diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 7346eab9e76..7e0398f09af 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module I18n extend self diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index a8b93f1d4b2..28a9fe29465 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Detect user based on identifier like # key-13 or user-36 or last commit module Gitlab diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 53fe2f8e436..f63a5ece71e 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module ImportExport extend self diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb index 4e611e7f16c..d4ba4d1181d 100644 --- a/lib/gitlab/import_formatter.rb +++ b/lib/gitlab/import_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ImportFormatter def comment(author, date, body) diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index f7f5c5787f6..f46bb837cf7 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Gitlab::ImportSources module # # Define import sources that can be used diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index d323cb9dadf..20fc8226611 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module IncomingEmail UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze diff --git a/lib/gitlab/insecure_key_fingerprint.rb b/lib/gitlab/insecure_key_fingerprint.rb index f85b6e9197f..e4f0e9d2c73 100644 --- a/lib/gitlab/insecure_key_fingerprint.rb +++ b/lib/gitlab/insecure_key_fingerprint.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # # Calculates the fingerprint of a given key without using diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb index 0c9de72329c..351d15605e0 100644 --- a/lib/gitlab/issuable_metadata.rb +++ b/lib/gitlab/issuable_metadata.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module IssuableMetadata def issuable_meta_data(issuable_collection, collection_type) diff --git a/lib/gitlab/issuable_sorter.rb b/lib/gitlab/issuable_sorter.rb index d392214867a..42bbfb32d0b 100644 --- a/lib/gitlab/issuable_sorter.rb +++ b/lib/gitlab/issuable_sorter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module IssuableSorter class << self diff --git a/lib/gitlab/issuables_count_for_state.rb b/lib/gitlab/issuables_count_for_state.rb index b5657a36998..659fb1472d2 100644 --- a/lib/gitlab/issuables_count_for_state.rb +++ b/lib/gitlab/issuables_count_for_state.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Class for counting and caching the number of issuables per state. class IssuablesCountForState diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index b8ca7f2f55f..17c9cb969df 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class IssuesLabels class << self diff --git a/lib/gitlab/job_waiter.rb b/lib/gitlab/job_waiter.rb index f7a8eae0be4..e97e961771c 100644 --- a/lib/gitlab/job_waiter.rb +++ b/lib/gitlab/job_waiter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # JobWaiter can be used to wait for a number of Sidekiq jobs to complete. # diff --git a/lib/gitlab/json_logger.rb b/lib/gitlab/json_logger.rb index 28e258196ca..3bff77731f6 100644 --- a/lib/gitlab/json_logger.rb +++ b/lib/gitlab/json_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class JsonLogger < ::Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index 15c5ece2350..3748fd6b5ef 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Helper methods to do with Kubernetes network services & resources module Kubernetes diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb index 588238de608..e88a15b8acd 100644 --- a/lib/gitlab/kubernetes/kube_client.rb +++ b/lib/gitlab/kubernetes/kube_client.rb @@ -45,6 +45,13 @@ module Gitlab :update_cluster_role_binding, to: :rbac_client + # RBAC methods delegates to the apis/rbac.authorization.k8s.io api + # group client + delegate :create_role_binding, + :get_role_binding, + :update_role_binding, + to: :rbac_client + # Deployments resource is currently on the apis/extensions api group delegate :get_deployments, to: :extensions_client diff --git a/lib/gitlab/kubernetes/role_binding.rb b/lib/gitlab/kubernetes/role_binding.rb new file mode 100644 index 00000000000..4f3ee040bf2 --- /dev/null +++ b/lib/gitlab/kubernetes/role_binding.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Gitlab + module Kubernetes + class RoleBinding + attr_reader :role_name, :namespace, :service_account_name + + def initialize(role_name:, namespace:, service_account_name:) + @role_name = role_name + @namespace = namespace + @service_account_name = service_account_name + end + + def generate + ::Kubeclient::Resource.new.tap do |resource| + resource.metadata = metadata + resource.roleRef = role_ref + resource.subjects = subjects + end + end + + private + + def metadata + { name: "gitlab-#{namespace}", namespace: namespace } + end + + def role_ref + { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'Role', + name: role_name + } + end + + def subjects + [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + end + end + end +end diff --git a/lib/gitlab/language_detection.rb b/lib/gitlab/language_detection.rb index a41435fdb79..7600e60b904 100644 --- a/lib/gitlab/language_detection.rb +++ b/lib/gitlab/language_detection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class LanguageDetection MAX_LANGUAGES = 5 diff --git a/lib/gitlab/lazy.rb b/lib/gitlab/lazy.rb index 99594577141..d7a22aa339e 100644 --- a/lib/gitlab/lazy.rb +++ b/lib/gitlab/lazy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # A class that can be wrapped around an expensive method call so it's only # executed when actually needed. diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index ead5d566871..fa44bd842b2 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class LfsToken attr_accessor :actor diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 3d7c049c17f..128a5dd8936 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Logger < ::Logger def self.file_name diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb index db04356a5e9..78f2d83c1af 100644 --- a/lib/gitlab/mail_room.rb +++ b/lib/gitlab/mail_room.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' require 'json' require_relative 'redis/queues' unless defined?(Gitlab::Redis::Queues) diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb index 49285e35251..142b7d1a472 100644 --- a/lib/gitlab/markup_helper.rb +++ b/lib/gitlab/markup_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module MarkupHelper extend self diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 7d63ca5627d..61ed20ad623 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Metrics include Gitlab::Metrics::InfluxDb diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb index 04135dac4ff..ce9d3ec3de4 100644 --- a/lib/gitlab/metrics/influx_db.rb +++ b/lib/gitlab/metrics/influx_db.rb @@ -86,7 +86,7 @@ module Gitlab # Example: # # Gitlab::Metrics.measure(:find_by_username_duration) do - # User.find_by_username(some_username) + # UserFinder.new(some_username).find_by_username # end # # name - The name of the field to store the execution time in. diff --git a/lib/gitlab/multi_collection_paginator.rb b/lib/gitlab/multi_collection_paginator.rb index fab7a43dd30..5375077d7dc 100644 --- a/lib/gitlab/multi_collection_paginator.rb +++ b/lib/gitlab/multi_collection_paginator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class MultiCollectionPaginator attr_reader :first_collection, :second_collection, :per_page diff --git a/lib/gitlab/namespace_sanitizer.rb b/lib/gitlab/namespace_sanitizer.rb new file mode 100644 index 00000000000..d755bbbcaf9 --- /dev/null +++ b/lib/gitlab/namespace_sanitizer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Gitlab + class NamespaceSanitizer + def self.sanitize(namespace) + namespace.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '') + end + end +end diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb index f33ea0880df..e0ac9eec1f2 100644 --- a/lib/gitlab/omniauth_initializer.rb +++ b/lib/gitlab/omniauth_initializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class OmniauthInitializer def initialize(devise_config) diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb index d09bce642b0..ce4ba9f752b 100644 --- a/lib/gitlab/optimistic_locking.rb +++ b/lib/gitlab/optimistic_locking.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module OptimisticLocking module_function diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb index fc3f21233dd..bc467486eee 100644 --- a/lib/gitlab/other_markup.rb +++ b/lib/gitlab/other_markup.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Parser/renderer for markups without other special support code. module OtherMarkup diff --git a/lib/gitlab/otp_key_rotator.rb b/lib/gitlab/otp_key_rotator.rb index ca5d49eedb9..1d3200aa099 100644 --- a/lib/gitlab/otp_key_rotator.rb +++ b/lib/gitlab/otp_key_rotator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # The +otp_key_base+ param is used to encrypt the User#otp_secret attribute. # diff --git a/lib/gitlab/pages.rb b/lib/gitlab/pages.rb index 981ef8faa9a..16df0700b08 100644 --- a/lib/gitlab/pages.rb +++ b/lib/gitlab/pages.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Pages VERSION = File.read(Rails.root.join("GITLAB_PAGES_VERSION")).strip.freeze diff --git a/lib/gitlab/pages_client.rb b/lib/gitlab/pages_client.rb index 7b358a3bd1b..3626e53f84c 100644 --- a/lib/gitlab/pages_client.rb +++ b/lib/gitlab/pages_client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class PagesClient class << self diff --git a/lib/gitlab/pages_transfer.rb b/lib/gitlab/pages_transfer.rb index fb215f27cbd..a70dc826f97 100644 --- a/lib/gitlab/pages_transfer.rb +++ b/lib/gitlab/pages_transfer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class PagesTransfer < ProjectTransfer def root_dir diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index a78314afdb2..44025650de0 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module PathRegex extend self diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb index fda61000f6a..4b0c7b5c7f8 100644 --- a/lib/gitlab/performance_bar.rb +++ b/lib/gitlab/performance_bar.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module PerformanceBar ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze diff --git a/lib/gitlab/plugin.rb b/lib/gitlab/plugin.rb index 0d1cb16b378..23353f36025 100644 --- a/lib/gitlab/plugin.rb +++ b/lib/gitlab/plugin.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Plugin def self.files diff --git a/lib/gitlab/plugin_logger.rb b/lib/gitlab/plugin_logger.rb index c4f6ec3e21d..df3bd56fd2f 100644 --- a/lib/gitlab/plugin_logger.rb +++ b/lib/gitlab/plugin_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class PluginLogger < Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/polling_interval.rb b/lib/gitlab/polling_interval.rb index fe4bdfe3831..0f69990df63 100644 --- a/lib/gitlab/polling_interval.rb +++ b/lib/gitlab/polling_interval.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class PollingInterval HEADER_NAME = 'Poll-Interval'.freeze diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index d0cb7c1a7cf..7fa00d0c68c 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'open3' @@ -11,7 +13,7 @@ module Gitlab def popen(cmd, path = nil, vars = {}, &block) result = popen_with_detail(cmd, path, vars, &block) - [result.stdout << result.stderr, result.status&.exitstatus] + ["#{result.stdout}#{result.stderr}", result.status&.exitstatus] end # Returns Result diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb index 15391b1330b..4a62f367835 100644 --- a/lib/gitlab/profiler.rb +++ b/lib/gitlab/profiler.rb @@ -1,4 +1,6 @@ # coding: utf-8 +# frozen_string_literal: true + module Gitlab module Profiler FILTERED_STRING = '[FILTERED]'.freeze diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 71e9cc7f238..3a202d915e3 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ProjectSearchResults < SearchResults attr_reader :project, :repository_ref @@ -57,7 +59,8 @@ module Gitlab ref = nil filename = nil basename = nil - data = "" + + data = [] startline = 0 result.each_line.each_with_index do |line, index| @@ -78,7 +81,7 @@ module Gitlab basename: basename, ref: ref, startline: startline, - data: data, + data: data.join, project_id: project ? project.id : nil ) end diff --git a/lib/gitlab/project_service_logger.rb b/lib/gitlab/project_service_logger.rb index e84dca97962..9b0357d3161 100644 --- a/lib/gitlab/project_service_logger.rb +++ b/lib/gitlab/project_service_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ProjectServiceLogger < Gitlab::JsonLogger def self.file_name_noext diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index 08f6a54776f..3bfd6ee892c 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ProjectTemplate attr_reader :title, :name, :description, :preview diff --git a/lib/gitlab/project_transfer.rb b/lib/gitlab/project_transfer.rb index 690c38737c0..d8f1d1e2316 100644 --- a/lib/gitlab/project_transfer.rb +++ b/lib/gitlab/project_transfer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # This class is used to move local, unhashed files owned by projects to their new location class ProjectTransfer diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb index b66253a10e0..45828c77a33 100644 --- a/lib/gitlab/prometheus_client.rb +++ b/lib/gitlab/prometheus_client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Helper methods to interact with Prometheus network services & resources class PrometheusClient diff --git a/lib/gitlab/protocol_access.rb b/lib/gitlab/protocol_access.rb index 2819c7d062c..efeb1e07d49 100644 --- a/lib/gitlab/protocol_access.rb +++ b/lib/gitlab/protocol_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module ProtocolAccess def self.allowed?(protocol) diff --git a/lib/gitlab/proxy_http_connection_adapter.rb b/lib/gitlab/proxy_http_connection_adapter.rb index d682289b632..82213098672 100644 --- a/lib/gitlab/proxy_http_connection_adapter.rb +++ b/lib/gitlab/proxy_http_connection_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This class is part of the Gitlab::HTTP wrapper. Depending on the value # of the global setting allow_local_requests_from_hooks_and_services this adapter # will allow/block connection to internal IPs and/or urls. diff --git a/lib/gitlab/query_limiting.rb b/lib/gitlab/query_limiting.rb index 9f69a9e4a39..31e6b120e45 100644 --- a/lib/gitlab/query_limiting.rb +++ b/lib/gitlab/query_limiting.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module QueryLimiting # Returns true if we should enable tracking of query counts. diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb index c9efa28d7e7..6559c3e3c57 100644 --- a/lib/gitlab/recaptcha.rb +++ b/lib/gitlab/recaptcha.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Recaptcha def self.load_configurations! diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb index bb26f1b610a..d2dbc6f5ef5 100644 --- a/lib/gitlab/reference_counter.rb +++ b/lib/gitlab/reference_counter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ReferenceCounter REFERENCE_EXPIRE_TIME = 600 diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 9ff82d628c0..00f817c2399 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor < Banzai::ReferenceExtractor diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 0f26fcfe8cb..7a1a2eaf6c0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Regex extend self diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 4888184403c..202d310e237 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module RepoPath NotFoundError = Class.new(StandardError) diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb index a03ce07b6a1..56007574b1b 100644 --- a/lib/gitlab/repository_cache.rb +++ b/lib/gitlab/repository_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Interface to the Redis-backed cache store module Gitlab class RepositoryCache @@ -6,7 +8,7 @@ module Gitlab def initialize(repository, extra_namespace: nil, backend: Rails.cache) @repository = repository @namespace = "#{repository.full_path}:#{repository.project.id}" - @namespace += ":#{extra_namespace}" if extra_namespace + @namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace @backend = backend end diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb index d95024fccf7..931298b5117 100644 --- a/lib/gitlab/repository_cache_adapter.rb +++ b/lib/gitlab/repository_cache_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module RepositoryCacheAdapter extend ActiveSupport::Concern diff --git a/lib/gitlab/repository_check_logger.rb b/lib/gitlab/repository_check_logger.rb index 485b596ca57..e90b0a002af 100644 --- a/lib/gitlab/repository_check_logger.rb +++ b/lib/gitlab/repository_check_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class RepositoryCheckLogger < Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb index 8562d4515aa..f8f8ec789ce 100644 --- a/lib/gitlab/request_context.rb +++ b/lib/gitlab/request_context.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class RequestContext class << self diff --git a/lib/gitlab/request_forgery_protection.rb b/lib/gitlab/request_forgery_protection.rb index a502ad8a541..b1e478093d3 100644 --- a/lib/gitlab/request_forgery_protection.rb +++ b/lib/gitlab/request_forgery_protection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # A module to check CSRF tokens in requests. # It's used in API helpers and OmniAuth. # Usage: GitLab::RequestForgeryProtection.call(env) diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb index 0c9ab759e81..64593153686 100644 --- a/lib/gitlab/request_profiler.rb +++ b/lib/gitlab/request_profiler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' module Gitlab diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb index f3952657983..a555bf1d812 100644 --- a/lib/gitlab/route_map.rb +++ b/lib/gitlab/route_map.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class RouteMap FormatError = Class.new(StandardError) diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb index 2c994536060..3b05f181ed2 100644 --- a/lib/gitlab/routing.rb +++ b/lib/gitlab/routing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Routing extend ActiveSupport::Concern @@ -47,7 +49,7 @@ module Gitlab # # `request.fullpath` includes the querystring new_path = request.path.sub(%r{/#{path}(/*)(?!.*#{path})}, "/-/#{path}\\1") - new_path << "?#{request.query_string}" if request.query_string.present? + new_path = "#{new_path}?#{request.query_string}" if request.query_string.present? new_path end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index d1cf8e10228..5ce3eda2ccb 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class SearchResults class FoundBlob diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index 98f005cb61b..84a51773276 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # :nocov: module DeliverNever def deliver_later diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb index 6381e94c1d2..24e3866128b 100644 --- a/lib/gitlab/sentry.rb +++ b/lib/gitlab/sentry.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Sentry def self.enabled? diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb index 5b68e4470cd..2b7e12639be 100644 --- a/lib/gitlab/setup_helper.rb +++ b/lib/gitlab/setup_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'toml-rb' module Gitlab @@ -30,7 +32,10 @@ module Gitlab end if Rails.env.test? - storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s } + storage_path = Rails.root.join('tmp', 'tests', 'second_storage').to_s + + FileUtils.mkdir(storage_path) unless File.exist?(storage_path) + storages << { name: 'test_second_storage', path: storage_path } end config = { socket_path: address.sub(/\Aunix:/, ''), storage: storages } diff --git a/lib/gitlab/shard_health_cache.rb b/lib/gitlab/shard_health_cache.rb index 3f03f46d4b1..6612347438e 100644 --- a/lib/gitlab/shard_health_cache.rb +++ b/lib/gitlab/shard_health_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class ShardHealthCache HEALTHY_SHARDS_KEY = 'gitlab-healthy-shards'.freeze diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 89d2028d7b0..16c1edb2f11 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Gitaly note: SSH key operations are not part of Gitaly so will never be migrated. require 'securerandom' diff --git a/lib/gitlab/shell_adapter.rb b/lib/gitlab/shell_adapter.rb index 053dd4ab9e0..59fc6ee8dc8 100644 --- a/lib/gitlab/shell_adapter.rb +++ b/lib/gitlab/shell_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # == GitLab Shell mixin # # Provide a shortcut to Gitlab::Shell instance by gitlab_shell diff --git a/lib/gitlab/sherlock.rb b/lib/gitlab/sherlock.rb index 6360527a7aa..a1471c9de47 100644 --- a/lib/gitlab/sherlock.rb +++ b/lib/gitlab/sherlock.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' module Gitlab diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb index c3d7814551c..01f60a98ad8 100644 --- a/lib/gitlab/sidekiq_config.rb +++ b/lib/gitlab/sidekiq_config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' require 'set' diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb index c1dab87a432..ce82a6f04bb 100644 --- a/lib/gitlab/sidekiq_logger.rb +++ b/lib/gitlab/sidekiq_logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class SidekiqLogger < Gitlab::Logger def self.file_name_noext diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb index a1f689d94d9..583a970bf4e 100644 --- a/lib/gitlab/sidekiq_status.rb +++ b/lib/gitlab/sidekiq_status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # The SidekiqStatus module and its child classes can be used for checking if a # Sidekiq job has been processed or not. diff --git a/lib/gitlab/sidekiq_versioning.rb b/lib/gitlab/sidekiq_versioning.rb index 9683214ec18..8164a5a9d7a 100644 --- a/lib/gitlab/sidekiq_versioning.rb +++ b/lib/gitlab/sidekiq_versioning.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module SidekiqVersioning def self.install! diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb index 95e37dfbdab..e360b552f89 100644 --- a/lib/gitlab/snippet_search_results.rb +++ b/lib/gitlab/snippet_search_results.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class SnippetSearchResults < SearchResults include SnippetsHelper diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb index 6f63ea91ae8..47571239b5c 100644 --- a/lib/gitlab/ssh_public_key.rb +++ b/lib/gitlab/ssh_public_key.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class SSHPublicKey Technology = Struct.new(:name, :key_class, :supported_sizes) @@ -26,7 +28,7 @@ module Gitlab return key_content if parts.empty? - parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index| + parts.each_with_object(+"#{ssh_type} ").with_index do |(part, content), index| content << part if Gitlab::SSHPublicKey.new(content).valid? diff --git a/lib/gitlab/string_placeholder_replacer.rb b/lib/gitlab/string_placeholder_replacer.rb index 9a2219b7d77..62621255a53 100644 --- a/lib/gitlab/string_placeholder_replacer.rb +++ b/lib/gitlab/string_placeholder_replacer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class StringPlaceholderReplacer # This method accepts the following paras diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb index c6ad997a4d4..780fe4c7725 100644 --- a/lib/gitlab/string_range_marker.rb +++ b/lib/gitlab/string_range_marker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class StringRangeMarker attr_accessor :raw_line, :rich_line, :html_escaped diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb index 1c87c56c45e..f1982ff914c 100644 --- a/lib/gitlab/string_regex_marker.rb +++ b/lib/gitlab/string_regex_marker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class StringRegexMarker < StringRangeMarker # rubocop: disable CodeReuse/ActiveRecord diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb index 922418966e9..224bb648d8f 100644 --- a/lib/gitlab/task_helpers.rb +++ b/lib/gitlab/task_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rainbow/ext/string' require 'gitlab/utils/strong_memoize' @@ -39,7 +41,7 @@ module Gitlab File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] end - os_name.try(:squish!) + os_name.try(:squish) end # Prompt the user to input something diff --git a/lib/gitlab/tcp_checker.rb b/lib/gitlab/tcp_checker.rb index 6e24e46d0ea..f37a044b607 100644 --- a/lib/gitlab/tcp_checker.rb +++ b/lib/gitlab/tcp_checker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class TcpChecker attr_reader :remote_host, :remote_port, :local_host, :local_port, :error diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb index 4456217017f..699d747892c 100644 --- a/lib/gitlab/template/base_template.rb +++ b/lib/gitlab/template/base_template.rb @@ -1,7 +1,7 @@ module Gitlab module Template class BaseTemplate - attr_reader :category + attr_accessor :category def initialize(path, project = nil, category: nil) @path = path diff --git a/lib/gitlab/template_helper.rb b/lib/gitlab/template_helper.rb index fc498dde723..b0e01697a66 100644 --- a/lib/gitlab/template_helper.rb +++ b/lib/gitlab/template_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module TemplateHelper def prepare_template_environment(file) diff --git a/lib/gitlab/temporarily_allow.rb b/lib/gitlab/temporarily_allow.rb index 8d7dacc6c54..000f8ca699d 100644 --- a/lib/gitlab/temporarily_allow.rb +++ b/lib/gitlab/temporarily_allow.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module TemporarilyAllow TEMPORARILY_ALLOW_MUTEX = Mutex.new diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb index 694b01b272c..63860b9cb26 100644 --- a/lib/gitlab/themes.rb +++ b/lib/gitlab/themes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # Module containing GitLab's application theme definitions and helper methods # for accessing them. diff --git a/lib/gitlab/time_tracking_formatter.rb b/lib/gitlab/time_tracking_formatter.rb index d615c24149a..cc206010e74 100644 --- a/lib/gitlab/time_tracking_formatter.rb +++ b/lib/gitlab/time_tracking_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module TimeTrackingFormatter extend self diff --git a/lib/gitlab/timeless.rb b/lib/gitlab/timeless.rb index 76a1808c8ac..4f974c98c71 100644 --- a/lib/gitlab/timeless.rb +++ b/lib/gitlab/timeless.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Timeless def self.timeless(model, &block) diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb index c2955cd374c..453d78e2f7b 100644 --- a/lib/gitlab/tree_summary.rb +++ b/lib/gitlab/tree_summary.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class TreeSummary include ::Gitlab::Utils::StrongMemoize diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index dc2d91dfa23..ba1137313d8 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab # An untrusted regular expression is any regexp containing patterns sourced # from user input. diff --git a/lib/gitlab/update_path_error.rb b/lib/gitlab/update_path_error.rb index 8947ecfb92e..bc066bf4143 100644 --- a/lib/gitlab/update_path_error.rb +++ b/lib/gitlab/update_path_error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab UpdatePathError = Class.new(StandardError) end diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index 024be6aca44..ccab0e4dd73 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class Upgrader def execute diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb index 7d7400bdabf..e0e7084e27e 100644 --- a/lib/gitlab/uploads_transfer.rb +++ b/lib/gitlab/uploads_transfer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class UploadsTransfer < ProjectTransfer def root_dir diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 3b483f27e70..7735b736689 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'resolv' module Gitlab diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index e64033b0dba..f86d599e4cb 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class UrlBuilder include Gitlab::Routing diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 29672d68cad..035268bc4f2 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class UrlSanitizer ALLOWED_SCHEMES = %w[http https ssh git].freeze diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 5097c3253c9..cc0817bdcd2 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class UsageData class << self diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 27560abfb96..980a8014409 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class UserAccess extend Gitlab::Cache::RequestCache diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index aeda66763e8..2c92458f777 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Utils extend self diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb index 6ee41e85cc9..aa6d5310161 100644 --- a/lib/gitlab/version_info.rb +++ b/lib/gitlab/version_info.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class VersionInfo include Comparable diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 2612208a927..a3c7de87765 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Gitlab::VisibilityLevel module # # Define allowed public modes that can be used for diff --git a/lib/gitlab/wiki_file_finder.rb b/lib/gitlab/wiki_file_finder.rb index f97278f05cd..a00cd65594c 100644 --- a/lib/gitlab/wiki_file_finder.rb +++ b/lib/gitlab/wiki_file_finder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class WikiFileFinder < FileFinder attr_reader :repository diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 30a8c3578d8..e1f777e9cd1 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'base64' require 'json' require 'securerandom' @@ -63,7 +65,7 @@ module Gitlab def send_git_archive(repository, ref:, format:, append_sha:) format ||= 'tar.gz' - format.downcase! + format = format.downcase params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha) raise "Repository or ref not found" if params.empty? diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb index 5ecd6169ecf..2e98227a05e 100644 --- a/lib/haml_lint/inline_javascript.rb +++ b/lib/haml_lint/inline_javascript.rb @@ -2,9 +2,9 @@ # frozen_string_literal: true unless Rails.env.production? - require 'haml_lint/haml_visitor' - require 'haml_lint/linter' - require 'haml_lint/linter_registry' + require_dependency 'haml_lint/haml_visitor' + require_dependency 'haml_lint/linter' + require_dependency 'haml_lint/linter_registry' module HamlLint class Linter::InlineJavaScript < Linter diff --git a/lib/quality/helm_client.rb b/lib/quality/helm_client.rb index 49d953da681..cf1f03b35b5 100644 --- a/lib/quality/helm_client.rb +++ b/lib/quality/helm_client.rb @@ -5,9 +5,13 @@ require_relative '../gitlab/popen' unless defined?(Gitlab::Popen) module Quality class HelmClient + CommandFailedError = Class.new(StandardError) + attr_reader :namespace - Release = Struct.new(:name, :revision, :last_update, :status, :chart, :namespace) do + RELEASE_JSON_ATTRIBUTES = %w[Name Revision Updated Status Chart AppVersion Namespace].freeze + + Release = Struct.new(:name, :revision, :last_update, :status, :chart, :app_version, :namespace) do def revision @revision ||= self[:revision].to_i end @@ -17,22 +21,24 @@ module Quality end end - def initialize(namespace: ENV['KUBE_NAMESPACE']) + # A single page of data and the corresponding page number. + Page = Struct.new(:releases, :number) + + def initialize(namespace:) @namespace = namespace end def releases(args: []) - command = ['list', %(--namespace "#{namespace}"), *args] - - run_command(command) - .stdout - .lines - .select { |line| line.include?(namespace) } - .map { |line| Release.new(*line.split(/\t/).map(&:strip)) } + each_release(args) end def delete(release_name:) - run_command(['delete', '--purge', release_name]) + run_command([ + 'delete', + %(--tiller-namespace "#{namespace}"), + '--purge', + release_name + ]) end private @@ -41,7 +47,67 @@ module Quality final_command = ['helm', *command].join(' ') puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output - Gitlab::Popen.popen_with_detail([final_command]) + result = Gitlab::Popen.popen_with_detail([final_command]) + + if result.status.success? + result.stdout.chomp.freeze + else + raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}" + end + end + + def raw_releases(args = []) + command = [ + 'list', + %(--namespace "#{namespace}"), + %(--tiller-namespace "#{namespace}" --output json), + *args + ] + json = JSON.parse(run_command(command)) + + releases = json['Releases'].map do |json_release| + Release.new(*json_release.values_at(*RELEASE_JSON_ATTRIBUTES)) + end + + [releases, json['Next']] + rescue JSON::ParserError => ex + puts "Ignoring this JSON parsing error: #{ex}" # rubocop:disable Rails/Output + [[], nil] + end + + # Fetches data from Helm and yields a Page object for every page + # of data, without loading all of them into memory. + # + # method - The Octokit method to use for getting the data. + # args - Arguments to pass to the `helm list` command. + def each_releases_page(args, &block) + return to_enum(__method__, args) unless block_given? + + page = 1 + offset = '' + + loop do + final_args = args.dup + final_args << "--offset #{offset}" unless offset.to_s.empty? + collection, offset = raw_releases(final_args) + + yield Page.new(collection, page += 1) + + break if offset.to_s.empty? + end + end + + # Iterates over all of the releases. + # + # args - Any arguments to pass to the `helm list` command. + def each_release(args, &block) + return to_enum(__method__, args) unless block_given? + + each_releases_page(args) do |page| + page.releases.each do |release| + yield release + end + end end end end diff --git a/lib/quality/kubernetes_client.rb b/lib/quality/kubernetes_client.rb index e366a688e3e..2ff9e811425 100644 --- a/lib/quality/kubernetes_client.rb +++ b/lib/quality/kubernetes_client.rb @@ -4,19 +4,22 @@ require_relative '../gitlab/popen' unless defined?(Gitlab::Popen) module Quality class KubernetesClient + CommandFailedError = Class.new(StandardError) + attr_reader :namespace - def initialize(namespace: ENV['KUBE_NAMESPACE']) + def initialize(namespace:) @namespace = namespace end def cleanup(release_name:) - command = ['kubectl'] - command << %(-n "#{namespace}" get ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa 2>&1) - command << '|' << %(grep "#{release_name}") - command << '|' << "awk '{print $1}'" - command << '|' << %(xargs kubectl -n "#{namespace}" delete) - command << '||' << 'true' + command = [ + %(--namespace "#{namespace}"), + 'delete', + 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa', + '--now', + %(-l release="#{release_name}") + ] run_command(command) end @@ -24,9 +27,16 @@ module Quality private def run_command(command) - puts "Running command: `#{command.join(' ')}`" # rubocop:disable Rails/Output + final_command = ['kubectl', *command].join(' ') + puts "Running command: `#{final_command}`" # rubocop:disable Rails/Output + + result = Gitlab::Popen.popen_with_detail([final_command]) - Gitlab::Popen.popen_with_detail(command) + if result.status.success? + result.stdout.chomp.freeze + else + raise CommandFailedError, "The `#{final_command}` command failed (status: #{result.status}) with the following error:\n#{result.stderr}" + end end end end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index fc59b3f937d..a16d4c47273 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -9,7 +9,7 @@ class GithubImport def initialize(token, gitlab_username, project_path, extras) @options = { token: token } @project_path = project_path - @current_user = User.find_by(username: gitlab_username) + @current_user = UserFinder.new(gitlab_username).find_by_username raise "GitLab user #{gitlab_username} not found. Please specify a valid username." unless @current_user diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 363654b2ac0..26270595c6a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -330,6 +330,9 @@ msgstr "" msgid "Add Readme" msgstr "" +msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message." +msgstr "" + msgid "Add a table" msgstr "" @@ -1214,6 +1217,9 @@ msgstr "" msgid "CiStatusLabel|created" msgstr "" +msgid "CiStatusLabel|delayed" +msgstr "" + msgid "CiStatusLabel|failed" msgstr "" @@ -1229,9 +1235,6 @@ msgstr "" msgid "CiStatusLabel|pending" msgstr "" -msgid "CiStatusLabel|scheduled" -msgstr "" - msgid "CiStatusLabel|skipped" msgstr "" @@ -1250,6 +1253,9 @@ msgstr "" msgid "CiStatusText|created" msgstr "" +msgid "CiStatusText|delayed" +msgstr "" + msgid "CiStatusText|failed" msgstr "" @@ -1262,9 +1268,6 @@ msgstr "" msgid "CiStatusText|pending" msgstr "" -msgid "CiStatusText|scheduled" -msgstr "" - msgid "CiStatusText|skipped" msgstr "" @@ -2049,6 +2052,9 @@ msgstr "" msgid "Create project label" msgstr "" +msgid "Create your first page" +msgstr "" + msgid "CreateTag|Tag" msgstr "" @@ -2172,7 +2178,7 @@ msgstr "" msgid "DelayedJobs|Unschedule" msgstr "" -msgid "DelayedJobs|scheduled" +msgid "DelayedJobs|delayed" msgstr "" msgid "Delete" @@ -3593,6 +3599,9 @@ msgstr "" msgid "List available repositories" msgstr "" +msgid "List view" +msgstr "" + msgid "List your Bitbucket Server repositories" msgstr "" @@ -4021,6 +4030,12 @@ msgstr "" msgid "No" msgstr "" +msgid "No Assignee" +msgstr "" + +msgid "No Label" +msgstr "" + msgid "No assignee" msgstr "" @@ -4126,6 +4141,12 @@ msgstr "" msgid "Notes|Are you sure you want to cancel creating this comment?" msgstr "" +msgid "Notes|Show all activity" +msgstr "" + +msgid "Notes|Show comments only" +msgstr "" + msgid "Notification events" msgstr "" @@ -5583,6 +5604,9 @@ msgstr "" msgid "Something went wrong while closing the %{issuable}. Please try again later" msgstr "" +msgid "Something went wrong while fetching comments. Please try again." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" @@ -6107,7 +6131,7 @@ msgstr "" msgid "This is a confidential issue." msgstr "" -msgid "This is a scheduled to run in " +msgid "This is a delayed to run in " msgstr "" msgid "This is the author's first Merge Request to this project." @@ -6197,6 +6221,9 @@ msgstr "" msgid "This project does not belong to a group and can therefore not make use of group Runners." msgstr "" +msgid "This project does not have a wiki homepage yet" +msgstr "" + msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again." msgstr "" @@ -6495,6 +6522,9 @@ msgstr "" msgid "Track time with quick actions" msgstr "" +msgid "Tree view" +msgstr "" + msgid "Trending" msgstr "" @@ -6576,6 +6606,9 @@ msgstr "" msgid "Up to date" msgstr "" +msgid "Upcoming" +msgstr "" + msgid "Update" msgstr "" @@ -6708,6 +6741,9 @@ msgstr "" msgid "Version" msgstr "" +msgid "View app" +msgstr "" + msgid "View file @ " msgstr "" diff --git a/package.json b/package.json index 8ec47bc2837..086617dc265 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js", "karma-start": "BABEL_ENV=karma karma start config/karma.config.js", "postinstall": "node ./scripts/frontend/postinstall.js", - "prettier-staged": "node ./scripts/frontend/prettier.js", + "prettier-staged": "node ./scripts/frontend/prettier.js check", "prettier-staged-save": "node ./scripts/frontend/prettier.js save", "prettier-all": "node ./scripts/frontend/prettier.js check-all", "prettier-all-save": "node ./scripts/frontend/prettier.js save-all", @@ -24,12 +24,11 @@ "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/plugin-syntax-import-meta": "^7.0.0", "@babel/preset-env": "^7.1.0", - "@gitlab-org/gitlab-svgs": "^1.32.0", + "@gitlab-org/gitlab-svgs": "^1.33.0", "@gitlab-org/gitlab-ui": "^1.8.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-loader": "^8.0.4", - "blackst0ne-mermaid": "^7.1.0-fixed", "bootstrap": "4.1.1", "brace-expansion": "^1.1.8", "cache-loader": "^1.2.2", @@ -71,6 +70,7 @@ "jszip-utils": "^0.0.2", "katex": "^0.9.0", "marked": "^0.3.12", + "mermaid": "^8.0.0-rc.8", "monaco-editor": "^0.14.3", "monaco-editor-webpack-plugin": "^1.5.4", "mousetrap": "^1.4.6", @@ -126,7 +126,6 @@ "eslint-plugin-jasmine": "^2.10.1", "gettext-extractor": "^3.3.2", "gettext-extractor-vue": "^4.0.1", - "ignore": "^3.3.7", "istanbul": "^0.4.5", "jasmine-core": "^2.9.0", "jasmine-diff": "^0.1.3", @@ -18,6 +18,7 @@ module QA autoload :Address, 'qa/runtime/address' autoload :Path, 'qa/runtime/path' autoload :Fixtures, 'qa/runtime/fixtures' + autoload :Logger, 'qa/runtime/logger' module API autoload :Client, 'qa/runtime/api/client' @@ -324,6 +325,14 @@ module QA end end end + + # Classes that provide support to other parts of the framework. + # + module Support + module Page + autoload :Logging, 'qa/support/page/logging' + end + end end QA::Runtime::Release.extend_autoloads! diff --git a/qa/qa/factory/README.md b/qa/qa/factory/README.md index c56c7c43129..10140e39510 100644 --- a/qa/qa/factory/README.md +++ b/qa/qa/factory/README.md @@ -254,8 +254,7 @@ module QA project.name = 'project-to-create-a-shirt' end - # Attribute inherited from the Shirt factory if present, - # or from the Browser UI otherwise (using the block) + # Attribute from the Browser UI (using the block) product :brand do Page::Shirt::Show.perform do |shirt_show| shirt_show.fetch_brand_from_page @@ -347,8 +346,7 @@ module QA project.name = 'project-to-create-a-shirt' end - # Attribute fetched from the API response if present if present, - # or from the Shirt factory if present, + # Attribute fetched from the API response if present, # or from the Browser UI otherwise (using the block) product :brand do Page::Shirt::Show.perform do |shirt_show| @@ -356,7 +354,7 @@ module QA end end - # Attribute fetched from the API response if present if present, + # Attribute fetched from the API response if present, # or from the Shirt factory if present, # or a QA::Factory::Product::NoValueError is raised otherwise product :name @@ -414,9 +412,9 @@ end **Notes on attributes precedence:** - attributes from the API response take precedence over attributes from the + Browser UI +- attributes from the Browser UI take precedence over attributes from the factory (i.e inherited) -- attributes from the factory (i.e inherited) take precedence over attributes - from the Browser UI - attributes without a value will raise a `QA::Factory::Product::NoValueError` error ## Creating resources in your tests diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb index a8ecac2a1e6..e1dc23d350d 100644 --- a/qa/qa/factory/base.rb +++ b/qa/qa/factory/base.rb @@ -63,7 +63,7 @@ module QA private_class_method :do_fabricate! def self.log_fabrication(method, factory, parents, args) - return yield unless Runtime::Env.verbose? + return yield unless Runtime::Env.debug? start = Time.now prefix = "==#{'=' * parents.size}>" diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb index 6c5088f1da5..703c78daa99 100644 --- a/qa/qa/factory/repository/push.rb +++ b/qa/qa/factory/repository/push.rb @@ -30,6 +30,14 @@ module QA @directory = dir end + def files=(files) + if !files.is_a?(Array) || files.empty? + raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]" + end + + @files = files + end + def fabricate! Git::Repository.perform do |repository| if ssh_key @@ -63,6 +71,10 @@ module QA @directory.each_child do |f| repository.add_file(f.basename, f.read) if f.file? end + elsif @files + @files.each do |f| + repository.add_file(f[:name], f[:content]) + end else repository.add_file(file_name, file_content) end diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb index 18046c7a8b2..d30da8a3db0 100644 --- a/qa/qa/factory/resource/merge_request.rb +++ b/qa/qa/factory/resource/merge_request.rb @@ -30,6 +30,7 @@ module QA push.project = factory.project push.branch_name = factory.target_branch push.remote_branch = factory.source_branch + push.new_branch = false push.file_name = "added_file.txt" push.file_content = "File Added" end diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb index f2e58a3ea38..5e8f883e25f 100644 --- a/qa/qa/factory/settings/hashed_storage.rb +++ b/qa/qa/factory/settings/hashed_storage.rb @@ -9,7 +9,7 @@ module QA Page::Main::Menu.act { go_to_admin_area } Page::Admin::Menu.act { go_to_repository_settings } - Page::Admin::Settings::Main.perform do |setting| + Page::Admin::Settings::Repository.perform do |setting| setting.expand_repository_storage do |page| page.enable_hashed_storage page.save_settings diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 14cb8125fdb..c6a8891d398 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -1,14 +1,24 @@ +# frozen_string_literal: true + require 'cgi' require 'uri' require 'open3' +require 'fileutils' +require 'tmpdir' module QA module Git class Repository include Scenario::Actable + attr_writer :password + attr_accessor :env_vars + def initialize - @ssh_cmd = "" + # We set HOME to the current working directory (which is a + # temporary directory created in .perform()) so the temporarily dropped + # .netrc can be utilised + self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}] end def self.perform(*args) @@ -21,36 +31,27 @@ module QA @uri = URI(address) end - def username=(name) - @username = name - @uri.user = name - end - - def password=(pass) - @password = pass - @uri.password = CGI.escape(pass).gsub('+', '%20') + def username=(username) + @username = username + @uri.user = username end def use_default_credentials - if ::QA::Runtime::User.ldap_user? - self.username = Runtime::User.ldap_username - self.password = Runtime::User.ldap_password - else - self.username = Runtime::User.username - self.password = Runtime::User.password - end + self.username, self.password = default_credentials + + add_credentials_to_netrc unless ssh_key_set? end def clone(opts = '') - run_and_redact_credentials(build_git_command("git clone #{opts} #{@uri} ./")) + run("git clone #{opts} #{uri} ./") end def checkout(branch_name) - `git checkout "#{branch_name}"` + run(%Q{git checkout "#{branch_name}"}) end def checkout_new_branch(branch_name) - `git checkout -b "#{branch_name}"` + run(%Q{git checkout -b "#{branch_name}"}) end def shallow_clone @@ -58,12 +59,10 @@ module QA end def configure_identity(name, email) - `git config user.name #{name}` - `git config user.email #{email}` - end + run(%Q{git config user.name #{name}}) + run(%Q{git config user.email #{email}}) - def configure_ssh_command(command) - @ssh_cmd = "GIT_SSH_COMMAND='#{command}'" + add_credentials_to_netrc end def commit_file(name, contents, message) @@ -74,54 +73,103 @@ module QA def add_file(name, contents) ::File.write(name, contents) - `git add #{name}` + run(%Q{git add #{name}}) end def commit(message) - `git commit -m "#{message}"` + run(%Q{git commit -m "#{message}"}) end def push_changes(branch = 'master') - output, _ = run_and_redact_credentials(build_git_command("git push #{@uri} #{branch}")) - - output + run("git push #{uri} #{branch}") end def commits - `git log --oneline`.split("\n") + run('git log --oneline').split("\n") end def use_ssh_key(key) @private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}") - File.binwrite(@private_key_file, key.private_key) - File.chmod(0700, @private_key_file) + File.binwrite(private_key_file, key.private_key) + File.chmod(0700, private_key_file) @known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}") keyscan_params = ['-H'] - keyscan_params << "-p #{@uri.port}" if @uri.port - keyscan_params << @uri.host - run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{@known_hosts_file.path}") + keyscan_params << "-p #{uri.port}" if uri.port + keyscan_params << uri.host + run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}") - configure_ssh_command("ssh -i #{@private_key_file.path} -o UserKnownHostsFile=#{@known_hosts_file.path}") + self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"} end def delete_ssh_key - return unless @private_key_file + return unless ssh_key_set? - @private_key_file.close(true) - @known_hosts_file.close(true) + private_key_file.close(true) + known_hosts_file.close(true) end - def build_git_command(command_str) - [@ssh_cmd, command_str].compact.join(' ') + private + + attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file + + def debug? + Runtime::Env.respond_to?(:verbose?) && Runtime::Env.verbose? end - private + def ssh_key_set? + !private_key_file.nil? + end + + def run(command_str) + command = [env_vars, command_str, '2>&1'].compact.join(' ') + warn "DEBUG: command=[#{command}]" if debug? + + output, _ = Open3.capture2(command) + output = output.chomp.gsub(/\s+$/, '') + warn "DEBUG: output=[#{output}]" if debug? + + output + end + + def default_credentials + if ::QA::Runtime::User.ldap_user? + [Runtime::User.ldap_username, Runtime::User.ldap_password] + else + [Runtime::User.username, Runtime::User.password] + end + end + + def tmp_netrc_directory + @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s) + end + + def netrc_file_path + @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc') + end + + def netrc_content + "machine #{uri.host} login #{username} password #{password}" + end + + def netrc_already_contains_content? + File.exist?(netrc_file_path) && + File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any? + end + + def add_credentials_to_netrc + # Despite libcurl supporting a custom .netrc location through the + # CURLOPT_NETRC_FILE environment variable, git does not support it :( + # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html + # + # This will create a .netrc in the correct working directory, which is + # a temporary directory created in .perform() + # + return if netrc_already_contains_content? - # Since the remote URL contains the credentials, and git occasionally - # outputs the URL. Note that stderr is redirected to stdout. - def run_and_redact_credentials(command) - Open3.capture2("#{command} 2>&1 | sed -E 's#://[^@]+@#://****@#g'") + FileUtils.mkdir_p(tmp_netrc_directory) + File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) } + File.chmod(0600, netrc_file_path) end end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 160ec58cf2c..91e229c4c8c 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -5,6 +5,7 @@ require 'capybara/dsl' module QA module Page class Base + prepend Support::Page::Logging if Runtime::Env.debug? include Capybara::DSL include Scenario::Actable extend SingleForwardable diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 5baf6439cfc..d688f15914c 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -4,30 +4,39 @@ module QA::Page COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running PASSED_STATUS = 'passed'.freeze - view 'app/views/shared/builds/_build_output.html.haml' do - element :build_output, '.js-build-output' # rubocop:disable QA/ElementWithPattern - element :loading_animation, '.js-build-refresh' # rubocop:disable QA/ElementWithPattern + view 'app/assets/javascripts/jobs/components/job_app.vue' do + element :loading_animation + end + + view 'app/assets/javascripts/jobs/components/job_log.vue' do + element :build_trace end view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do - element :status_badge, 'ci-status' # rubocop:disable QA/ElementWithPattern + element :status_badge end def completed? - COMPLETED_STATUSES.include? find('.ci-status').text + COMPLETED_STATUSES.include?(status_badge) end def passed? - find('.ci-status').text == PASSED_STATUS + status_badge == PASSED_STATUS end def trace_loading? - has_css?('.js-build-refresh') + has_element?(:loading_animation) end # Reminder: You may wish to wait for a particular job status before checking output def output - find('.js-build-output').text + find_element(:build_trace).text + end + + private + + def status_badge + find_element(:status_badge).text end end end diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index bc125d1af88..cb4a10e1b6a 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -87,6 +87,14 @@ module QA end end + def go_to_labels + hover_issues do + within_submenu do + click_element(:labels_link) + end + end + end + def click_merge_requests within_sidebar do click_link('Merge Requests') @@ -105,8 +113,10 @@ module QA end end - def go_to_labels - hover_issues { click_element :labels_link } + def click_repository + within_sidebar do + click_link('Repository') + end end private diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index fcc4bb79c10..d6dddf03ffb 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -42,6 +42,10 @@ module QA element :web_ide_button end + view 'app/views/projects/tree/_tree_content.html.haml' do + element :file_tree + end + def project_name find('.qa-project-name').text end @@ -51,6 +55,12 @@ module QA click_element :new_file_option end + def go_to_file(filename) + within_element(:file_tree) do + click_on filename + end + end + def switch_to_branch(branch_name) find_element(:branches_select).click diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 533ed87453a..c7052a9f300 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module QA module Runtime module Env @@ -5,8 +7,12 @@ module QA attr_writer :personal_access_token - def verbose? - enabled?(ENV['VERBOSE'], default: false) + def debug? + enabled?(ENV['QA_DEBUG'], default: false) + end + + def log_destination + ENV['QA_LOG_PATH'] || $stdout end # set to 'false' to have Chrome run visibly instead of headless diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb new file mode 100644 index 00000000000..3baa24de0ec --- /dev/null +++ b/qa/qa/runtime/logger.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'logger' + +module QA + module Runtime + module Logger + extend SingleForwardable + + def_delegators :logger, :debug, :info, :error, :warn, :fatal, :unknown + + singleton_class.module_eval do + def logger + return @logger if @logger + + @logger = ::Logger.new Runtime::Env.log_destination + @logger.level = ::Logger::DEBUG + @logger + end + end + end + end +end diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb index 3e3c9e859aa..ba1ba204d24 100644 --- a/qa/qa/specs/features/api/1_manage/users_spec.rb +++ b/qa/qa/specs/features/api/1_manage/users_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage do + context 'Manage' do describe 'Users API' do before(:context) do @api_client = Runtime::API::Client.new(:gitlab) diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb index ae196349c6b..dae2a9e0236 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb @@ -1,5 +1,5 @@ module QA - context :manage, :smoke do + context 'Manage', :smoke do describe 'basic user login' do it 'user logs in using basic credentials' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb index 217870531da..eb9e0297287 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage, :orchestrated, :ldap do + context 'Manage', :orchestrated, :ldap do describe 'LDAP login' do it 'user logs into GitLab using LDAP credentials' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb index 6eda2c750d4..b1d641b507f 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage, :orchestrated, :mattermost do + context 'Manage', :orchestrated, :mattermost do describe 'Mattermost login' do it 'user logs into Mattermost using GitLab OAuth' do Runtime::Browser.visit(:gitlab, Page::Main::Login) do diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb index 8d5055aab45..87f0e9030d2 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage, :orchestrated, :instance_saml do + context 'Manage', :orchestrated, :instance_saml do describe 'Instance wide SAML SSO' do it 'User logs in to gitlab with SAML SSO' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index fb6b4937554..45cb5df8252 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -16,13 +16,13 @@ module QA end end - context :manage, :skip_signup_disabled do + context 'Manage', :skip_signup_disabled do describe 'standard' do it_behaves_like 'registration and login' end end - context :manage, :orchestrated, :ldap, :skip_signup_disabled do + context 'Manage', :orchestrated, :ldap, :skip_signup_disabled do describe 'while LDAP is enabled' do it_behaves_like 'registration and login' end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index 53865b44684..7bf26c22fa6 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage do + context 'Manage' do describe 'Add project member' do it 'user adds project member' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb index c8ea558aed6..a242f2158da 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage, :smoke do + context 'Manage', :smoke do describe 'Project creation' do it 'user creates a new project' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index d1cd9865aef..a99b0522e73 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage, :orchestrated, :github do + context 'Manage', :orchestrated, :github do describe 'Project import from GitHub' do let(:imported_project) do Factory::Resource::ProjectImportedFromGithub.fabricate! do |project| diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb index 97ac35e8dba..768d40f3acf 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :manage do + context 'Manage' do describe 'Project activity' do it 'user creates an event in the activity page upon Git push' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 49d76f31e3a..e67561b3a39 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :plan, :smoke do + context 'Plan', :smoke do describe 'Issue creation' do let(:issue_title) { 'issue title' } diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index f59bfb6b64d..037ff5efbd4 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Merge request creation' do it 'user creates a new merge request' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index 922feadb4e1..058af8aebdd 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Merge request creation from fork' do it 'user forks a project, submits a merge request and maintainer merges it' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 827dbb67076..3bcf086d332 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Merge request rebasing' do it 'user rebases source branch of merge request' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb index b5b8855a35d..46e1005829d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Merge request squashing' do it 'user squashes commits while merging' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb index c7edcf4c025..7705e12b95e 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'File templates' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb index b163ca896a7..df70b9608d9 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'SSH keys support' do let(:key_title) { "key for ssh tests #{Time.now.to_f}" } diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb index a982a4604ac..9c64a9a3439 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Git clone over HTTP', :ldap do let(:location) do Page::Project::Show.act do diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb index 82d635065a0..f65a1569fb0 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Files management' do it 'user creates, edits and deletes a file via the Web' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb index bf32569b6cb..b9bed39662f 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Git push over HTTP', :ldap do it 'user pushes code to the repository' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb index b2da685c477..5f42cb00bd3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Protected branch support', :ldap do let(:branch_name) { 'protected-branch' } let(:commit_message) { 'Protected push commit message' } diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb index 563393b3d07..36068ffba69 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'SSH key support' do # Note: If you run this test against GDK make sure you've enabled sshd # See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 1f07d08e664..07dbf39a8a3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Web IDE file templates' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb index 44dd85c1746..4126fd9fd3e 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :create do + context 'Create' do describe 'Wiki management' do def login Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index e901531b1bf..d66bcce879b 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :verify, :orchestrated, :docker do + context 'Verify', :orchestrated, :docker do describe 'Pipeline creation and processing' do let(:executor) { "qa-runner-#{Time.now.to_i}" } diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index 8d83a20f5bf..5d9aa00582f 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :verify, :docker do + context 'Verify', :docker do describe 'Runner registration' do let(:executor) { "qa-runner-#{Time.now.to_i}" } diff --git a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb index 08a87df5837..292f24d9c0d 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/secret_variable/add_secret_variable_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :verify do + context 'Verify' do describe 'Secret variable support' do it 'user adds a secret variable' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb index 17dfa887434..64b98da8bf5 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :release do + context 'Release' do describe 'Deploy key creation' do it 'user adds a deploy key' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 73af24e7f50..caf014c89ea 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -3,7 +3,7 @@ require 'digest/sha1' module QA - context :release, :docker do + context 'Release', :docker do describe 'Git clone using a deploy key' do def login Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb index e521597e07f..263ba6a6800 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :release do + context 'Release' do describe 'Deploy token creation' do it 'user adds a deploy token' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 3735bc00aff..c98ede25b68 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -3,7 +3,7 @@ require 'pathname' module QA - context :configure, :orchestrated, :kubernetes do + context 'Configure', :orchestrated, :kubernetes do describe 'Auto DevOps support' do after do @cluster&.remove! diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb index af24b36b734..7096864e011 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context :configure, :orchestrated, :mattermost do + context 'Configure', :orchestrated, :mattermost do describe 'Mattermost support' do it 'user creates a group with a mattermost team' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb new file mode 100644 index 00000000000..cf5cd3a79f8 --- /dev/null +++ b/qa/qa/support/page/logging.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +module QA + module Support + module Page + module Logging + def refresh + log("refreshing #{current_url}") + + super + end + + def wait(max: 60, time: 0.1, reload: true) + log("with wait: max #{max}; time #{time}; reload #{reload}") + now = Time.now + + element = super + + log("ended wait after #{Time.now - now} seconds") + + element + end + + def scroll_to(selector, text: nil) + msg = "scrolling to :#{selector}" + msg += " with text: #{text}" if text + log(msg) + + super + end + + def asset_exists?(url) + exists = super + + log("asset_exists? #{url} returned #{exists}") + + exists + end + + def find_element(name) + log("finding :#{name}") + + element = super + + log("found :#{name}") if element + + element + end + + def all_elements(name) + log("finding all :#{name}") + + elements = super + + log("found #{elements.size} :#{name}") if elements + + elements + end + + def click_element(name) + log("clicking :#{name}") + + super + end + + def fill_element(name, content) + masked_content = name.to_s.include?('password') ? '*****' : content + + log(%Q(filling :#{name} with "#{masked_content}")) + + super + end + + def has_element?(name) + found = super + + log("has_element? :#{name} returned #{found}") + + found + end + + def within_element(name) + log("within element :#{name}") + + element = super + + log("end within element :#{name}") + + element + end + + private + + def log(msg) + QA::Runtime::Logger.debug(msg) + end + end + end + end +end diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb index 184802a7903..229f93a1041 100644 --- a/qa/spec/factory/base_spec.rb +++ b/qa/spec/factory/base_spec.rb @@ -35,8 +35,8 @@ describe QA::Factory::Base do end end - it 'does not log the factory and build method when VERBOSE=false' do - stub_env('VERBOSE', 'false') + it 'does not log the factory and build method when QA_DEBUG=false' do + stub_env('QA_DEBUG', 'false') expect(factory).to receive(fabrication_method_used).and_return(product_location) expect { subject.public_send(fabrication_method_called, 'something', factory: factory) } @@ -79,8 +79,8 @@ describe QA::Factory::Base do expect(result).to eq(product) end - it 'logs the factory and build method when VERBOSE=true' do - stub_env('VERBOSE', 'true') + it 'logs the factory and build method when QA_DEBUG=true' do + stub_env('QA_DEBUG', 'true') expect(factory).to receive(:fabricate_via_api!).and_return(product_location) expect { subject.fabricate_via_api!(factory: factory, parents: []) } @@ -106,8 +106,8 @@ describe QA::Factory::Base do expect(result).to eq(product) end - it 'logs the factory and build method when VERBOSE=true' do - stub_env('VERBOSE', 'true') + it 'logs the factory and build method when QA_DEBUG=true' do + stub_env('QA_DEBUG', 'true') expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) } .to output(/==> Built a MyFactory via browser_ui with args \["something"\] in [\d\w\.\-]+/) diff --git a/qa/spec/factory/repository/push_spec.rb b/qa/spec/factory/repository/push_spec.rb new file mode 100644 index 00000000000..2eb6c008248 --- /dev/null +++ b/qa/spec/factory/repository/push_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +describe QA::Factory::Repository::Push do + describe '.files=' do + let(:files) do + [ + { + name: 'file.txt', + content: 'foo' + } + ] + end + + it 'raises an error if files is not an array' do + expect { subject.files = '' }.to raise_error(ArgumentError) + end + + it 'raises an error if files is an empty array' do + expect { subject.files = [] }.to raise_error(ArgumentError) + end + + it 'does not raise if files is an array' do + expect { subject.files = files }.not_to raise_error + end + end +end diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb index 53bff3bf0b3..c629f802aa4 100644 --- a/qa/spec/git/repository_spec.rb +++ b/qa/spec/git/repository_spec.rb @@ -1,17 +1,18 @@ describe QA::Git::Repository do + include Support::StubENV + let(:repository) { described_class.new } before do + stub_env('GITLAB_USERNAME', 'root') cd_empty_temp_directory set_bad_uri repository.use_default_credentials end describe '#clone' do - it 'redacts credentials from the URI in output' do - output, _ = repository.clone - - expect(output).to include("fatal: unable to access 'http://****@foo/bar.git/'") + it 'is unable to resolve host' do + expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'") end end @@ -20,10 +21,8 @@ describe QA::Git::Repository do `git init` # need a repo to push from end - it 'redacts credentials from the URI in output' do - output, _ = repository.push_changes - - expect(output).to include("error: failed to push some refs to 'http://****@foo/bar.git'") + it 'fails to push changes' do + expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'") end end diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb new file mode 100644 index 00000000000..9f17de4edbf --- /dev/null +++ b/qa/spec/page/logging_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +describe QA::Support::Page::Logging do + let(:page) { double().as_null_object } + + before do + allow(Capybara).to receive(:current_session).and_return(page) + allow(page).to receive(:current_url).and_return('http://current-url') + allow(page).to receive(:has_css?).with(any_args).and_return(true) + end + + subject do + Class.new(QA::Page::Base) do + prepend QA::Support::Page::Logging + end.new + end + + it 'logs refresh' do + expect { subject.refresh } + .to output(%r{refreshing http://current-url}).to_stdout_from_any_process + end + + it 'logs wait' do + expect { subject.wait(max: 0) {} } + .to output(/with wait/).to_stdout_from_any_process + expect { subject.wait(max: 0) {} } + .to output(/ended wait after .* seconds$/).to_stdout_from_any_process + end + + it 'logs scroll_to' do + expect { subject.scroll_to(:element) } + .to output(/scrolling to :element/).to_stdout_from_any_process + end + + it 'logs asset_exists?' do + expect { subject.asset_exists?('http://asset-url') } + .to output(%r{asset_exists\? http://asset-url returned false}).to_stdout_from_any_process + end + + it 'logs find_element' do + expect { subject.find_element(:element) } + .to output(/found :element/).to_stdout_from_any_process + end + + it 'logs click_element' do + expect { subject.click_element(:element) } + .to output(/clicking :element/).to_stdout_from_any_process + end + + it 'logs fill_element' do + expect { subject.fill_element(:element, 'foo') } + .to output(/filling :element with "foo"/).to_stdout_from_any_process + end + + it 'logs has_element?' do + expect { subject.has_element?(:element) } + .to output(/has_element\? :element 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 + expect { subject.within_element(:element) } + .to output(/end within element :element/).to_stdout_from_any_process + end + + context 'all_elements' do + it 'logs the number of elements found' do + allow(page).to receive(:all).and_return([1, 2]) + + expect { subject.all_elements(:element) } + .to output(/finding all :element/).to_stdout_from_any_process + expect { subject.all_elements(:element) } + .to output(/found 2 :element/).to_stdout_from_any_process + end + + it 'logs 0 if no elements are found' do + allow(page).to receive(:all).and_return([]) + + expect { subject.all_elements(:element) } + .to output(/finding all :element/).to_stdout_from_any_process + expect { subject.all_elements(:element) } + .not_to output(/found 0 :elements/).to_stdout_from_any_process + end + end +end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index b5ecf1afb80..c59c415c148 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe QA::Runtime::Env do include Support::StubENV @@ -34,14 +36,14 @@ describe QA::Runtime::Env do end end - describe '.verbose?' do - it_behaves_like 'boolean method', :verbose?, 'VERBOSE', false - end - describe '.signup_disabled?' do it_behaves_like 'boolean method', :signup_disabled?, 'SIGNUP_DISABLED', false end + describe '.debug?' do + it_behaves_like 'boolean method', :debug?, 'QA_DEBUG', false + end + describe '.chrome_headless?' do it_behaves_like 'boolean method', :chrome_headless?, 'CHROME_HEADLESS', true end @@ -166,4 +168,18 @@ describe QA::Runtime::Env do expect { described_class.require_github_access_token! }.not_to raise_error end end + + describe '.log_destination' do + it 'returns $stdout if QA_LOG_PATH is not defined' do + stub_env('QA_LOG_PATH', nil) + + expect(described_class.log_destination).to eq($stdout) + end + + it 'returns the path if QA_LOG_PATH is defined' do + stub_env('QA_LOG_PATH', 'path/to_file') + + expect(described_class.log_destination).to eq('path/to_file') + end + end end diff --git a/qa/spec/runtime/logger_spec.rb b/qa/spec/runtime/logger_spec.rb new file mode 100644 index 00000000000..794e1f9bfe6 --- /dev/null +++ b/qa/spec/runtime/logger_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +describe QA::Runtime::Logger do + it 'logs debug' do + expect { described_class.debug('test') }.to output(/DEBUG -- : test/).to_stdout_from_any_process + end + + it 'logs info' do + expect { described_class.info('test') }.to output(/INFO -- : test/).to_stdout_from_any_process + end + + it 'logs warn' do + expect { described_class.warn('test') }.to output(/WARN -- : test/).to_stdout_from_any_process + end + + it 'logs error' do + expect { described_class.error('test') }.to output(/ERROR -- : test/).to_stdout_from_any_process + end + + it 'logs fatal' do + expect { described_class.fatal('test') }.to output(/FATAL -- : test/).to_stdout_from_any_process + end + + it 'logs unknown' do + expect { described_class.unknown('test') }.to output(/ANY -- : test/).to_stdout_from_any_process + end +end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 8e6613cd688..8e01da01340 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -3,6 +3,10 @@ require_relative '../qa' Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f } RSpec.configure do |config| + config.before do |example| + QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug? + end + config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end diff --git a/scripts/frontend/frontend_script_utils.js b/scripts/frontend/frontend_script_utils.js index e42b912d359..e3d357b4a40 100644 --- a/scripts/frontend/frontend_script_utils.js +++ b/scripts/frontend/frontend_script_utils.js @@ -13,7 +13,8 @@ const execGitCmd = args => exec('git', args) .trim() .toString() - .split('\n'); + .split('\n') + .filter(Boolean); module.exports = { getStagedFiles: fileExtensionFilter => { diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js index b66ba885701..ce86a9f4601 100644 --- a/scripts/frontend/prettier.js +++ b/scripts/frontend/prettier.js @@ -1,126 +1,116 @@ const glob = require('glob'); const prettier = require('prettier'); const fs = require('fs'); -const path = require('path'); -const prettierIgnore = require('ignore')(); +const { getStagedFiles } = require('./frontend_script_utils'); -const getStagedFiles = require('./frontend_script_utils').getStagedFiles; +const matchExtensions = ['js', 'vue']; + +// This will improve glob performance by excluding certain directories. +// The .prettierignore file will also be respected, but after the glob has executed. +const globIgnore = ['**/node_modules/**', 'vendor/**', 'public/**']; + +const readFileAsync = (file, options) => + new Promise((resolve, reject) => { + fs.readFile(file, options, function(err, data) { + if (err) reject(err); + else resolve(data); + }); + }); + +const writeFileAsync = (file, data, options) => + new Promise((resolve, reject) => { + fs.writeFile(file, data, options, function(err) { + if (err) reject(err); + else resolve(); + }); + }); const mode = process.argv[2] || 'check'; const shouldSave = mode === 'save' || mode === 'save-all'; const allFiles = mode === 'check-all' || mode === 'save-all'; -let dirPath = process.argv[3] || ''; -if (dirPath && dirPath.charAt(dirPath.length - 1) !== '/') dirPath += '/'; - -const config = { - patterns: ['**/*.js', '**/*.vue', '**/*.scss'], - /* - * The ignore patterns below are just to reduce search time with glob, as it includes the - * folders with the most ignored assets, the actual `.prettierignore` will be used later on - */ - ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'], - parsers: { - js: 'babylon', - vue: 'vue', - scss: 'css', - }, -}; +let globDir = process.argv[3] || ''; +if (globDir && globDir.charAt(globDir.length - 1) !== '/') globDir += '/'; -/* - * Unfortunately the prettier API does not expose support for `.prettierignore` files, they however - * use the ignore package, so we do the same. We simply cannot use the glob package, because - * gitignore style is not compatible with globs ignore style. - */ -prettierIgnore.add( - fs - .readFileSync(path.join(__dirname, '../../', '.prettierignore')) - .toString() - .trim() - .split(/\r?\n/) +console.log( + `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...` ); -const availableExtensions = Object.keys(config.parsers); - -console.log(`Loading ${allFiles ? 'All' : 'Selected'} Files ...`); +const globPatterns = matchExtensions.map(ext => `${globDir}**/*.${ext}`); +const matchedFiles = allFiles + ? glob.sync(`{${globPatterns.join(',')}}`, { ignore: globIgnore }) + : getStagedFiles(globPatterns); +const matchedCount = matchedFiles.length; -const stagedFiles = - allFiles || dirPath ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`)); - -if (stagedFiles) { - if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) { - console.log('No matching staged files.'); - process.exit(1); - } - console.log(`Matching staged Files : ${stagedFiles.length}`); +if (!matchedCount) { + console.log('No files found to process with prettier'); + process.exit(0); } let didWarn = false; -let didError = false; - -let files; -if (allFiles) { - const ignore = config.ignore; - const patterns = config.patterns; - const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`; - files = glob.sync(globPattern, { ignore }).filter(f => allFiles || stagedFiles.includes(f)); -} else if (dirPath) { - const ignore = config.ignore; - const patterns = config.patterns.map(item => { - return dirPath + item; - }); - const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`; - files = glob.sync(globPattern, { ignore }); -} else { - files = stagedFiles.filter(f => availableExtensions.includes(f.split('.').pop())); -} - -files = prettierIgnore.filter(files); - -if (!files.length) { - console.log('No Files found to process with Prettier'); - process.exit(1); -} - -console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`); - -files.forEach(file => { - try { - prettier - .resolveConfig(file) - .then(options => { - const fileExtension = file.split('.').pop(); - Object.assign(options, { - parser: config.parsers[fileExtension], +let passedCount = 0; +let failedCount = 0; +let ignoredCount = 0; + +console.log(`${shouldSave ? 'Updating' : 'Checking'} ${matchedCount} file(s)`); + +const fixCommand = `yarn prettier-${allFiles ? 'all' : 'staged'}-save`; +const warningMessage = ` +=============================== +GitLab uses Prettier to format all JavaScript code. +Please format each file listed below or run "${fixCommand}" +=============================== +`; + +const checkFileWithOptions = (filePath, options) => + readFileAsync(filePath, 'utf8').then(input => { + if (shouldSave) { + const output = prettier.format(input, options); + if (input === output) { + passedCount += 1; + } else { + return writeFileAsync(filePath, output, 'utf8').then(() => { + console.log(`Prettified : ${filePath}`); + failedCount += 1; }); - - const input = fs.readFileSync(file, 'utf8'); - - if (shouldSave) { - const output = prettier.format(input, options); - if (output !== input) { - fs.writeFileSync(file, output, 'utf8'); - console.log(`Prettified : ${file}`); - } - } else if (!prettier.check(input, options)) { - if (!didWarn) { - console.log( - '\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n' - ); - didWarn = true; - } - console.log(`Prettify Manually : ${file}`); + } + } else { + if (prettier.check(input, options)) { + passedCount += 1; + } else { + if (!didWarn) { + console.log(warningMessage); + didWarn = true; } - }) - .catch(e => { - console.log(`Error on loading the Config File: ${e.message}`); - process.exit(1); - }); - } catch (error) { - didError = true; - console.log(`\n\nError with ${file}: ${error.message}`); - } -}); + console.log(`Prettify Manually : ${filePath}`); + failedCount += 1; + } + } + }); -if (didWarn || didError) { - process.exit(1); -} +const checkFileWithPrettierConfig = filePath => + prettier + .getFileInfo(filePath, { ignorePath: '.prettierignore' }) + .then(({ ignored, inferredParser }) => { + if (ignored || !inferredParser) { + ignoredCount += 1; + return; + } + return prettier.resolveConfig(filePath).then(fileOptions => { + const options = { ...fileOptions, parser: inferredParser }; + return checkFileWithOptions(filePath, options); + }); + }); + +Promise.all(matchedFiles.map(checkFileWithPrettierConfig)) + .then(() => { + const failAction = shouldSave ? 'fixed' : 'failed'; + console.log( + `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n` + ); + + if (didWarn) process.exit(1); + }) + .catch(e => { + console.log(`\nAn error occured while processing files with prettier: ${e.message}\n`); + process.exit(1); + }); diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb index a5f0ec372d8..4166070f7cd 100755 --- a/scripts/review_apps/automated_cleanup.rb +++ b/scripts/review_apps/automated_cleanup.rb @@ -5,12 +5,26 @@ require_relative File.expand_path('../../lib/quality/helm_client.rb', __dir__) require_relative File.expand_path('../../lib/quality/kubernetes_client.rb', __dir__) class AutomatedCleanup - attr_reader :project_path, :gitlab_token, :cleaned_up_releases + attr_reader :project_path, :gitlab_token + + DEPLOYMENTS_PER_PAGE = 100 + HELM_RELEASES_BATCH_SIZE = 5 + IGNORED_HELM_ERRORS = [ + 'transport is closing', + 'error upgrading connection' + ].freeze + IGNORED_KUBERNETES_ERRORS = [ + 'NotFound' + ].freeze + + def self.ee? + ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md') + end def initialize(project_path: ENV['CI_PROJECT_PATH'], gitlab_token: ENV['GITLAB_BOT_REVIEW_APPS_CLEANUP_TOKEN']) @project_path = project_path @gitlab_token = gitlab_token - @cleaned_up_releases = [] + ENV['TILLER_NAMESPACE'] ||= review_apps_namespace end def gitlab @@ -25,12 +39,16 @@ class AutomatedCleanup end end + def review_apps_namespace + self.class.ee? ? 'review-apps-ee' : 'review-apps-ce' + end + def helm - @helm ||= Quality::HelmClient.new + @helm ||= Quality::HelmClient.new(namespace: review_apps_namespace) end def kubernetes - @kubernetes ||= Quality::KubernetesClient.new + @kubernetes ||= Quality::KubernetesClient.new(namespace: review_apps_namespace) end def perform_gitlab_environment_cleanup!(days_for_stop:, days_for_delete:) @@ -39,26 +57,27 @@ class AutomatedCleanup checked_environments = [] delete_threshold = threshold_time(days: days_for_delete) stop_threshold = threshold_time(days: days_for_stop) - gitlab.deployments(project_path, per_page: 50).auto_paginate do |deployment| - next unless deployment.environment.name.start_with?('review/') - next if checked_environments.include?(deployment.environment.slug) - puts + gitlab.deployments(project_path, per_page: DEPLOYMENTS_PER_PAGE).auto_paginate do |deployment| + environment = deployment.environment - checked_environments << deployment.environment.slug - deployed_at = Time.parse(deployment.created_at) + next unless environment.name.start_with?('review/') + next if checked_environments.include?(environment.slug) + + last_deploy = deployment.created_at + deployed_at = Time.parse(last_deploy) if deployed_at < delete_threshold - print_release_state(subject: 'Review app', release_name: deployment.environment.slug, release_date: deployment.created_at, action: 'deleting') - gitlab.delete_environment(project_path, deployment.environment.id) - cleaned_up_releases << deployment.environment.slug + delete_environment(environment, deployment) + release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace) + delete_helm_release(release) elsif deployed_at < stop_threshold - print_release_state(subject: 'Review app', release_name: deployment.environment.slug, release_date: deployment.created_at, action: 'stopping') - gitlab.stop_environment(project_path, deployment.environment.id) - cleaned_up_releases << deployment.environment.slug + stop_environment(environment, deployment) else - print_release_state(subject: 'Review app', release_name: deployment.environment.slug, release_date: deployment.created_at, action: 'leaving') + print_release_state(subject: 'Review app', release_name: environment.slug, release_date: last_deploy, action: 'leaving') end + + checked_environments << environment.slug end end @@ -66,25 +85,58 @@ class AutomatedCleanup puts "Checking for Helm releases not updated in the last #{days} days..." threshold_day = threshold_time(days: days) - helm.releases(args: ['--deployed', '--failed', '--date', '--reverse', '--max 25']).each do |release| - next if cleaned_up_releases.include?(release.name) - if release.last_update < threshold_day - print_release_state(subject: 'Release', release_name: release.name, release_date: release.last_update, action: 'cleaning') - helm.delete(release_name: release.name) - kubernetes.cleanup(release_name: release.name) + helm_releases.each do |release| + if release.status == 'FAILED' || release.last_update < threshold_day + delete_helm_release(release) else print_release_state(subject: 'Release', release_name: release.name, release_date: release.last_update, action: 'leaving') end end end + private + + def delete_environment(environment, deployment) + print_release_state(subject: 'Review app', release_name: environment.slug, release_date: deployment.created_at, action: 'deleting') + gitlab.delete_environment(project_path, environment.id) + end + + def stop_environment(environment, deployment) + print_release_state(subject: 'Review app', release_name: environment.slug, release_date: deployment.created_at, action: 'stopping') + gitlab.stop_environment(project_path, environment.id) + end + + def helm_releases + args = ['--all', '--date', "--max #{HELM_RELEASES_BATCH_SIZE}"] + + helm.releases(args: args) + end + + def delete_helm_release(release) + print_release_state(subject: 'Release', release_name: release.name, release_status: release.status, release_date: release.last_update, action: 'cleaning') + helm.delete(release_name: release.name) + kubernetes.cleanup(release_name: release.name) + rescue Quality::HelmClient::CommandFailedError => ex + raise ex unless ignore_exception?(ex.message, IGNORED_HELM_ERRORS) + + puts "Ignoring the following Helm error:\n#{ex}\n" + rescue Quality::KubernetesClient::CommandFailedError => ex + raise ex unless ignore_exception?(ex.message, IGNORED_KUBERNETES_ERRORS) + + puts "Ignoring the following Kubernetes error:\n#{ex}\n" + end + def threshold_time(days:) Time.now - days * 24 * 3600 end - def print_release_state(subject:, release_name:, release_date:, action:) - puts "\n#{subject} '#{release_name}' was last deployed on #{release_date}: #{action} it." + def ignore_exception?(exception_message, exceptions_ignored) + exception_message.match?(/(#{exceptions_ignored})/) + end + + def print_release_state(subject:, release_name:, release_date:, action:, release_status: nil) + puts "\n#{subject} '#{release_name}' #{"(#{release_status}) " if release_status}was last deployed on #{release_date}: #{action} it.\n" end end diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 78293464265..d372bcbdab1 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -47,15 +47,23 @@ function create_secret() { --dry-run -o json | kubectl apply -f - } +function deployExists() { + local namespace="${1}" + local deploy="${2}" + helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1 + return $? +} + function previousDeployFailed() { set +e - echo "Checking for previous deployment of $CI_ENVIRONMENT_SLUG" - deployment_status=$(helm status $CI_ENVIRONMENT_SLUG >/dev/null 2>&1) + deploy="${1}" + echo "Checking for previous deployment of ${deploy}" + deployment_status=$(helm status ${deploy} >/dev/null 2>&1) status=$? # if `status` is `0`, deployment exists, has a status if [ $status -eq 0 ]; then echo "Previous deployment found, checking status" - deployment_status=$(helm status $CI_ENVIRONMENT_SLUG | grep ^STATUS | cut -d' ' -f2) + deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2) echo "Previous deployment state: $deployment_status" if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then status=0; @@ -113,7 +121,7 @@ function deploy() { fi # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade` - if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed ; then + if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed "$CI_ENVIRONMENT_SLUG" ; then echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG" delete cleanup @@ -149,6 +157,7 @@ HELM_CMD=$(cat << EOF --set gitlab.gitlab-shell.image.tag="v$GITLAB_SHELL_VERSION" \ --set gitlab.unicorn.workhorse.image="$gitlab_workhorse_image_repository" \ --set gitlab.unicorn.workhorse.tag="$CI_COMMIT_REF_NAME" \ + --set nginx-ingress.controller.config.ssl-ciphers="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4" \ --namespace="$KUBE_NAMESPACE" \ --version="$CI_PIPELINE_ID-$CI_JOB_ID" \ "$name" \ @@ -182,3 +191,23 @@ function cleanup() { | xargs kubectl -n "$KUBE_NAMESPACE" delete \ || true } + +function install_external_dns() { + local release_name="dns-gitlab-review-app" + local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}') + + if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then + echo "Installing external-dns helm chart" + helm repo update + helm install stable/external-dns \ + -n "${release_name}" \ + --namespace "${KUBE_NAMESPACE}" \ + --set provider="aws" \ + --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \ + --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \ + --set aws.zoneType="public" \ + --set domainFilters[0]="${domain}" \ + --set txtOwnerId="${KUBE_NAMESPACE}" \ + --set rbac.create="true" + fi +} diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index d98e6ff0df8..c365988a100 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -30,6 +30,15 @@ describe Boards::IssuesController do context 'when list id is present' do context 'with valid list id' do + let(:group) { create(:group, :private, projects: [project]) } + let(:group_board) { create(:board, group: group) } + let!(:list3) { create(:list, board: group_board, label: development, position: 2) } + let(:sub_group_1) { create(:group, :private, parent: group) } + + before do + group.add_maintainer(user) + end + it 'returns issues that have the list label applied' do issue = create(:labeled_issue, project: project, labels: [planning]) create(:labeled_issue, project: project, labels: [planning]) @@ -56,6 +65,39 @@ describe Boards::IssuesController do expect { list_issues(user: user, board: board, list: list2) }.not_to exceed_query_limit(control_count) end + + it 'avoids N+1 database queries when adding a project', :request_store do + create(:labeled_issue, project: project, labels: [development]) + control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count + + 2.times do + p = create(:project, group: group) + create(:labeled_issue, project: p, labels: [development]) + end + + project_2 = create(:project, group: group) + create(:labeled_issue, project: project_2, labels: [development], assignees: [johndoe]) + + # because each issue without relative_position must be updated with + # a different value, we have 8 extra queries per issue + expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1)) + end + + it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do + create(:project, group: sub_group_1) + create(:labeled_issue, project: project, labels: [development]) + control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count + project_2 = create(:project, group: group) + + 2.times do + p = create(:project, group: sub_group_1) + create(:labeled_issue, project: p, labels: [development]) + end + + create(:labeled_issue, project: project_2, labels: [development], assignees: [johndoe]) + + expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1)) + end end context 'with invalid list id' do @@ -102,12 +144,15 @@ describe Boards::IssuesController do sign_in(user) params = { - namespace_id: project.namespace.to_param, - project_id: project, board_id: board.to_param, list_id: list.try(:to_param) } + unless board.try(:parent)&.is_a?(Group) + params[:namespace_id] = project.namespace.to_param + params[:project_id] = project + end + get :index, params.compact end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 9df77560320..80138183c07 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1028,6 +1028,13 @@ describe Projects::IssuesController do .not_to exceed_query_limit(control) end + context 'when user is setting notes filters' do + let(:issuable) { issue } + let!(:discussion_note) { create(:discussion_note_on_issue, :system, noteable: issuable, project: project) } + + it_behaves_like 'issuable notes filter' + end + context 'with cross-reference system note', :request_store do let(:new_issue) { create(:issue) } let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 73b62dc1151..dcfd6c05200 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -87,6 +87,14 @@ describe Projects::MergeRequestsController do end end + context 'when user is setting notes filters' do + let(:issuable) { merge_request } + let!(:discussion_note) { create(:discussion_note_on_merge_request, :system, noteable: issuable, project: project) } + let!(:discussion_comment) { create(:discussion_note_on_merge_request, noteable: issuable, project: project) } + + it_behaves_like 'issuable notes filter' + end + describe 'as json' do context 'with basic serializer param' do it 'renders basic MR entity as json' do @@ -838,12 +846,14 @@ describe Projects::MergeRequestsController do end context 'with a forked project' do - let(:fork_project) { create(:project, :repository, forked_from_project: project) } - let(:fork_owner) { fork_project.owner } + let(:forked_project) { fork_project(project, fork_owner, repository: true) } + let(:fork_owner) { create(:user) } before do - merge_request.update!(source_project: fork_project) - fork_project.add_reporter(user) + project.add_developer(fork_owner) + + merge_request.update!(source_project: forked_project) + forked_project.add_reporter(user) end context 'user cannot push to source branch' do diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index e48c9dea976..9ac7b8ee8a8 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -47,6 +47,37 @@ describe Projects::NotesController do get :index, request_params end + context 'when user notes_filter is present' do + let(:notes_json) { parsed_response[:notes] } + let!(:comment) { create(:note, noteable: issue, project: project) } + let!(:system_note) { create(:note, noteable: issue, project: project, system: true) } + + it 'filters system notes by comments' do + user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue) + + get :index, request_params + + expect(notes_json.count).to eq(1) + expect(notes_json.first[:id].to_i).to eq(comment.id) + end + + it 'returns all notes' do + user.set_notes_filter(UserPreference::NOTES_FILTERS[:all_notes], issue) + + get :index, request_params + + expect(notes_json.map { |note| note[:id].to_i }).to contain_exactly(comment.id, system_note.id) + end + + it 'does not merge label event notes' do + user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue) + + expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new) + + get :index, request_params + end + end + context 'for a discussion note' do let(:project) { create(:project, :repository) } let!(:note) { create(:discussion_note_on_merge_request, project: project) } diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index e0aeadeecd7..2c76c22ba69 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -119,11 +119,11 @@ FactoryBot.define do trait :codequality do file_type :codequality - file_format :gzip + file_format :raw after(:build) do |artifact, evaluator| artifact.file = fixture_file_upload( - Rails.root.join('spec/fixtures/codequality/codequality.json.gz'), 'application/x-gzip') + Rails.root.join('spec/fixtures/codequality/codequality.json'), 'application/json') end end diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index f564e7bee47..24e70913b87 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -47,5 +47,15 @@ FactoryBot.define do trait :ref_protected do access_level :ref_protected end + + trait :tagged_only do + run_untagged false + + tag_list %w(tag1 tag2) + end + + trait :locked do + locked true + end end end diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb new file mode 100644 index 00000000000..6fdada75a3d --- /dev/null +++ b/spec/factories/clusters/kubernetes_namespaces.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do + cluster + project + cluster_project + end +end diff --git a/spec/factories/clusters/projects.rb b/spec/factories/clusters/projects.rb new file mode 100644 index 00000000000..6cda77c6f85 --- /dev/null +++ b/spec/factories/clusters/projects.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :cluster_project, class: Clusters::Project do + cluster + project + end +end diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index cac56695319..90d6a338479 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -16,5 +16,10 @@ FactoryBot.define do allow(deployment.project.repository).to receive(:create_ref) end end + + trait :review_app do + sha { TestEnv::BRANCH_SHA['pages-deploy'] } + ref 'pages-deploy' + end end end diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb deleted file mode 100644 index bc59fea81ec..00000000000 --- a/spec/factories/forked_project_links.rb +++ /dev/null @@ -1,15 +0,0 @@ -FactoryBot.define do - factory :forked_project_link do - association :forked_to_project, factory: [:project, :repository] - association :forked_from_project, factory: [:project, :repository] - - after(:create) do |link| - link.forked_from_project.reload - link.forked_to_project.reload - end - - trait :forked_to_empty_project do - association :forked_to_project, factory: [:project, :repository] - end - end -end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 8094c43b065..2392bfc4a53 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -101,6 +101,20 @@ FactoryBot.define do end end + trait :deployed_review_app do + target_branch 'pages-deploy-target' + + transient do + deployment { create(:deployment, :review_app) } + end + + after(:build) do |merge_request, evaluator| + merge_request.source_branch = evaluator.deployment.ref + merge_request.source_project = evaluator.deployment.project + merge_request.target_project = evaluator.deployment.project + end + end + after(:build) do |merge_request| target_project = merge_request.target_project source_project = merge_request.source_project diff --git a/spec/factories/user_preferences.rb b/spec/factories/user_preferences.rb new file mode 100644 index 00000000000..19059a93625 --- /dev/null +++ b/spec/factories/user_preferences.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :user_preference do + user + + trait :only_comments do + issue_notes_filter { UserPreference::NOTES_FILTERS[:only_comments] } + merge_request_notes_filter { UserPreference::NOTE_FILTERS[:only_comments] } + end + end +end diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb new file mode 100644 index 00000000000..a6ca0803469 --- /dev/null +++ b/spec/features/admin/dashboard_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe 'admin visits dashboard' do + include ProjectForksHelper + + before do + sign_in(create(:admin)) + end + + context 'counting forks' do + it 'correctly counts 2 forks of a project' do + project = create(:project) + project_fork = fork_project(project) + fork_project(project_fork) + + # Make sure the fork_networks & fork_networks reltuples have been updated + # to get a correct count on postgresql + if Gitlab::Database.postgresql? + ActiveRecord::Base.connection.execute('ANALYZE fork_networks') + ActiveRecord::Base.connection.execute('ANALYZE fork_network_members') + end + + visit admin_root_path + + expect(page).to have_content('Forks 2') + end + end +end diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index e5c5ab9c039..31a1dcf826d 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -2,8 +2,12 @@ require 'spec_helper' describe 'Dashboard shortcuts', :js do context 'logged in' do + let(:user) { create(:user) } + let(:project) { create(:project) } + before do - sign_in(create(:user)) + project.add_developer(user) + sign_in(user) visit root_dashboard_path end @@ -50,6 +54,6 @@ describe 'Dashboard shortcuts', :js do end def check_page_title(title) - expect(find('.breadcrumbs-sub-title')).to have_content(title) + expect(find('.page-title')).to have_content(title) end end diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 80df0618a6a..e8ca6a6714f 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -23,17 +23,17 @@ describe 'Group milestones' do description.native.send_keys('') - click_link('Preview') + click_button('Preview') preview = find('.js-md-preview') expect(preview).to have_content('Nothing to preview.') - click_link('Write') + click_button('Write') description.native.send_keys(':+1: Nice') - click_link('Preview') + click_button('Preview') expect(preview).to have_css('gl-emoji') expect(find('#milestone_description', visible: false)).not_to be_visible diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index f76d30056da..ef5801e61e8 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -189,13 +189,21 @@ describe 'Dropdown milestone', :js do end it 'selects `no milestone`' do - click_static_milestone('No Milestone') + click_static_milestone('None') expect(page).to have_css(js_dropdown_milestone, visible: false) expect_tokens([milestone_token('none', false)]) expect_filtered_search_input_empty end + it 'selects `any milestone`' do + click_static_milestone('Any') + + expect(page).to have_css(js_dropdown_milestone, visible: false) + expect_tokens([milestone_token('any', false)]) + expect_filtered_search_input_empty + end + it 'selects `upcoming milestone`' do click_static_milestone('Upcoming') diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 5e8662100c5..687a6f1eafc 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -47,15 +47,15 @@ describe "User creates issue" do textarea = first(".gfm-form textarea") page.within(form) do - click_link("Preview") + click_button("Preview") preview = find(".js-md-preview") # this element is findable only when the "Preview" link is clicked. expect(preview).to have_content("Nothing to preview.") - click_link("Write") + click_button("Write") fill_in("Description", with: "Bug fixed :smile:") - click_link("Preview") + click_button("Preview") expect(preview).to have_css("gl-emoji") expect(textarea).not_to be_visible diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 1d9c3abc20f..60b88ef4bdf 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -17,9 +17,9 @@ describe "User edits issue", :js do page.within(form) do fill_in("Description", with: "Bug fixed :smile:") - click_link("Preview") + click_button("Preview") end - expect(form).to have_link("Write") + expect(form).to have_button("Write") end end diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb index 1ac31de62cb..9d2a94a4a41 100644 --- a/spec/features/merge_request/user_creates_mr_spec.rb +++ b/spec/features/merge_request/user_creates_mr_spec.rb @@ -28,4 +28,29 @@ describe 'Merge request > User creates MR' do it_behaves_like 'a creatable merge request' end end + + context 'source project', :js do + let(:user) { create(:user) } + let(:target_project) { create(:project, :public, :repository) } + let(:source_project) { target_project } + + before do + source_project.add_maintainer(user) + + sign_in(user) + visit project_new_merge_request_path( + target_project, + merge_request: { + source_project_id: source_project.id, + target_project_id: target_project.id + }) + end + + it 'filters source project' do + find('.js-source-project').click + find('.dropdown-source-project input').set('source') + + expect(find('.dropdown-source-project .dropdown-content')).not_to have_content(source_project.name) + end + end end diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index 8c9e782aa76..3152707136c 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -1,11 +1,13 @@ require 'rails_helper' describe 'Merge request > User edits MR' do + include ProjectForksHelper + it_behaves_like 'an editable merge request' context 'for a forked project' do it_behaves_like 'an editable merge request' do - let(:source_project) { create(:project, :repository, forked_from_project: target_project) } + let(:source_project) { fork_project(target_project, nil, repository: true) } end end end diff --git a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb index 029b66b5e8e..fc4a188d4a7 100644 --- a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb @@ -1,18 +1,20 @@ require 'rails_helper' describe 'Merge request > User sees MR from deleted forked project', :js do + include ProjectForksHelper + let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } - let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) } + let(:forked_project) { fork_project(project, nil, repository: true) } let!(:merge_request) do - create(:merge_request_with_diffs, source_project: fork_project, + create(:merge_request_with_diffs, source_project: forked_project, target_project: project, description: 'Test merge request') end before do MergeRequests::MergeService.new(project, user).execute(merge_request) - fork_project.destroy! + forked_project.destroy! sign_in(user) visit project_merge_request_path(project, merge_request) end diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb index d4ad0b0a377..a6118453540 100644 --- a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb @@ -1,18 +1,20 @@ require 'rails_helper' describe 'Merge request > User sees notes from forked project', :js do + include ProjectForksHelper + let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } - let(:fork_project) { create(:project, :public, :repository, forked_from_project: project) } + let(:forked_project) { fork_project(project, nil, repository: true) } let!(:merge_request) do - create(:merge_request_with_diffs, source_project: fork_project, + create(:merge_request_with_diffs, source_project: forked_project, target_project: project, description: 'Test merge request') end before do create(:note_on_commit, note: 'A commit comment', - project: fork_project, + project: forked_project, commit_id: merge_request.commit_shas.first) sign_in(user) end diff --git a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb index d30dcefc6aa..97093bbc2f6 100644 --- a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb @@ -1,17 +1,19 @@ require 'rails_helper' describe 'Merge request > User sees pipelines from forked project', :js do + include ProjectForksHelper + let(:target_project) { create(:project, :public, :repository) } let(:user) { target_project.creator } - let(:fork_project) { create(:project, :repository, forked_from_project: target_project) } + let(:forked_project) { fork_project(target_project, nil, repository: true) } let!(:merge_request) do - create(:merge_request_with_diffs, source_project: fork_project, + create(:merge_request_with_diffs, source_project: forked_project, target_project: target_project, description: 'Test merge request') end let(:pipeline) do create(:ci_pipeline, - project: fork_project, + project: forked_project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) end diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 6ac495aa03d..71022c6bb08 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -41,7 +41,7 @@ describe 'User views an open merge request' do find('.gfm-form').fill_in(:merge_request_description, with: '') page.within('.gfm-form') do - click_link('Preview') + click_button('Preview') expect(find('.js-md-preview')).to have_content('Nothing to preview.') end @@ -51,12 +51,12 @@ describe 'User views an open merge request' do find('.gfm-form').fill_in(:merge_request_description, with: ':+1: Nice') page.within('.gfm-form') do - click_link('Preview') + click_button('Preview') expect(find('.js-md-preview')).to have_css('gl-emoji') end - expect(find('.gfm-form')).to have_css('.js-md-preview').and have_link('Write') + expect(find('.gfm-form')).to have_css('.js-md-preview').and have_button('Write') expect(find('#merge_request_description', visible: false)).not_to be_visible end end diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb index 6397df086a7..29442a58ea4 100644 --- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb +++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb @@ -28,19 +28,19 @@ describe "User adds a comment on a commit", :js do fill_in("note[note]", with: "#{comment_text} #{emoji}") # Check on `Preview` tab - click_link("Preview") + click_button("Preview") expect(find(".js-md-preview")).to have_content(comment_text).and have_css("gl-emoji") expect(page).not_to have_css(".js-note-text") # Check on the `Write` tab - click_link("Write") + click_button("Write") expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji}") # Submit comment from the `Preview` tab to get rid of a separate `it` block # which would specially tests if everything gets cleared from the note form. - click_link("Preview") + click_button("Preview") click_button("Comment") end @@ -88,13 +88,13 @@ describe "User adds a comment on a commit", :js do # Test Preview feature for both forms. page.within("form[data-line-code='#{sample_commit.line_code}']") do - click_link("Preview") + click_button("Preview") end page.within("form[data-line-code='#{sample_commit.del_line_code}']") do fill_in("note[note]", with: another_comment_text) - click_link("Preview") + click_button("Preview") end expect(page).to have_css(".js-md-preview", visible: true, count: 2) diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb index 6397a8ad845..73ce8d2b996 100644 --- a/spec/features/projects/commit/user_comments_on_commit_spec.rb +++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb @@ -25,19 +25,19 @@ describe "User comments on commit", :js do fill_in("note[note]", with: "#{comment_text} #{emoji_code}") # Check on `Preview` tab - click_link("Preview") + click_button("Preview") expect(find(".js-md-preview")).to have_content(comment_text).and have_css("gl-emoji") expect(page).not_to have_css(".js-note-text") # Check on `Write` tab - click_link("Write") + click_button("Write") expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji_code}") # Submit comment from the `Preview` tab to get rid of a separate `it` block # which would specially tests if everything gets cleared from the note form. - click_link("Preview") + click_button("Preview") click_button("Comment") end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 7f5c3ff6ed8..c3902ecdd17 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -151,9 +151,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end it 'renders escaped tooltip name' do - page.within('aside.right-sidebar') do - expect(find('.active.build-job a')['data-original-title']).to eq('<img src=x onerror=alert(document.domain)> - passed') - end + page.find('.active.build-job a').hover + expect(page).to have_content('<img src=x onerror=alert(document.domain)> - passed') end end @@ -373,17 +372,15 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do context 'when job starts environment', :js do let(:environment) { create(:environment, name: 'production', project: project) } + before do + visit project_job_path(project, build) + wait_for_requests + end + context 'job is successful and has deployment' do let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline) } let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) } - before do - visit project_job_path(project, build) - wait_for_requests - # scroll to the top of the page first - execute_script "window.scrollTo(0,0)" - end - it 'shows a link for the job' do expect(page).to have_link environment.name end @@ -398,11 +395,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do let(:build) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) } it 'shows a link for the job' do - visit project_job_path(project, build) - wait_for_requests - # scroll to the top of the page first - execute_script "window.scrollTo(0,0)" - expect(page).to have_link environment.name expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}") end @@ -412,11 +404,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } it 'shows a link to latest deployment' do - visit project_job_path(project, build) - wait_for_all_requests - # scroll to the top of the page first - execute_script "window.scrollTo(0,0)" - expect(page).to have_link environment.name expect(page).to have_content 'This job is creating a deployment' expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}") @@ -453,8 +440,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do before do visit project_job_path(project, job) wait_for_requests - # scroll to the top of the page first - execute_script "window.scrollTo(0,0)" end context 'job with outdated deployment' do @@ -484,8 +469,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do it 'shows deployment message' do expected_text = 'The deployment of this job to staging did not succeed.' - expect(page).to have_css( - '.environment-information', text: expected_text) + expect(page).to have_css('.environment-information', text: expected_text) end end @@ -498,8 +482,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do it 'shows deployment message' do expected_text = 'This job is creating a deployment to staging' - expect(page).to have_css( - '.environment-information', text: expected_text) + expect(page).to have_css('.environment-information', text: expected_text) expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}") end @@ -509,10 +492,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do it 'shows that deployment will be overwritten' do expected_text = 'This job is creating a deployment to staging' - expect(page).to have_css( - '.environment-information', text: expected_text) - expect(page).to have_css( - '.environment-information', text: 'latest deployment') + expect(page).to have_css('.environment-information', text: expected_text) + expect(page).to have_css('.environment-information', text: 'latest deployment') expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}") end end @@ -594,7 +575,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end it 'shows delayed job', :js do - expect(page).to have_content('This is a scheduled to run in') + expect(page).to have_content('This is a delayed to run in') expect(page).to have_content("This job will automatically run after it's timer finishes.") expect(page).to have_link('Unschedule job') end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 491c64fc329..cd6c37bf54d 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -68,6 +68,10 @@ describe 'Pipeline', :js do expect(page).to have_css('#js-tab-pipeline.active') end + it 'shows link to the pipeline ref' do + expect(page).to have_link(pipeline.ref) + end + it_behaves_like 'showing user status' do let(:user_with_status) { pipeline.user } @@ -236,6 +240,20 @@ describe 'Pipeline', :js do it { expect(page).not_to have_content('Cancel running') } end end + + context 'when pipeline ref does not exist in repository anymore' do + let(:pipeline) do + create(:ci_empty_pipeline, project: project, + ref: 'non-existent', + sha: project.commit.id, + user: user) + end + + it 'does not render link to the pipeline ref' do + expect(page).not_to have_link(pipeline.ref) + expect(page).to have_content(pipeline.ref) + end + end end context 'when user does not have access to read jobs' do diff --git a/spec/finders/applications_finder_spec.rb b/spec/finders/applications_finder_spec.rb new file mode 100644 index 00000000000..14d6b35cc27 --- /dev/null +++ b/spec/finders/applications_finder_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ApplicationsFinder do + let(:application1) { create(:application, name: 'some_application', owner: nil, redirect_uri: 'http://some_application.url', scopes: '') } + let(:application2) { create(:application, name: 'another_application', owner: nil, redirect_uri: 'http://other_application.url', scopes: '') } + + describe '#execute' do + it 'returns an array of applications' do + found = described_class.new.execute + + expect(found).to match_array([application1, application2]) + end + it 'returns the application by id' do + params = { id: application1.id } + found = described_class.new(params).execute + + expect(found).to match(application1) + end + end +end diff --git a/spec/finders/fork_projects_finder_spec.rb b/spec/finders/fork_projects_finder_spec.rb index f0cef7ea406..b3fdffc3331 100644 --- a/spec/finders/fork_projects_finder_spec.rb +++ b/spec/finders/fork_projects_finder_spec.rb @@ -1,20 +1,21 @@ require 'spec_helper' describe ForkProjectsFinder do - let(:source_project) { create(:project, :empty_repo) } - let(:private_fork) { create(:project, :private, :empty_repo, name: 'A') } - let(:internal_fork) { create(:project, :internal, :empty_repo, name: 'B') } - let(:public_fork) { create(:project, :public, :empty_repo, name: 'C') } + include ProjectForksHelper + + let(:source_project) { create(:project, :public, :empty_repo) } + let(:private_fork) { fork_project(source_project, nil, name: 'A') } + let(:internal_fork) { fork_project(source_project, nil, name: 'B') } + let(:public_fork) { fork_project(source_project, nil, name: 'C') } let(:non_member) { create(:user) } let(:private_fork_member) { create(:user) } before do + private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) private_fork.add_developer(private_fork_member) - source_project.forks << private_fork - source_project.forks << internal_fork - source_project.forks << public_fork + internal_fork.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) end describe '#execute' do diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index b776e9d856a..de9974c45e1 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -9,6 +9,27 @@ describe NotesFinder do end describe '#execute' do + context 'when notes filter is present' do + let!(:comment) { create(:note_on_issue, project: project) } + let!(:system_note) { create(:note_on_issue, project: project, system: true) } + + it 'filters system notes' do + finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments]) + + notes = finder.execute + + expect(notes).to match_array(comment) + end + + it 'gets all notes' do + finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity]) + + notes = finder.execute + + expect(notes).to match_array([comment, system_note]) + end + end + it 'finds notes on merge requests' do create(:note_on_merge_request, project: project) diff --git a/spec/finders/user_finder_spec.rb b/spec/finders/user_finder_spec.rb index e53aa50dd33..4771b878b8e 100644 --- a/spec/finders/user_finder_spec.rb +++ b/spec/finders/user_finder_spec.rb @@ -3,40 +3,176 @@ require 'spec_helper' describe UserFinder do - describe '#execute' do + set(:user) { create(:user) } + + describe '#find_by_id' do + context 'when the user exists' do + it 'returns the user' do + found = described_class.new(user.id).find_by_id + + expect(found).to eq(user) + end + end + + context 'when the user exists (id as string)' do + it 'returns the user' do + found = described_class.new(user.id.to_s).find_by_id + + expect(found).to eq(user) + end + end + + context 'when the user does not exist' do + it 'returns nil' do + found = described_class.new(1).find_by_id + + expect(found).to be_nil + end + end + end + + describe '#find_by_username' do context 'when the user exists' do it 'returns the user' do - user = create(:user) - found = described_class.new(id: user.id).execute + found = described_class.new(user.username).find_by_username + + expect(found).to eq(user) + end + end + + context 'when the user does not exist' do + it 'returns nil' do + found = described_class.new("non_existent_username").find_by_username + + expect(found).to be_nil + end + end + end + + describe '#find_by_id_or_username' do + context 'when the user exists (id)' do + it 'returns the user' do + found = described_class.new(user.id).find_by_id_or_username + + expect(found).to eq(user) + end + end + + context 'when the user exists (id as string)' do + it 'returns the user' do + found = described_class.new(user.id.to_s).find_by_id_or_username expect(found).to eq(user) end end + context 'when the user exists (username)' do + it 'returns the user' do + found = described_class.new(user.username).find_by_id_or_username + + expect(found).to eq(user) + end + end + + context 'when the user does not exist (username)' do + it 'returns nil' do + found = described_class.new("non_existent_username").find_by_id_or_username + + expect(found).to be_nil + end + end + context 'when the user does not exist' do it 'returns nil' do - found = described_class.new(id: 1).execute + found = described_class.new(1).find_by_id_or_username expect(found).to be_nil end end end - describe '#execute!' do + describe '#find_by_id!' do + context 'when the user exists' do + it 'returns the user' do + found = described_class.new(user.id).find_by_id! + + expect(found).to eq(user) + end + end + + context 'when the user exists (id as string)' do + it 'returns the user' do + found = described_class.new(user.id.to_s).find_by_id! + + expect(found).to eq(user) + end + end + + context 'when the user does not exist' do + it 'raises ActiveRecord::RecordNotFound' do + finder = described_class.new(1) + + expect { finder.find_by_id! }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe '#find_by_username!' do context 'when the user exists' do it 'returns the user' do - user = create(:user) - found = described_class.new(id: user.id).execute! + found = described_class.new(user.username).find_by_username! + + expect(found).to eq(user) + end + end + + context 'when the user does not exist' do + it 'raises ActiveRecord::RecordNotFound' do + finder = described_class.new("non_existent_username") + + expect { finder.find_by_username! }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe '#find_by_id_or_username!' do + context 'when the user exists (id)' do + it 'returns the user' do + found = described_class.new(user.id).find_by_id_or_username! + + expect(found).to eq(user) + end + end + + context 'when the user exists (id as string)' do + it 'returns the user' do + found = described_class.new(user.id.to_s).find_by_id_or_username! expect(found).to eq(user) end end + context 'when the user exists (username)' do + it 'returns the user' do + found = described_class.new(user.username).find_by_id_or_username! + + expect(found).to eq(user) + end + end + + context 'when the user does not exist (username)' do + it 'raises ActiveRecord::RecordNotFound' do + finder = described_class.new("non_existent_username") + + expect { finder.find_by_id_or_username! }.to raise_error(ActiveRecord::RecordNotFound) + end + end + context 'when the user does not exist' do it 'raises ActiveRecord::RecordNotFound' do - finder = described_class.new(id: 1) + finder = described_class.new(1) - expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) + expect { finder.find_by_id_or_username! }.to raise_error(ActiveRecord::RecordNotFound) end end end diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb index 4249c52c481..fecf97dc641 100644 --- a/spec/finders/users_finder_spec.rb +++ b/spec/finders/users_finder_spec.rb @@ -22,6 +22,12 @@ describe UsersFinder do expect(users).to contain_exactly(user1) end + it 'filters by username (case insensitive)' do + users = described_class.new(user, username: 'joHNdoE').execute + + expect(users).to contain_exactly(user1) + end + it 'filters by search' do users = described_class.new(user, search: 'orando').execute diff --git a/spec/fixtures/security-reports/feature-branch.zip b/spec/fixtures/security-reports/feature-branch.zip Binary files differnew file mode 100644 index 00000000000..730ce3dc5f8 --- /dev/null +++ b/spec/fixtures/security-reports/feature-branch.zip diff --git a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json new file mode 100644 index 00000000000..9840382df6f --- /dev/null +++ b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json @@ -0,0 +1,18 @@ +{ + "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583", + "unapproved": [ + "CVE-2017-15650" + ], + "vulnerabilities": [ + { + "featurename": "musl", + "featureversion": "1.1.14-r15", + "vulnerability": "CVE-2017-15650", + "namespace": "alpine:v3.4", + "description": "", + "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650", + "severity": "Medium", + "fixedby": "1.1.14-r16" + } + ] +} diff --git a/spec/fixtures/security-reports/feature-branch/gl-dast-report.json b/spec/fixtures/security-reports/feature-branch/gl-dast-report.json new file mode 100644 index 00000000000..3a308bf047e --- /dev/null +++ b/spec/fixtures/security-reports/feature-branch/gl-dast-report.json @@ -0,0 +1,40 @@ +{ + "site": { + "alerts": [ + { + "sourceid": "3", + "wascid": "15", + "cweid": "16", + "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>", + "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>", + "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>", + "count": "2", + "pluginid": "10021", + "alert": "X-Content-Type-Options Header Missing", + "name": "X-Content-Type-Options Header Missing", + "riskcode": "1", + "confidence": "2", + "riskdesc": "Low (Medium)", + "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>", + "instances": [ + { + "param": "X-Content-Type-Options", + "method": "GET", + "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" + }, + { + "param": "X-Content-Type-Options", + "method": "GET", + "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/" + } + ] + } + ], + "@ssl": "false", + "@port": "80", + "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io", + "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" + }, + "@generated": "Fri, 13 Apr 2018 09:22:01", + "@version": "2.7.0" +} diff --git a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json new file mode 100644 index 00000000000..4b47e259c0f --- /dev/null +++ b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json @@ -0,0 +1,46 @@ +[ + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2012-4387", + "url": "http://struts.apache.org/docs/s2-011.html", + "message": "Long parameter name DoS for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + }, + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2013-1966", + "url": "http://struts.apache.org/docs/s2-014.html", + "message": "Remote command execution due to flaw in the includeParams attribute of URL and Anchor tags for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + }, + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2013-2115", + "url": "http://struts.apache.org/docs/s2-014.html", + "message": "Remote command execution due to flaw in the includeParams attribute of URL and Anchor tags for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + }, + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2013-2134", + "url": "http://struts.apache.org/docs/s2-015.html", + "message": "Arbitrary OGNL code execution via unsanitized wildcard matching for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + } +] diff --git a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json new file mode 100644 index 00000000000..c1d20fa02fa --- /dev/null +++ b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json @@ -0,0 +1,242 @@ +{ + "licenses": [ + { + "count": 13, + "name": "MIT" + }, + { + "count": 2, + "name": "New BSD" + }, + { + "count": 1, + "name": "LGPL" + } + ], + "dependencies": [ + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "bundler", + "url": "http://bundler.io", + "description": "The best way to manage your application's dependencies", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "concurrent-ruby", + "url": "http://www.concurrent-ruby.com", + "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "connection_pool", + "url": "https://github.com/mperham/connection_pool", + "description": "Generic connection pool for Ruby", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "mini_portile2", + "url": "http://github.com/flavorjones/mini_portile", + "description": "Simplistic port-like solution for developers", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "mustermann", + "url": "https://github.com/sinatra/mustermann", + "description": "Your personal string matching expert.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "nokogiri", + "url": "http://nokogiri.org", + "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "New BSD", + "url": "http://opensource.org/licenses/BSD-3-Clause" + }, + "dependency": { + "name": "pg", + "url": "https://bitbucket.org/ged/ruby-pg", + "description": "Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/]", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "New BSD", + "url": "http://opensource.org/licenses/BSD-3-Clause" + }, + "dependency": { + "name": "puma", + "url": "http://puma.io", + "description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "rack", + "url": "https://rack.github.io/", + "description": "a modular Ruby webserver interface", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "rack-protection", + "url": "http://github.com/sinatra/sinatra/tree/master/rack-protection", + "description": "Protect against typical web attacks, works with all Rack apps, including Rails.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "redis", + "url": "https://github.com/redis/redis-rb", + "description": "A Ruby client library for Redis", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "LGPL", + "url": "http://www.gnu.org/licenses/lgpl.txt" + }, + "dependency": { + "name": "sidekiq", + "url": "http://sidekiq.org", + "description": "Simple, efficient background processing for Ruby", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "sinatra", + "url": "http://www.sinatrarb.com/", + "description": "Classy web-development dressed in a DSL", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "slim", + "url": "http://slim-lang.com/", + "description": "Slim is a template language.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "temple", + "url": "https://github.com/judofyr/temple", + "description": "Template compilation framework in Ruby", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "tilt", + "url": "http://github.com/rtomayko/tilt/", + "description": "Generic interface to multiple Ruby template engines", + "pathes": [ + "." + ] + } + } + ] +} diff --git a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json new file mode 100644 index 00000000000..a85b9be8b5f --- /dev/null +++ b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json @@ -0,0 +1,944 @@ +[ + { + "category": "sast", + "message": "Probable insecure usage of temp file/directory.", + "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-tmp.py", + "start_line": 1, + "end_line": 1 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B108", + "value": "B108", + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" + } + ], + "priority": "Medium", + "file": "python/hardcoded/hardcoded-tmp.py", + "line": 1, + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", + "tool": "bandit" + }, + { + "category": "sast", + "name": "Predictable pseudorandom number generator", + "message": "Predictable pseudorandom number generator", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 47, + "end_line": 47, + "class": "com.gitlab.security_products.tests.App", + "method": "generateSecretToken2" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-PREDICTABLE_RANDOM", + "value": "PREDICTABLE_RANDOM", + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 47, + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "name": "Predictable pseudorandom number generator", + "message": "Predictable pseudorandom number generator", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 41, + "end_line": 41, + "class": "com.gitlab.security_products.tests.App", + "method": "generateSecretToken1" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-PREDICTABLE_RANDOM", + "value": "PREDICTABLE_RANDOM", + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 41, + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 11, + "end_line": 11 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 11, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 12, + "end_line": 12 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 12, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 13, + "end_line": 13 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 13, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 14, + "end_line": 14 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 14, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Pickle library appears to be in use, possible security issue.", + "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 15, + "end_line": 15 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B301", + "value": "B301" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 15, + "tool": "bandit" + }, + { + "category": "sast", + "name": "ECB mode is insecure", + "message": "ECB mode is insecure", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 29, + "end_line": 29, + "class": "com.gitlab.security_products.tests.App", + "method": "insecureCypher" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-ECB_MODE", + "value": "ECB_MODE", + "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 29, + "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "name": "Cipher with no integrity", + "message": "Cipher with no integrity", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 29, + "end_line": 29, + "class": "com.gitlab.security_products.tests.App", + "method": "insecureCypher" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-CIPHER_INTEGRITY", + "value": "CIPHER_INTEGRITY", + "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 29, + "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "message": "Probable insecure usage of temp file/directory.", + "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-tmp.py", + "start_line": 14, + "end_line": 14 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B108", + "value": "B108", + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" + } + ], + "priority": "Medium", + "file": "python/hardcoded/hardcoded-tmp.py", + "line": 14, + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Probable insecure usage of temp file/directory.", + "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-tmp.py", + "start_line": 10, + "end_line": 10 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B108", + "value": "B108", + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" + } + ], + "priority": "Medium", + "file": "python/hardcoded/hardcoded-tmp.py", + "line": 10, + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with Popen module.", + "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 1, + "end_line": 1 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 1, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with pickle module.", + "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports.py", + "start_line": 2, + "end_line": 2 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports.py", + "line": 2, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with subprocess module.", + "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports.py", + "start_line": 4, + "end_line": 4 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports.py", + "line": 4, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'blerg'", + "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 22, + "end_line": 22 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B106", + "value": "B106", + "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 22, + "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'root'", + "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 5, + "end_line": 5 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 5, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: ''", + "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 9, + "end_line": 9 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 9, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'", + "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 13, + "end_line": 13 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 13, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'blerg'", + "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 23, + "end_line": 23 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 23, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'blerg'", + "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 24, + "end_line": 24 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 24, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with subprocess module.", + "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-function.py", + "start_line": 4, + "end_line": 4 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-function.py", + "line": 4, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with pickle module.", + "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-function.py", + "start_line": 2, + "end_line": 2 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports-function.py", + "line": 2, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with Popen module.", + "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-from.py", + "start_line": 7, + "end_line": 7 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-from.py", + "line": 7, + "tool": "bandit" + }, + { + "category": "sast", + "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell", + "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 9, + "end_line": 9 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B602", + "value": "B602", + "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 9, + "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with subprocess module.", + "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-from.py", + "start_line": 6, + "end_line": 6 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-from.py", + "line": 6, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with Popen module.", + "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-from.py", + "start_line": 1, + "end_line": 2 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-from.py", + "line": 1, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with pickle module.", + "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 7, + "end_line": 8 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 7, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with loads module.", + "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 6, + "end_line": 6 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 6, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)", + "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120", + "confidence": "Low", + "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "c/subdir/utils.c", + "start_line": 4 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-119", + "value": "119", + "url": "https://cwe.mitre.org/data/definitions/119.html" + }, + { + "type": "cwe", + "name": "CWE-120", + "value": "120", + "url": "https://cwe.mitre.org/data/definitions/120.html" + } + ], + "file": "c/subdir/utils.c", + "line": 4, + "url": "https://cwe.mitre.org/data/definitions/119.html", + "tool": "flawfinder" + }, + { + "category": "sast", + "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)", + "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362", + "confidence": "Low", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "c/subdir/utils.c", + "start_line": 8 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-362", + "value": "362", + "url": "https://cwe.mitre.org/data/definitions/362.html" + } + ], + "file": "c/subdir/utils.c", + "line": 8, + "url": "https://cwe.mitre.org/data/definitions/362.html", + "tool": "flawfinder" + }, + { + "category": "sast", + "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)", + "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120", + "confidence": "Low", + "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "cplusplus/src/hello.cpp", + "start_line": 6 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-119", + "value": "119", + "url": "https://cwe.mitre.org/data/definitions/119.html" + }, + { + "type": "cwe", + "name": "CWE-120", + "value": "120", + "url": "https://cwe.mitre.org/data/definitions/120.html" + } + ], + "file": "cplusplus/src/hello.cpp", + "line": 6, + "url": "https://cwe.mitre.org/data/definitions/119.html", + "tool": "flawfinder" + }, + { + "category": "sast", + "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)", + "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120", + "confidence": "Low", + "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "cplusplus/src/hello.cpp", + "start_line": 7 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-120", + "value": "120", + "url": "https://cwe.mitre.org/data/definitions/120.html" + } + ], + "file": "cplusplus/src/hello.cpp", + "line": 7, + "url": "https://cwe.mitre.org/data/definitions/120.html", + "tool": "flawfinder" + } +] diff --git a/spec/fixtures/security-reports/master.zip b/spec/fixtures/security-reports/master.zip Binary files differnew file mode 100644 index 00000000000..4684aecb738 --- /dev/null +++ b/spec/fixtures/security-reports/master.zip diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json new file mode 100644 index 00000000000..500c19e3abb --- /dev/null +++ b/spec/fixtures/security-reports/master/gl-container-scanning-report.json @@ -0,0 +1,18 @@ +{ + "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583", + "unapproved": [ + "CVE-2017-15651" + ], + "vulnerabilities": [ + { + "featurename": "musl", + "featureversion": "1.1.14-r15", + "vulnerability": "CVE-2017-15651", + "namespace": "alpine:v3.4", + "description": "", + "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15651", + "severity": "Medium", + "fixedby": "1.1.14-r16" + } + ] +} diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json new file mode 100644 index 00000000000..3a308bf047e --- /dev/null +++ b/spec/fixtures/security-reports/master/gl-dast-report.json @@ -0,0 +1,40 @@ +{ + "site": { + "alerts": [ + { + "sourceid": "3", + "wascid": "15", + "cweid": "16", + "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>", + "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>", + "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>", + "count": "2", + "pluginid": "10021", + "alert": "X-Content-Type-Options Header Missing", + "name": "X-Content-Type-Options Header Missing", + "riskcode": "1", + "confidence": "2", + "riskdesc": "Low (Medium)", + "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>", + "instances": [ + { + "param": "X-Content-Type-Options", + "method": "GET", + "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" + }, + { + "param": "X-Content-Type-Options", + "method": "GET", + "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/" + } + ] + } + ], + "@ssl": "false", + "@port": "80", + "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io", + "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io" + }, + "@generated": "Fri, 13 Apr 2018 09:22:01", + "@version": "2.7.0" +} diff --git a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json new file mode 100644 index 00000000000..b4e4e8e7dd5 --- /dev/null +++ b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json @@ -0,0 +1,35 @@ +[ + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2012-4386", + "url": "http://struts.apache.org/docs/s2-010.html", + "message": "CSRF protection bypass for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + }, + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2012-4387", + "url": "http://struts.apache.org/docs/s2-011.html", + "message": "Long parameter name DoS for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + }, + { + "priority": "Unknown", + "file": "pom.xml", + "cve": "CVE-2013-1966", + "url": "http://struts.apache.org/docs/s2-014.html", + "message": "Remote command execution due to flaw in the includeParams attribute of URL and Anchor tags for org.apache.struts/struts2-core", + "tools": [ + "gemnasium" + ], + "tool": "gemnasium" + } +] diff --git a/spec/fixtures/security-reports/master/gl-license-management-report.json b/spec/fixtures/security-reports/master/gl-license-management-report.json new file mode 100644 index 00000000000..fe91e4fb7ee --- /dev/null +++ b/spec/fixtures/security-reports/master/gl-license-management-report.json @@ -0,0 +1,150 @@ +{ + "licenses": [ + { + "count": 10, + "name": "MIT" + } + ], + "dependencies": [ + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "mini_portile2", + "url": "http://github.com/flavorjones/mini_portile", + "description": "Simplistic port-like solution for developers", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "mustermann", + "url": "https://github.com/sinatra/mustermann", + "description": "Your personal string matching expert.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "nokogiri", + "url": "http://nokogiri.org", + "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "rack", + "url": "https://rack.github.io/", + "description": "a modular Ruby webserver interface", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "rack-protection", + "url": "http://github.com/sinatra/sinatra/tree/master/rack-protection", + "description": "Protect against typical web attacks, works with all Rack apps, including Rails.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "redis", + "url": "https://github.com/redis/redis-rb", + "description": "A Ruby client library for Redis", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "sinatra", + "url": "http://www.sinatrarb.com/", + "description": "Classy web-development dressed in a DSL", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "slim", + "url": "http://slim-lang.com/", + "description": "Slim is a template language.", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "temple", + "url": "https://github.com/judofyr/temple", + "description": "Template compilation framework in Ruby", + "pathes": [ + "." + ] + } + }, + { + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/mit-license" + }, + "dependency": { + "name": "tilt", + "url": "http://github.com/rtomayko/tilt/", + "description": "Generic interface to multiple Ruby template engines", + "pathes": [ + "." + ] + } + } + ] +} diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json new file mode 100644 index 00000000000..a85b9be8b5f --- /dev/null +++ b/spec/fixtures/security-reports/master/gl-sast-report.json @@ -0,0 +1,944 @@ +[ + { + "category": "sast", + "message": "Probable insecure usage of temp file/directory.", + "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-tmp.py", + "start_line": 1, + "end_line": 1 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B108", + "value": "B108", + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" + } + ], + "priority": "Medium", + "file": "python/hardcoded/hardcoded-tmp.py", + "line": 1, + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", + "tool": "bandit" + }, + { + "category": "sast", + "name": "Predictable pseudorandom number generator", + "message": "Predictable pseudorandom number generator", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 47, + "end_line": 47, + "class": "com.gitlab.security_products.tests.App", + "method": "generateSecretToken2" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-PREDICTABLE_RANDOM", + "value": "PREDICTABLE_RANDOM", + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 47, + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "name": "Predictable pseudorandom number generator", + "message": "Predictable pseudorandom number generator", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 41, + "end_line": 41, + "class": "com.gitlab.security_products.tests.App", + "method": "generateSecretToken1" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-PREDICTABLE_RANDOM", + "value": "PREDICTABLE_RANDOM", + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 41, + "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 11, + "end_line": 11 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 11, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 12, + "end_line": 12 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 12, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 13, + "end_line": 13 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 13, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Use of insecure MD2, MD4, or MD5 hash function.", + "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 14, + "end_line": 14 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B303", + "value": "B303" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 14, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Pickle library appears to be in use, possible security issue.", + "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 15, + "end_line": 15 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B301", + "value": "B301" + } + ], + "priority": "Medium", + "file": "python/imports/imports-aliases.py", + "line": 15, + "tool": "bandit" + }, + { + "category": "sast", + "name": "ECB mode is insecure", + "message": "ECB mode is insecure", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 29, + "end_line": 29, + "class": "com.gitlab.security_products.tests.App", + "method": "insecureCypher" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-ECB_MODE", + "value": "ECB_MODE", + "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 29, + "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "name": "Cipher with no integrity", + "message": "Cipher with no integrity", + "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY", + "severity": "Medium", + "confidence": "High", + "scanner": { + "id": "find_sec_bugs", + "name": "Find Security Bugs" + }, + "location": { + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "start_line": 29, + "end_line": 29, + "class": "com.gitlab.security_products.tests.App", + "method": "insecureCypher" + }, + "identifiers": [ + { + "type": "find_sec_bugs_type", + "name": "Find Security Bugs-CIPHER_INTEGRITY", + "value": "CIPHER_INTEGRITY", + "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY" + } + ], + "priority": "Medium", + "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy", + "line": 29, + "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY", + "tool": "find_sec_bugs" + }, + { + "category": "sast", + "message": "Probable insecure usage of temp file/directory.", + "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-tmp.py", + "start_line": 14, + "end_line": 14 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B108", + "value": "B108", + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" + } + ], + "priority": "Medium", + "file": "python/hardcoded/hardcoded-tmp.py", + "line": 14, + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Probable insecure usage of temp file/directory.", + "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108", + "severity": "Medium", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-tmp.py", + "start_line": 10, + "end_line": 10 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B108", + "value": "B108", + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" + } + ], + "priority": "Medium", + "file": "python/hardcoded/hardcoded-tmp.py", + "line": 10, + "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with Popen module.", + "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 1, + "end_line": 1 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 1, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with pickle module.", + "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports.py", + "start_line": 2, + "end_line": 2 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports.py", + "line": 2, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with subprocess module.", + "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports.py", + "start_line": 4, + "end_line": 4 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports.py", + "line": 4, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'blerg'", + "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 22, + "end_line": 22 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B106", + "value": "B106", + "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 22, + "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'root'", + "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 5, + "end_line": 5 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 5, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: ''", + "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 9, + "end_line": 9 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 9, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'", + "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 13, + "end_line": 13 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 13, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'blerg'", + "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 23, + "end_line": 23 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 23, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Possible hardcoded password: 'blerg'", + "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105", + "severity": "Low", + "confidence": "Medium", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/hardcoded/hardcoded-passwords.py", + "start_line": 24, + "end_line": 24 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B105", + "value": "B105", + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html" + } + ], + "priority": "Low", + "file": "python/hardcoded/hardcoded-passwords.py", + "line": 24, + "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with subprocess module.", + "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-function.py", + "start_line": 4, + "end_line": 4 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-function.py", + "line": 4, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with pickle module.", + "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-function.py", + "start_line": 2, + "end_line": 2 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports-function.py", + "line": 2, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with Popen module.", + "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-from.py", + "start_line": 7, + "end_line": 7 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-from.py", + "line": 7, + "tool": "bandit" + }, + { + "category": "sast", + "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell", + "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 9, + "end_line": 9 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B602", + "value": "B602", + "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 9, + "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html", + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with subprocess module.", + "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-from.py", + "start_line": 6, + "end_line": 6 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-from.py", + "line": 6, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with Popen module.", + "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-from.py", + "start_line": 1, + "end_line": 2 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B404", + "value": "B404" + } + ], + "priority": "Low", + "file": "python/imports/imports-from.py", + "line": 1, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with pickle module.", + "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 7, + "end_line": 8 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 7, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Consider possible security implications associated with loads module.", + "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403", + "severity": "Low", + "confidence": "High", + "scanner": { + "id": "bandit", + "name": "Bandit" + }, + "location": { + "file": "python/imports/imports-aliases.py", + "start_line": 6, + "end_line": 6 + }, + "identifiers": [ + { + "type": "bandit_test_id", + "name": "Bandit Test ID B403", + "value": "B403" + } + ], + "priority": "Low", + "file": "python/imports/imports-aliases.py", + "line": 6, + "tool": "bandit" + }, + { + "category": "sast", + "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)", + "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120", + "confidence": "Low", + "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "c/subdir/utils.c", + "start_line": 4 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-119", + "value": "119", + "url": "https://cwe.mitre.org/data/definitions/119.html" + }, + { + "type": "cwe", + "name": "CWE-120", + "value": "120", + "url": "https://cwe.mitre.org/data/definitions/120.html" + } + ], + "file": "c/subdir/utils.c", + "line": 4, + "url": "https://cwe.mitre.org/data/definitions/119.html", + "tool": "flawfinder" + }, + { + "category": "sast", + "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)", + "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362", + "confidence": "Low", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "c/subdir/utils.c", + "start_line": 8 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-362", + "value": "362", + "url": "https://cwe.mitre.org/data/definitions/362.html" + } + ], + "file": "c/subdir/utils.c", + "line": 8, + "url": "https://cwe.mitre.org/data/definitions/362.html", + "tool": "flawfinder" + }, + { + "category": "sast", + "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)", + "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120", + "confidence": "Low", + "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "cplusplus/src/hello.cpp", + "start_line": 6 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-119", + "value": "119", + "url": "https://cwe.mitre.org/data/definitions/119.html" + }, + { + "type": "cwe", + "name": "CWE-120", + "value": "120", + "url": "https://cwe.mitre.org/data/definitions/120.html" + } + ], + "file": "cplusplus/src/hello.cpp", + "line": 6, + "url": "https://cwe.mitre.org/data/definitions/119.html", + "tool": "flawfinder" + }, + { + "category": "sast", + "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)", + "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120", + "confidence": "Low", + "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)", + "scanner": { + "id": "flawfinder", + "name": "Flawfinder" + }, + "location": { + "file": "cplusplus/src/hello.cpp", + "start_line": 7 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-120", + "value": "120", + "url": "https://cwe.mitre.org/data/definitions/120.html" + } + ], + "file": "cplusplus/src/hello.cpp", + "line": 7, + "url": "https://cwe.mitre.org/data/definitions/120.html", + "tool": "flawfinder" + } +] diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb index cc310766433..8bf378549fe 100644 --- a/spec/helpers/time_helper_spec.rb +++ b/spec/helpers/time_helper_spec.rb @@ -22,34 +22,17 @@ describe TimeHelper do describe "#duration_in_numbers" do using RSpec::Parameterized::TableSyntax - context "without passing allow_overflow" do - where(:duration, :formatted_string) do - 0 | "00:00" - 1.second | "00:01" - 42.seconds | "00:42" - 2.minutes + 1.second | "02:01" - 3.hours + 2.minutes + 1.second | "03:02:01" - 30.hours | "06:00:00" - end - - with_them do - it { expect(duration_in_numbers(duration)).to eq formatted_string } - end + where(:duration, :formatted_string) do + 0 | "00:00" + 1.second | "00:01" + 42.seconds | "00:42" + 2.minutes + 1.second | "02:01" + 3.hours + 2.minutes + 1.second | "03:02:01" + 30.hours | "30:00:00" end - context "with allow_overflow = true" do - where(:duration, :formatted_string) do - 0 | "00:00:00" - 1.second | "00:00:01" - 42.seconds | "00:00:42" - 2.minutes + 1.second | "00:02:01" - 3.hours + 2.minutes + 1.second | "03:02:01" - 30.hours | "30:00:00" - end - - with_them do - it { expect(duration_in_numbers(duration, true)).to eq formatted_string } - end + with_them do + it { expect(duration_in_numbers(duration)).to eq formatted_string } end end end diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index a3be222b7bd..e565ac8c530 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe VisibilityLevelHelper do + include ProjectForksHelper + let(:project) { build(:project) } let(:group) { build(:group) } let(:personal_snippet) { build(:personal_snippet) } @@ -83,13 +85,13 @@ describe VisibilityLevelHelper do describe "disallowed_visibility_level?" do describe "forks" do - let(:project) { create(:project, :internal) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:project, :internal) } + let(:forked_project) { fork_project(project) } it "disallows levels" do - expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy - expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey - expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey + expect(disallowed_visibility_level?(forked_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy + expect(disallowed_visibility_level?(forked_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey + expect(disallowed_visibility_level?(forked_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey end end diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js index 9270433dcb7..fd73fb4bfcc 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js @@ -18,8 +18,6 @@ describe('BalsamiqViewer', () => { }); }); - describe('fileLoaded', () => {}); - describe('loadFile', () => { let xhr; let loadFile; diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js index bdee85f90b1..dc5737558c0 100644 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -45,8 +45,10 @@ describe('Issuable right sidebar collapsed todo toggle', () => { expect(document.querySelector('.js-issuable-todo.sidebar-collapsed-icon')).not.toBeNull(); expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-plus-square'), - ).not.toBeNull(); + document + .querySelector('.js-issuable-todo.sidebar-collapsed-icon svg use') + .getAttribute('xlink:href'), + ).toContain('todo-add'); expect( document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), @@ -68,8 +70,10 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ).not.toBeNull(); expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-check-square'), - ).not.toBeNull(); + document + .querySelector('.js-issuable-todo.sidebar-collapsed-icon svg.todo-undone use') + .getAttribute('xlink:href'), + ).toContain('todo-done'); done(); }); diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js index 08e25d2004e..fc94d0bab5b 100644 --- a/spec/javascripts/diffs/components/tree_list_spec.js +++ b/spec/javascripts/diffs/components/tree_list_spec.js @@ -53,7 +53,7 @@ describe('Diffs tree list component', () => { fileHash: 'test', key: 'index.js', name: 'index.js', - path: 'index.js', + path: 'app/index.js', removedLines: 0, tempFile: true, type: 'blob', @@ -104,7 +104,55 @@ describe('Diffs tree list component', () => { vm.$el.querySelector('.file-row').click(); - expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'index.js'); + expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'app/index.js'); + }); + + it('renders as file list when renderTreeList is false', done => { + vm.renderTreeList = false; + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.file-row').length).toBe(1); + + done(); + }); + }); + + it('renders file paths when renderTreeList is false', done => { + vm.renderTreeList = false; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.file-row').textContent).toContain('app/index.js'); + + done(); + }); + }); + + it('hides render buttons when input is focused', done => { + const focusEvent = new Event('focus'); + + vm.$el.querySelector('.form-control').dispatchEvent(focusEvent); + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.tree-list-view-toggle').style.display).toBe('none'); + + done(); + }); + }); + + it('shows render buttons when input is blurred', done => { + const blurEvent = new Event('blur'); + vm.focusSearch = true; + + vm.$nextTick() + .then(() => { + vm.$el.querySelector('.form-control').dispatchEvent(blurEvent); + }) + .then(vm.$nextTick) + .then(() => { + expect(vm.$el.querySelector('.tree-list-view-toggle').style.display).not.toBe('none'); + }) + .then(done) + .catch(done.fail); }); }); @@ -117,4 +165,24 @@ describe('Diffs tree list component', () => { expect(vm.search).toBe(''); }); }); + + describe('toggleRenderTreeList', () => { + it('updates renderTreeList', () => { + expect(vm.renderTreeList).toBe(true); + + vm.toggleRenderTreeList(false); + + expect(vm.renderTreeList).toBe(false); + }); + }); + + describe('toggleFocusSearch', () => { + it('updates focusSearch', () => { + expect(vm.focusSearch).toBe(false); + + vm.toggleFocusSearch(true); + + expect(vm.focusSearch).toBe(true); + }); + }); }); diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index ef367fc09fa..f49dee3696d 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -445,6 +445,14 @@ describe('DiffsStoreUtils', () => { fileHash: 'test', }, { + newPath: 'app/test/filepathneedstruncating.js', + deletedFile: false, + newFile: true, + removedLines: 0, + addedLines: 0, + fileHash: 'test', + }, + { newPath: 'package.json', deletedFile: true, newFile: false, @@ -498,6 +506,19 @@ describe('DiffsStoreUtils', () => { type: 'blob', tree: [], }, + { + addedLines: 0, + changed: true, + deleted: false, + fileHash: 'test', + key: 'app/test/filepathneedstruncating.js', + name: 'filepathneedstruncating.js', + path: 'app/test/filepathneedstruncating.js', + removedLines: 0, + tempFile: true, + type: 'blob', + tree: [], + }, ], }, ], @@ -527,6 +548,7 @@ describe('DiffsStoreUtils', () => { 'app/index.js', 'app/test', 'app/test/index.js', + 'app/test/filepathneedstruncating.js', 'package.json', ]); }); diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index d7338ee0f66..aecab331ead 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -172,7 +172,7 @@ describe('Flash', () => { flash('test'); expect(document.querySelector('.flash-text').className).toBe( - 'flash-text container-fluid container-limited', + 'flash-text container-fluid container-limited limit-container-width', ); }); @@ -180,7 +180,7 @@ describe('Flash', () => { document.querySelector('.content-wrapper').className = 'js-content-wrapper'; flash('test'); - expect(document.querySelector('.flash-text').className.trim()).toBe('flash-text'); + expect(document.querySelector('.flash-text').className.trim()).toContain('flash-text'); }); it('removes element after clicking', () => { diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js index 70b885ede26..878e17ac805 100644 --- a/spec/javascripts/ide/components/new_dropdown/upload_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js @@ -40,21 +40,10 @@ describe('new dropdown upload', () => { describe('readFile', () => { beforeEach(() => { - spyOn(FileReader.prototype, 'readAsText'); spyOn(FileReader.prototype, 'readAsDataURL'); }); - it('calls readAsText for text files', () => { - const file = { - type: 'text/html', - }; - - vm.readFile(file); - - expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file); - }); - - it('calls readAsDataURL for non-text files', () => { + it('calls readAsDataURL for all files', () => { const file = { type: 'images/png', }; @@ -66,32 +55,37 @@ describe('new dropdown upload', () => { }); describe('createFile', () => { - const target = { - result: 'content', + const textTarget = { + result: 'base64,cGxhaW4gdGV4dA==', }; const binaryTarget = { - result: 'base64,base64content', + result: 'base64,w4I=', + }; + const textFile = { + name: 'textFile', + type: 'text/plain', }; - const file = { - name: 'file', + const binaryFile = { + name: 'binaryFile', + type: 'image/png', }; - it('creates new file', () => { - vm.createFile(target, file, true); + it('creates file in plain text (without encoding) if the file content is plain text', () => { + vm.createFile(textTarget, textFile); expect(vm.$emit).toHaveBeenCalledWith('create', { - name: file.name, + name: textFile.name, type: 'blob', - content: target.result, + content: 'plain text', base64: false, }); }); it('splits content on base64 if binary', () => { - vm.createFile(binaryTarget, file, false); + vm.createFile(binaryTarget, binaryFile); expect(vm.$emit).toHaveBeenCalledWith('create', { - name: file.name, + name: binaryFile.name, type: 'blob', content: binaryTarget.result.split('base64,')[1], base64: true, diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index c9a1158a14e..df291ade3f7 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -279,8 +279,6 @@ describe('Multi-file store actions', () => { }); }); - describe('popHistoryState', () => {}); - describe('scrollToTab', () => { it('focuses the current active element', done => { document.body.innerHTML += diff --git a/spec/javascripts/image_diff/image_diff_spec.js b/spec/javascripts/image_diff/image_diff_spec.js index 9a7f288cd6b..21e7b8e2e9b 100644 --- a/spec/javascripts/image_diff/image_diff_spec.js +++ b/spec/javascripts/image_diff/image_diff_spec.js @@ -128,16 +128,6 @@ describe('ImageDiff', () => { }); }); - describe('image loaded', () => { - beforeEach(() => { - spyOn(imageUtility, 'isImageLoaded').and.returnValue(true); - imageDiff = new ImageDiff(element); - imageDiff.imageEl = imageEl; - }); - - it('should renderBadges', () => {}); - }); - describe('image not loaded', () => { beforeEach(() => { spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); diff --git a/spec/javascripts/jobs/components/job_container_item_spec.js b/spec/javascripts/jobs/components/job_container_item_spec.js new file mode 100644 index 00000000000..8588eda19c8 --- /dev/null +++ b/spec/javascripts/jobs/components/job_container_item_spec.js @@ -0,0 +1,73 @@ +import Vue from 'vue'; +import JobContainerItem from '~/jobs/components/job_container_item.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import job from '../mock_data'; + +describe('JobContainerItem', () => { + const Component = Vue.extend(JobContainerItem); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + const sharedTests = () => { + it('displays a status icon', () => { + expect(vm.$el).toHaveSpriteIcon(job.status.icon); + }); + + it('displays the job name', () => { + expect(vm.$el).toContainText(job.name); + }); + + it('displays a link to the job', () => { + const link = vm.$el.querySelector('.js-job-link'); + + expect(link.href).toBe(job.status.details_path); + }); + }; + + describe('when a job is not active and not retied', () => { + beforeEach(() => { + vm = mountComponent(Component, { + job, + isActive: false, + }); + }); + + sharedTests(); + }); + + describe('when a job is active', () => { + beforeEach(() => { + vm = mountComponent(Component, { + job, + isActive: true, + }); + }); + + sharedTests(); + + it('displays an arrow', () => { + expect(vm.$el).toHaveSpriteIcon('arrow-right'); + }); + }); + + describe('when a job is retried', () => { + beforeEach(() => { + vm = mountComponent(Component, { + job: { + ...job, + retried: true, + }, + isActive: false, + }); + }); + + sharedTests(); + + it('displays an icon', () => { + expect(vm.$el).toHaveSpriteIcon('retry'); + }); + }); +}); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index ca6fbabeeb6..0398f184c0a 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -1,3 +1,5 @@ +import { TEST_HOST } from 'spec/test_constants'; + const threeWeeksAgo = new Date(); threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); @@ -19,7 +21,7 @@ export default { label: 'passed', group: 'success', has_details: true, - details_path: '/root/ci-mock/-/jobs/4757', + details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`, favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', action: { diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 7251ce19a90..7714197c821 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -224,6 +224,14 @@ describe('MergeRequestTabs', function() { expect($('.content-wrapper')).not.toContainElement('.container-limited'); }); + it('does not add container-limited when fluid layout is prefered', function() { + $('.content-wrapper .container-fluid').removeClass('container-limited'); + + this.class.expandViewContainer(false); + + expect($('.content-wrapper')).not.toContainElement('.container-limited'); + }); + it('does remove container-limited from breadcrumbs', function() { $('.container-limited').addClass('breadcrumbs'); this.class.expandViewContainer(); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js new file mode 100644 index 00000000000..70dd5bb3be5 --- /dev/null +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -0,0 +1,60 @@ +import Vue from 'vue'; +import createStore from '~/notes/stores'; +import DiscussionFilter from '~/notes/components/discussion_filter.vue'; +import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { discussionFiltersMock, discussionMock } from '../mock_data'; + +describe('DiscussionFilter component', () => { + let vm; + let store; + + beforeEach(() => { + store = createStore(); + + const discussions = [{ + ...discussionMock, + id: discussionMock.id, + notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }], + }]; + const Component = Vue.extend(DiscussionFilter); + const defaultValue = discussionFiltersMock[0].value; + + store.state.discussions = discussions; + vm = mountComponentWithStore(Component, { + el: null, + store, + props: { + filters: discussionFiltersMock, + defaultValue, + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders the all filters', () => { + expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual(discussionFiltersMock.length); + }); + + it('renders the default selected item', () => { + expect(vm.$el.querySelector('#discussion-filter-dropdown').textContent.trim()).toEqual(discussionFiltersMock[0].title); + }); + + it('updates to the selected item', () => { + const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + filterItem.click(); + + expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim()); + }); + + it('only updates when selected filter changes', () => { + const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button'); + + spyOn(vm, 'filterDiscussion'); + filterItem.click(); + + expect(vm.filterDiscussion).not.toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 3e289a6b8e6..06b30375306 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -97,8 +97,7 @@ describe('note_app', () => { }); it('should render list of notes', done => { - const note = - mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ + const note = mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ '/gitlab-org/gitlab-ce/issues/26/discussions.json' ][0].notes[0]; diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 9a0e7f34a9c..ad0e793b915 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -1244,3 +1244,18 @@ export const discussion3 = { export const unresolvableDiscussion = { resolvable: false, }; + +export const discussionFiltersMock = [ + { + title: 'Show all activity', + value: 0, + }, + { + title: 'Show comments only', + value: 1, + }, + { + title: 'Show system notes only', + value: 2, + }, +]; diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index 20b5532a837..ce850bc621e 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -14,6 +14,20 @@ const deploymentMockData = { external_url_formatted: 'diplo.', deployed_at: '2017-03-22T22:44:42.258Z', deployed_at_formatted: 'Mar 22, 2017 10:44pm', + changes: [ + { + path: 'index.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', + }, + { + path: 'imgs/gallery.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', + }, + { + path: 'about/', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', + }, + ], }; const createComponent = () => { const Component = Vue.extend(deploymentComponent); @@ -176,4 +190,42 @@ describe('Deployment component', () => { expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull(); }); }); + + describe('with `features.ciEnvironmentsStatusChanges` enabled', () => { + beforeEach(() => { + window.gon = window.gon || {}; + window.gon.features = window.gon.features || {}; + window.gon.features.ciEnvironmentsStatusChanges = true; + + vm = createComponent(deploymentMockData); + }); + + afterEach(() => { + window.gon.features = {}; + }); + + it('renders dropdown with changes', () => { + expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).not.toBeNull(); + expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).toBeNull(); + }); + }); + + describe('with `features.ciEnvironmentsStatusChanges` disabled', () => { + beforeEach(() => { + window.gon = window.gon || {}; + window.gon.features = window.gon.features || {}; + window.gon.features.ciEnvironmentsStatusChanges = false; + + vm = createComponent(deploymentMockData); + }); + + afterEach(() => { + delete window.gon.features.ciEnvironmentsStatusChanges; + }); + + it('renders the old link to the review app', () => { + expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); + expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); + }); + }); }); 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 6b5e32fdfd5..d1a064b9f4d 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -7,11 +7,12 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mockData from './mock_data'; import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from '../lib/utils/mock_data'; -const returnPromise = data => new Promise((resolve) => { - resolve({ - data, +const returnPromise = data => + new Promise(resolve => { + resolve({ + data, + }); }); -}); describe('mrWidgetOptions', () => { let vm; @@ -135,7 +136,7 @@ describe('mrWidgetOptions', () => { describe('methods', () => { describe('checkStatus', () => { - it('should tell service to check status', (done) => { + it('should tell service to check status', done => { spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData)); spyOn(vm.mr, 'setData'); spyOn(vm, 'handleNotification'); @@ -185,7 +186,7 @@ describe('mrWidgetOptions', () => { }); describe('fetchDeployments', () => { - it('should fetch deployments', (done) => { + it('should fetch deployments', done => { spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }])); vm.fetchDeployments(); @@ -200,7 +201,7 @@ describe('mrWidgetOptions', () => { }); describe('fetchActionsContent', () => { - it('should fetch content of Cherry Pick and Revert modals', (done) => { + it('should fetch content of Cherry Pick and Revert modals', done => { spyOn(vm.service, 'fetchMergeActionsContent').and.returnValue(returnPromise('hello world')); vm.fetchActionsContent(); @@ -251,7 +252,7 @@ describe('mrWidgetOptions', () => { }; const allArgs = eventHub.$on.calls.allArgs(); - allArgs.forEach((params) => { + allArgs.forEach(params => { const eventName = params[0]; const callback = params[1]; @@ -270,18 +271,6 @@ describe('mrWidgetOptions', () => { }); }); - describe('handleMounted', () => { - it('should call required methods to do the initial kick-off', () => { - spyOn(vm, 'initDeploymentsPolling'); - spyOn(vm, 'setFaviconHelper'); - - vm.handleMounted(); - - expect(vm.setFaviconHelper).toHaveBeenCalled(); - expect(vm.initDeploymentsPolling).toHaveBeenCalled(); - }); - }); - describe('setFavicon', () => { let faviconElement; @@ -298,13 +287,14 @@ describe('mrWidgetOptions', () => { document.body.removeChild(document.getElementById('favicon')); }); - it('should call setFavicon method', (done) => { + it('should call setFavicon method', done => { vm.mr.ciStatusFaviconPath = overlayDataUrl; - vm.setFaviconHelper().then(() => { - expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl); - done(); - }) - .catch(done.fail); + vm.setFaviconHelper() + .then(() => { + expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl); + done(); + }) + .catch(done.fail); }); it('should not call setFavicon when there is no ciStatusFaviconPath', () => { @@ -379,7 +369,7 @@ describe('mrWidgetOptions', () => { }); describe('rendering relatedLinks', () => { - beforeEach((done) => { + beforeEach(done => { vm.mr.relatedLinks = { assignToMe: null, closing: ` @@ -396,7 +386,7 @@ describe('mrWidgetOptions', () => { expect(vm.$el.querySelector('.close-related-link')).toBeDefined(); }); - it('does not render if state is nothingToMerge', (done) => { + it('does not render if state is nothingToMerge', done => { vm.mr.state = stateKey.nothingToMerge; Vue.nextTick(() => { expect(vm.$el.querySelector('.close-related-link')).toBeNull(); @@ -406,7 +396,7 @@ describe('mrWidgetOptions', () => { }); describe('rendering source branch removal status', () => { - it('renders when user cannot remove branch and branch should be removed', (done) => { + it('renders when user cannot remove branch and branch should be removed', done => { vm.mr.canRemoveSourceBranch = false; vm.mr.shouldRemoveSourceBranch = true; vm.mr.state = 'readyToMerge'; @@ -423,7 +413,7 @@ describe('mrWidgetOptions', () => { }); }); - it('does not render in merged state', (done) => { + it('does not render in merged state', done => { vm.mr.canRemoveSourceBranch = false; vm.mr.shouldRemoveSourceBranch = true; vm.mr.state = 'merged'; @@ -438,6 +428,20 @@ describe('mrWidgetOptions', () => { }); describe('rendering deployments', () => { + const changes = [ + { + path: 'index.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', + }, + { + path: 'imgs/gallery.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', + }, + { + path: 'about/', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', + }, + ]; const deploymentMockData = { id: 15, name: 'review/diplo', @@ -449,15 +453,23 @@ describe('mrWidgetOptions', () => { external_url_formatted: 'diplo.', deployed_at: '2017-03-22T22:44:42.258Z', deployed_at_formatted: 'Mar 22, 2017 10:44pm', + changes, }; - beforeEach((done) => { - vm.mr.deployments.push({ - ...deploymentMockData, - }, { - ...deploymentMockData, - id: deploymentMockData.id + 1, - }); + beforeEach(done => { + window.gon = window.gon || {}; + window.gon.features = window.gon.features || {}; + window.gon.features.ciEnvironmentsStatusChanges = true; + + vm.mr.deployments.push( + { + ...deploymentMockData, + }, + { + ...deploymentMockData, + id: deploymentMockData.id + 1, + }, + ); vm.$nextTick(done); }); @@ -465,5 +477,13 @@ describe('mrWidgetOptions', () => { it('renders multiple deployments', () => { expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2); }); + + it('renders dropdpown with multiple file changes', () => { + expect( + vm.$el + .querySelector('.js-mr-wigdet-deployment-dropdown') + .querySelectorAll('.js-filtered-dropdown-result').length, + ).toEqual(changes.length); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/bar_chart_spec.js b/spec/javascripts/vue_shared/components/bar_chart_spec.js index 7e91cd6f63f..8f753876e44 100644 --- a/spec/javascripts/vue_shared/components/bar_chart_spec.js +++ b/spec/javascripts/vue_shared/components/bar_chart_spec.js @@ -71,12 +71,6 @@ describe('Bar chart component', () => { expect(barChart.xAxisLocation).toEqual('translate(100, 250)'); }); - it('Contains a total of 4 ticks across the y axis', () => { - const ticks = barChart.$el.querySelector('.y-axis').querySelectorAll('.tick').length; - - expect(ticks).toEqual(4); - }); - it('rotates the x axis labels a total of 90 degress (CCW)', () => { const xAxisLabel = barChart.$el.querySelector('.x-axis').querySelectorAll('text')[0]; diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js index 21e7afaf78f..4b0b7ba66e5 100644 --- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js +++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js @@ -86,7 +86,7 @@ describe('CI Badge Link Component', () => { expect(vm.$el.getAttribute('href')).toEqual(statuses[status].details_path); expect(vm.$el.textContent.trim()).toEqual(statuses[status].text); - expect(vm.$el.getAttribute('class')).toEqual(`ci-status ci-${statuses[status].group}`); + expect(vm.$el.getAttribute('class')).toContain(`ci-status ci-${statuses[status].group}`); expect(vm.$el.querySelector('svg')).toBeDefined(); return vm; }); diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js index 9914c0b70f3..67752c1c455 100644 --- a/spec/javascripts/vue_shared/components/file_row_spec.js +++ b/spec/javascripts/vue_shared/components/file_row_spec.js @@ -71,4 +71,40 @@ describe('RepoFile', () => { expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px'); }); + + describe('outputText', () => { + beforeEach(done => { + createComponent({ + file: { + ...file(), + path: 'app/assets/index.js', + }, + level: 0, + }); + + vm.displayTextKey = 'path'; + + vm.$nextTick(done); + }); + + it('returns text if truncateStart is 0', done => { + vm.truncateStart = 0; + + vm.$nextTick(() => { + expect(vm.outputText).toBe('app/assets/index.js'); + + done(); + }); + }); + + it('returns text truncated at start', done => { + vm.truncateStart = 5; + + vm.$nextTick(() => { + expect(vm.outputText).toBe('...ssets/index.js'); + + done(); + }); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js new file mode 100644 index 00000000000..b71cb36ecf6 --- /dev/null +++ b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js @@ -0,0 +1,91 @@ +import Vue from 'vue'; +import component from '~/vue_shared/components/filtered_search_dropdown.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Filtered search dropdown', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('with an empty array of items', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [], + filterKey: '', + }); + }); + + it('renders empty list', () => { + expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(0); + }); + + it('renders filter input', () => { + expect(vm.$el.querySelector('.js-filtered-dropdown-input')).not.toBeNull(); + }); + }); + + describe('when visible numbers is less than the items length', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }], + visibleItems: 2, + filterKey: 'title', + }); + }); + + it('it renders only the maximum number provided', () => { + expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(2); + }); + }); + + describe('when visible number is bigger than the items lenght', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }], + filterKey: 'title', + }); + }); + + it('it renders the full list of items the maximum number provided', () => { + expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(3); + }); + }); + + describe('while filtering', () => { + beforeEach(() => { + vm = mountComponent(Component, { + items: [ + { title: 'One' }, + { title: 'Two/three' }, + { title: 'Three four' }, + { title: 'Five' }, + ], + filterKey: 'title', + }); + }); + + it('updates the results to match the typed value', done => { + vm.$el.querySelector('.js-filtered-dropdown-input').value = 'three'; + vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input')); + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(2); + done(); + }); + }); + + describe('when no value matches the typed one', () => { + it('does not render any result', done => { + vm.$el.querySelector('.js-filtered-dropdown-input').value = 'six'; + vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input')); + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.js-filtered-dropdown-result').length).toEqual(0); + done(); + }); + }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index 6c4bc3602c1..59613faa49f 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -76,7 +76,7 @@ describe('Markdown field header component', () => { }); it('blurs preview link after click', done => { - const link = vm.$el.querySelector('li:nth-child(2) a'); + const link = vm.$el.querySelector('li:nth-child(2) button'); spyOn(HTMLElement.prototype, 'blur'); link.click(); diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb index 09bf21b5946..292ab870dad 100644 --- a/spec/lib/gitaly/server_spec.rb +++ b/spec/lib/gitaly/server_spec.rb @@ -26,9 +26,7 @@ describe Gitaly::Server do end end - context 'when the storage is not readable' do - let(:server) { described_class.new('broken') } - + context 'when the storage is not readable', :broken_storage do it 'returns false' do expect(server).not_to be_readable end @@ -42,9 +40,7 @@ describe Gitaly::Server do end end - context 'when the storage is not writeable' do - let(:server) { described_class.new('broken') } - + context 'when the storage is not writeable', :broken_storage do it 'returns false' do expect(server).not_to be_writeable end diff --git a/spec/lib/gitlab/ci/build/artifacts/gzip_file_adapter_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb index 384329dda18..987c6b37aaa 100644 --- a/spec/lib/gitlab/ci/build/artifacts/gzip_file_adapter_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Ci::Build::Artifacts::GzipFileAdapter do +describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do describe '#initialize' do context 'when stream is passed' do let(:stream) { File.open(expand_fixture_path('junit/junit.xml.gz'), 'rb') } diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb new file mode 100644 index 00000000000..ec2dd724b45 --- /dev/null +++ b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do + describe '#initialize' do + context 'when stream is passed' do + let(:stream) { File.open(expand_fixture_path('junit/junit.xml'), 'rb') } + + it 'initialized' do + expect { described_class.new(stream) }.not_to raise_error + end + end + + context 'when stream is not passed' do + let(:stream) { nil } + + it 'raises an error' do + expect { described_class.new(stream) }.to raise_error(described_class::InvalidStreamError) + end + end + end + + describe '#each_blob' do + let(:adapter) { described_class.new(stream) } + + context 'when file is not empty' do + let(:stream) { File.open(expand_fixture_path('junit/junit.xml'), 'rb') } + + it 'iterates content' do + expect { |b| adapter.each_blob(&b) } + .to yield_with_args(fixture_file('junit/junit.xml'), 'raw') + end + end + + context 'when file is empty' do + let(:stream) { Tempfile.new } + + after do + stream.unlink + end + + it 'does not iterate content' do + expect { |b| adapter.each_blob(&b) } + .not_to yield_control + end + end + end +end diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb index e327399d82d..a9a4af1f455 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb @@ -112,4 +112,34 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do end end end + + context 'generated metadata' do + let(:tmpfile) { Tempfile.new('test-metadata') } + let(:generator) { CiArtifactMetadataGenerator.new(tmpfile) } + let(:entry_count) { 5 } + + before do + tmpfile.binmode + + (1..entry_count).each do |index| + generator.add_entry("public/test-#{index}.txt") + end + + generator.write + end + + after do + File.unlink(tmpfile.path) + end + + describe '#find_entries!' do + it 'reads expected number of entries' do + stream = File.open(tmpfile.path) + + metadata = described_class.new(stream, 'public', { recursive: true }) + + expect(metadata.find_entries!.count).to eq entry_count + end + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 7cf541447ce..8095a231cf3 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -38,6 +38,8 @@ describe Gitlab::Ci::Config::Entry::Reports do :dependency_scanning | 'gl-dependency-scanning-report.json' :container_scanning | 'gl-container-scanning-report.json' :dast | 'gl-dast-report.json' + :license_management | 'gl-license-management-report.json' + :performance | 'performance.json' end with_them do diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb new file mode 100644 index 00000000000..2e92d5204d6 --- /dev/null +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::Ci::Config::External::File::Base do + subject { described_class.new(location) } + + before do + allow_any_instance_of(described_class) + .to receive(:content).and_return('key: value') + end + + describe '#valid?' do + context 'when location is not a YAML file' do + let(:location) { 'some/file.txt' } + + it { is_expected.not_to be_valid } + end + + context 'when location has not a valid naming scheme' do + let(:location) { 'some/file/.yml' } + + it { is_expected.not_to be_valid } + end + + context 'when location is a valid .yml extension' do + let(:location) { 'some/file/config.yml' } + + it { is_expected.to be_valid } + end + + context 'when location is a valid .yaml extension' do + let(:location) { 'some/file/config.yaml' } + + it { is_expected.to be_valid } + end + + context 'when there are YAML syntax errors' do + let(:location) { 'some/file/config.yml' } + + before do + allow_any_instance_of(described_class) + .to receive(:content).and_return('invalid_syntax') + end + + it 'is not a valid file' do + expect(subject).not_to be_valid + expect(subject.error_message).to match /does not have valid YAML syntax/ + end + end + end +end diff --git a/spec/lib/gitlab/ci/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index 73bb4ccf468..2708d8d5b6b 100644 --- a/spec/lib/gitlab/ci/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Ci::External::File::Local do +describe Gitlab::Ci::Config::External::File::Local do let(:project) { create(:project, :repository) } let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) } @@ -72,7 +72,7 @@ describe Gitlab::Ci::External::File::Local do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } it 'should return an error message' do - expect(local_file.error_message).to eq("Local file '#{location}' is not valid.") + expect(local_file.error_message).to eq("Local file `#{location}` does not exist!") end end end diff --git a/spec/lib/gitlab/ci/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index b1819c8960b..7c1a1c38736 100644 --- a/spec/lib/gitlab/ci/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Ci::External::File::Remote do +describe Gitlab::Ci::Config::External::File::Remote do let(:remote_file) { described_class.new(location) } let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } let(:remote_file_content) do @@ -105,10 +105,53 @@ describe Gitlab::Ci::External::File::Remote do end describe "#error_message" do - let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + subject { remote_file.error_message } - it 'should return an error message' do - expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.") + context 'when remote file location is not valid' do + let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } + + it 'returns an error message describing invalid address' do + expect(subject).to match /does not have a valid address!/ + end + end + + context 'when timeout error has been raised' do + before do + WebMock.stub_request(:get, location).to_timeout + end + + it 'should returns error message about a timeout' do + expect(subject).to match /could not be fetched because of a timeout error!/ + end + end + + context 'when HTTP error has been raised' do + before do + WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error) + end + + it 'should returns error message about a HTTP error' do + expect(subject).to match /could not be fetched because of HTTP error!/ + end + end + + context 'when response has 404 status' do + before do + WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404) + end + + it 'should returns error message about a timeout' do + expect(subject).to match /could not be fetched because of HTTP code `404` error!/ + end + end + + context 'when the URL is blocked' do + let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' } + + it 'should include details about blocked URL' do + expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \ + 'is blocked: Requests to localhost are not allowed!' + end end end end diff --git a/spec/lib/gitlab/ci/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index d925d6af73d..5b236fe99f1 100644 --- a/spec/lib/gitlab/ci/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Ci::External::Mapper do +describe Gitlab::Ci::Config::External::Mapper do let(:project) { create(:project, :repository) } let(:file_content) do <<~HEREDOC @@ -27,7 +27,8 @@ describe Gitlab::Ci::External::Mapper do end it 'returns File instances' do - expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local) + expect(subject.first) + .to be_an_instance_of(Gitlab::Ci::Config::External::File::Local) end end @@ -49,7 +50,8 @@ describe Gitlab::Ci::External::Mapper do end it 'returns File instances' do - expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote) + expect(subject.first) + .to be_an_instance_of(Gitlab::Ci::Config::External::File::Remote) end end end diff --git a/spec/lib/gitlab/ci/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 3c7394f53d2..1a05f716247 100644 --- a/spec/lib/gitlab/ci/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Ci::External::Processor do +describe Gitlab::Ci::Config::External::Processor do let(:project) { create(:project, :repository) } let(:processor) { described_class.new(values, project, '12345') } @@ -20,8 +20,8 @@ describe Gitlab::Ci::External::Processor do it 'should raise an error' do expect { processor.perform }.to raise_error( - described_class::FileError, - "Local file '/lib/gitlab/ci/templates/non-existent-file.yml' is not valid." + described_class::IncludeError, + "Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!" ) end end @@ -36,8 +36,8 @@ describe Gitlab::Ci::External::Processor do it 'should raise an error' do expect { processor.perform }.to raise_error( - described_class::FileError, - "Remote file '#{remote_file}' is not valid." + described_class::IncludeError, + "Remote file `#{remote_file}` could not be fetched because of a socket error!" ) end end @@ -92,7 +92,8 @@ describe Gitlab::Ci::External::Processor do end before do - allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content) + allow_any_instance_of(Gitlab::Ci::Config::External::File::Local) + .to receive(:fetch_local_content).and_return(local_file_content) end it 'should append the file to the values' do @@ -131,7 +132,10 @@ describe Gitlab::Ci::External::Processor do before do local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml')) - allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content) + + allow_any_instance_of(Gitlab::Ci::Config::External::File::Local) + .to receive(:fetch_local_content).and_return(local_file_content) + WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) end @@ -150,11 +154,15 @@ describe Gitlab::Ci::External::Processor do let(:local_file_content) { 'invalid content file ////' } before do - allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content) + allow_any_instance_of(Gitlab::Ci::Config::External::File::Local) + .to receive(:fetch_local_content).and_return(local_file_content) end it 'should raise an error' do - expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError) + expect { processor.perform }.to raise_error( + described_class::IncludeError, + "Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!" + ) end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index b43aca8a354..975e11e8cc1 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -1,6 +1,4 @@ -require 'fast_spec_helper' - -require_dependency 'active_model' +require 'spec_helper' describe Gitlab::Ci::Config do let(:config) do @@ -202,8 +200,8 @@ describe Gitlab::Ci::Config do it 'raises error YamlProcessor validationError' do expect { config }.to raise_error( - ::Gitlab::Ci::YamlProcessor::ValidationError, - "Local file 'invalid' is not valid." + described_class::ConfigError, + "Included file `invalid` does not have YAML extension!" ) end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index aa53ecd5967..b379b08ad62 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -339,7 +339,7 @@ describe Gitlab::Ci::Status::Build::Factory do end it 'fabricates status with correct details' do - expect(status.text).to eq 'scheduled' + expect(status.text).to eq 'delayed' expect(status.group).to eq 'scheduled' expect(status.icon).to eq 'status_scheduled' expect(status.favicon).to eq 'favicon_status_scheduled' diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb index f98183d6d18..4a52b3ab8de 100644 --- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb +++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb @@ -26,9 +26,9 @@ describe Gitlab::Ci::Status::Build::Scheduled do context 'when scheduled_at is expired' do let(:build) { create(:ci_build, :expired_scheduled, project: project) } - it 'shows 00:00:00' do + it 'shows 00:00' do Timecop.freeze do - expect(subject.status_tooltip).to include('00:00:00') + expect(subject.status_tooltip).to include('00:00') end end end diff --git a/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb index 29afa08b56b..f89712d2b03 100644 --- a/spec/lib/gitlab/ci/status/pipeline/scheduled_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Ci::Status::Pipeline::Scheduled do +describe Gitlab::Ci::Status::Pipeline::Delayed do let(:pipeline) { double('pipeline') } subject do @@ -9,7 +9,7 @@ describe Gitlab::Ci::Status::Pipeline::Scheduled do describe '#text' do it 'overrides status text' do - expect(subject.text).to eq 'scheduled' + expect(subject.text).to eq 'delayed' end end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index d97fdc01109..466087a0e31 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -71,7 +71,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do it 'matches a correct extended statuses' do expect(factory.extended_statuses) - .to eq [Gitlab::Ci::Status::Pipeline::Scheduled] + .to eq [Gitlab::Ci::Status::Pipeline::Delayed] end it 'extends core status with common pipeline methods' do diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb index c35a6f43d5d..b8ca3caa1f7 100644 --- a/spec/lib/gitlab/ci/status/scheduled_spec.rb +++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb @@ -6,11 +6,11 @@ describe Gitlab::Ci::Status::Scheduled do end describe '#text' do - it { expect(subject.text).to eq 'scheduled' } + it { expect(subject.text).to eq 'delayed' } end describe '#label' do - it { expect(subject.label).to eq 'scheduled' } + it { expect(subject.label).to eq 'delayed' } end describe '#icon' do diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb index d0dadfa78da..6c37c157f5d 100644 --- a/spec/lib/gitlab/http_spec.rb +++ b/spec/lib/gitlab/http_spec.rb @@ -46,4 +46,30 @@ describe Gitlab::HTTP do end end end + + describe 'handle redirect loops' do + before do + WebMock.stub_request(:any, "http://example.org").to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep")) + end + + it 'handles GET requests' do + expect { described_class.get('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep) + end + + it 'handles POST requests' do + expect { described_class.post('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep) + end + + it 'handles PUT requests' do + expect { described_class.put('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep) + end + + it 'handles DELETE requests' do + expect { described_class.delete('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep) + end + + it 'handles HEAD requests' do + expect { described_class.head('http://example.org') }.to raise_error(Gitlab::HTTP::RedirectionTooDeep) + end + end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index fe167033941..a63f34b5536 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -229,9 +229,8 @@ project: - mock_ci_service - mock_deployment_service - mock_monitoring_service -- forked_project_link +- forked_to_members - forked_from_project -- forked_project_links - forks - merge_requests - fork_merge_requests diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 7ebfc61f5e7..b0570680d5a 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -335,7 +335,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do restored_project_json - expect(project.lfs_enabled).to be_nil + expect(project.lfs_enabled).to be_falsey end end diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb new file mode 100644 index 00000000000..da3f5d27b25 --- /dev/null +++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Kubernetes::RoleBinding, '#generate' do + let(:role_name) { 'edit' } + let(:namespace) { 'my-namespace' } + let(:service_account_name) { 'my-service-account' } + + let(:subjects) do + [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + end + + let(:role_ref) do + { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'Role', + name: role_name + } + end + + let(:resource) do + ::Kubeclient::Resource.new( + metadata: { name: "gitlab-#{namespace}", namespace: namespace }, + roleRef: role_ref, + subjects: subjects + ) + end + + subject do + described_class.new( + role_name: role_name, + namespace: namespace, + service_account_name: service_account_name + ).generate + end + + it 'should build a Kubeclient Resource' do + is_expected.to eq(resource) + end +end diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb index 553cd8719de..7abb9688d5a 100644 --- a/spec/lib/quality/helm_client_spec.rb +++ b/spec/lib/quality/helm_client_spec.rb @@ -1,62 +1,111 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Quality::HelmClient do let(:namespace) { 'review-apps-ee' } let(:release_name) { 'my-release' } - let(:raw_helm_list_result) do + let(:raw_helm_list_page1) do <<~OUTPUT - NAME REVISION UPDATED STATUS CHART NAMESPACE - review-improve-re-2dsd9d 1 Tue Jul 31 15:53:17 2018 FAILED gitlab-0.3.4 #{namespace} - review-11-1-stabl-3r2fso 1 Mon Jul 30 22:44:14 2018 FAILED gitlab-0.3.3 #{namespace} - review-49375-css-fk664j 1 Thu Jul 19 11:01:30 2018 FAILED gitlab-0.2.4 #{namespace} + {"Next":"review-6709-group-t40qbv", + "Releases":[ + {"Name":"review-qa-60-reor-1mugd1", "Revision":1,"Updated":"Thu Oct 4 17:52:31 2018","Status":"FAILED", "Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-7846-fix-s-261vd6","Revision":1,"Updated":"Thu Oct 4 17:33:29 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-7867-snowp-lzo3iy","Revision":1,"Updated":"Thu Oct 4 17:22:14 2018","Status":"DEPLOYED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-rename-geo-o4a780","Revision":1,"Updated":"Thu Oct 4 17:14:57 2018","Status":"DEPLOYED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-5781-opera-0k93fx","Revision":1,"Updated":"Thu Oct 4 17:06:15 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-6709-group-2pzeec","Revision":1,"Updated":"Thu Oct 4 16:36:59 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-ce-to-ee-2-l554mn","Revision":1,"Updated":"Thu Oct 4 16:27:02 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-epics-e2e-m690eb","Revision":1,"Updated":"Thu Oct 4 16:08:26 2018","Status":"DEPLOYED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-7126-admin-06fae2","Revision":1,"Updated":"Thu Oct 4 15:56:35 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"}, + {"Name":"review-6983-promo-xyou11","Revision":1,"Updated":"Thu Oct 4 15:15:34 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"} + ]} + OUTPUT + end + let(:raw_helm_list_page2) do + <<~OUTPUT + {"Releases":[ + {"Name":"review-6709-group-t40qbv","Revision":1,"Updated":"Thu Oct 4 17:52:31 2018","Status":"FAILED","Chart":"gitlab-1.1.3","AppVersion":"master","Namespace":"#{namespace}"} + ]} OUTPUT end subject { described_class.new(namespace: namespace) } describe '#releases' do + it 'raises an error if the Helm command fails' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + + expect { subject.releases.to_a }.to raise_error(described_class::CommandFailedError) + end + it 'calls helm list with default arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}")]) - .and_return(Gitlab::Popen::Result.new([], '')) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) - subject.releases + subject.releases.to_a end - it 'calls helm list with given arguments' do + it 'calls helm list with extra arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --deployed)]) - .and_return(Gitlab::Popen::Result.new([], '')) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) - subject.releases(args: ['--deployed']) + subject.releases(args: ['--deployed']).to_a end it 'returns a list of Release objects' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with([%(helm list --namespace "#{namespace}" --deployed)]) - .and_return(Gitlab::Popen::Result.new([], raw_helm_list_result)) - - releases = subject.releases(args: ['--deployed']) - - expect(releases.size).to eq(3) - expect(releases[0].name).to eq('review-improve-re-2dsd9d') - expect(releases[0].revision).to eq(1) - expect(releases[0].last_update).to eq(Time.parse('Tue Jul 31 15:53:17 2018')) - expect(releases[0].status).to eq('FAILED') - expect(releases[0].chart).to eq('gitlab-0.3.4') - expect(releases[0].namespace).to eq(namespace) + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --deployed)]) + .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true))) + + releases = subject.releases(args: ['--deployed']).to_a + + expect(releases.size).to eq(1) + expect(releases[0]).to have_attributes( + name: 'review-6709-group-t40qbv', + revision: 1, + last_update: Time.parse('Thu Oct 4 17:52:31 2018'), + status: 'FAILED', + chart: 'gitlab-1.1.3', + app_version: 'master', + namespace: namespace + ) + end + + it 'automatically paginates releases' do + expect(Gitlab::Popen).to receive(:popen_with_detail).ordered + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json)]) + .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page1, '', double(success?: true))) + expect(Gitlab::Popen).to receive(:popen_with_detail).ordered + .with([%(helm list --namespace "#{namespace}" --tiller-namespace "#{namespace}" --output json --offset review-6709-group-t40qbv)]) + .and_return(Gitlab::Popen::Result.new([], raw_helm_list_page2, '', double(success?: true))) + + releases = subject.releases.to_a + + expect(releases.size).to eq(11) + expect(releases.last.name).to eq('review-6709-group-t40qbv') end end describe '#delete' do + it 'raises an error if the Helm command fails' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + + expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) + end + it 'calls helm delete with default arguments' do expect(Gitlab::Popen).to receive(:popen_with_detail) - .with(["helm delete --purge #{release_name}"]) - .and_return(Gitlab::Popen::Result.new([], '', '', 0)) + .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) - expect(subject.delete(release_name: release_name).status).to eq(0) + expect(subject.delete(release_name: release_name)).to eq('') end end end diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb index 3c0c0d0977a..f35d9464d48 100644 --- a/spec/lib/quality/kubernetes_client_spec.rb +++ b/spec/lib/quality/kubernetes_client_spec.rb @@ -1,25 +1,33 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Quality::KubernetesClient do - subject { described_class.new(namespace: 'review-apps-ee') } + let(:namespace) { 'review-apps-ee' } + let(:release_name) { 'my-release' } + + subject { described_class.new(namespace: namespace) } describe '#cleanup' do + it 'raises an error if the Kubernetes command fails' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl --namespace "#{namespace}" delete ) \ + 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ + "--now -l release=\"#{release_name}\""]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + + expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError) + end + it 'calls kubectl with the correct arguments' do - # popen_with_detail will receive an array with a bunch of arguments; we're - # only concerned with it having the correct namespace and release name - expect(Gitlab::Popen).to receive(:popen_with_detail) do |args| - expect(args) - .to satisfy_one { |arg| arg.start_with?('-n "review-apps-ee" get') } - expect(args) - .to satisfy_one { |arg| arg == 'grep "my-release"' } - expect(args) - .to satisfy_one { |arg| arg.end_with?('-n "review-apps-ee" delete') } - end + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl --namespace "#{namespace}" delete ) \ + 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ + "--now -l release=\"#{release_name}\""]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) # We're not verifying the output here, just silencing it - expect { subject.cleanup(release_name: 'my-release') }.to output.to_stdout + expect { subject.cleanup(release_name: release_name) }.to output.to_stdout end end end diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb index 034e8a6a4e5..baf16c2ce53 100644 --- a/spec/migrations/rename_more_reserved_project_names_spec.rb +++ b/spec/migrations/rename_more_reserved_project_names_spec.rb @@ -31,7 +31,16 @@ describe RenameMoreReservedProjectNames, :delete do context 'when exception is raised during rename' do before do - allow(project).to receive(:rename_repo).and_raise(StandardError) + service = instance_double('service') + + allow(service) + .to receive(:execute) + .and_raise(Projects::AfterRenameService::RenameFailedError) + + allow(Projects::AfterRenameService) + .to receive(:new) + .with(project) + .and_return(service) end it 'captures exception from project rename' do diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb index 592ac2b5fb9..7818aa0d560 100644 --- a/spec/migrations/rename_reserved_project_names_spec.rb +++ b/spec/migrations/rename_reserved_project_names_spec.rb @@ -35,7 +35,16 @@ describe RenameReservedProjectNames, :migration, schema: :latest do context 'when exception is raised during rename' do before do - allow(project).to receive(:rename_repo).and_raise(StandardError) + service = instance_double('service') + + allow(service) + .to receive(:execute) + .and_raise(Projects::AfterRenameService::RenameFailedError) + + allow(Projects::AfterRenameService) + .to receive(:new) + .with(project) + .and_return(service) end it 'captures exception from project rename' do diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 85fad77a242..fb5bec4108a 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -194,6 +194,14 @@ describe Ci::JobArtifact do end end + context 'when file format is raw' do + let(:artifact) { build(:ci_job_artifact, :codequality, file_format: :raw) } + + it 'iterates blob once' do + expect { |b| artifact.each_blob(&b) }.to yield_control.once + end + end + context 'when there are no adapters for the file format' do let(:artifact) { build(:ci_job_artifact, :junit, file_format: :zip) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3b01b39ecab..153244b2159 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -779,6 +779,41 @@ describe Ci::Pipeline, :mailer do end end + describe 'ref_exists?' do + context 'when repository exists' do + using RSpec::Parameterized::TableSyntax + + let(:project) { create(:project, :repository) } + + where(:tag, :ref, :result) do + false | 'master' | true + false | 'non-existent-branch' | false + true | 'v1.1.0' | true + true | 'non-existent-tag' | false + end + + with_them do + let(:pipeline) do + create(:ci_empty_pipeline, project: project, tag: tag, ref: ref) + end + + it "correctly detects ref" do + expect(pipeline.ref_exists?).to be result + end + end + end + + context 'when repository does not exist' do + let(:pipeline) do + create(:ci_empty_pipeline, project: project, ref: 'master') + end + + it 'always returns false' do + expect(pipeline.ref_exists?).to eq false + end + end + end + context 'with non-empty project' do let(:project) { create(:project, :repository) } diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 42448b42c8e..d5fb1a9d010 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -17,7 +17,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.31') + expect(application.reload.version).to eq('0.1.35') end end end @@ -45,7 +45,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.31') + expect(subject.version).to eq('0.1.35') expect(subject).not_to be_rbac expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.files).to eq(gitlab_runner.files) @@ -63,7 +63,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.31') + expect(subject.version).to eq('0.1.35') end end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 34d321ec604..f5c4b0b66ae 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -11,6 +11,9 @@ describe Clusters::Cluster do it { is_expected.to have_one(:application_ingress) } it { is_expected.to have_one(:application_prometheus) } it { is_expected.to have_one(:application_runner) } + it { is_expected.to have_many(:kubernetes_namespaces) } + it { is_expected.to have_one(:kubernetes_namespace) } + it { is_expected.to delegate_method(:status).to(:provider) } it { is_expected.to delegate_method(:status_reason).to(:provider) } it { is_expected.to delegate_method(:status_name).to(:provider) } @@ -20,6 +23,7 @@ describe Clusters::Cluster do it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix } it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix } it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix } + it { is_expected.to respond_to :project } describe '.enabled' do diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb new file mode 100644 index 00000000000..dea58fa26c7 --- /dev/null +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::KubernetesNamespace, type: :model do + it { is_expected.to belong_to(:cluster_project) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:cluster) } + it { is_expected.to have_one(:platform_kubernetes) } + + describe 'namespace uniqueness validation' do + let(:cluster_project) { create(:cluster_project) } + + let(:kubernetes_namespace) do + build(:cluster_kubernetes_namespace, + cluster: cluster_project.cluster, + project: cluster_project.project, + cluster_project: cluster_project) + end + + subject { kubernetes_namespace } + + context 'when cluster is using the namespace' do + before do + create(:cluster_kubernetes_namespace, + cluster: cluster_project.cluster, + project: cluster_project.project, + cluster_project: cluster_project, + namespace: kubernetes_namespace.namespace) + end + + it { is_expected.not_to be_valid } + end + + context 'when cluster is not using the namespace' do + it { is_expected.to be_valid } + end + end + + describe '#set_namespace_and_service_account_to_default' do + let(:cluster) { platform.cluster } + let(:cluster_project) { create(:cluster_project, cluster: cluster) } + let(:kubernetes_namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster_project.cluster, + project: cluster_project.project, + cluster_project: cluster_project) + end + + describe 'namespace' do + let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) } + + subject { kubernetes_namespace.namespace } + + context 'when platform has a namespace assigned' do + let(:namespace) { 'platform-namespace' } + + it 'should copy the namespace' do + is_expected.to eq('platform-namespace') + end + end + + context 'when platform does not have namespace assigned' do + let(:namespace) { nil } + + it 'should set default namespace' do + project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}" + + is_expected.to eq(project_slug) + end + end + end + + describe 'service_account_name' do + let(:platform) { create(:cluster_platform_kubernetes) } + + subject { kubernetes_namespace.service_account_name } + + it 'should set a service account name based on namespace' do + is_expected.to eq("#{kubernetes_namespace.namespace}-service-account") + end + end + end +end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 66198d5ee2b..e13eb554add 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -9,6 +9,15 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to be_kind_of(ReactiveCaching) } it { is_expected.to respond_to :ca_pem } + it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) } + it { is_expected.to validate_presence_of(:api_url) } + it { is_expected.to validate_presence_of(:token) } + + it { is_expected.to delegate_method(:project).to(:cluster) } + it { is_expected.to delegate_method(:enabled?).to(:cluster) } + it { is_expected.to delegate_method(:managed?).to(:cluster) } + it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) } + describe 'before_validation' do context 'when namespace includes upper case' do let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) } @@ -90,6 +99,28 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { expect(kubernetes.save).to be_falsey } end end + + describe 'when using reserved namespaces' do + subject { build(:cluster_platform_kubernetes, namespace: namespace) } + + context 'when no namespace is manually assigned' do + let(:namespace) { nil } + + it { is_expected.to be_valid } + end + + context 'when no reserved namespace is assigned' do + let(:namespace) { 'my-namespace' } + + it { is_expected.to be_valid } + end + + context 'when reserved namespace is assigned' do + let(:namespace) { 'gitlab-managed-apps' } + + it { is_expected.not_to be_valid } + end + end end describe '#kubeclient' do @@ -117,41 +148,39 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end describe '#actual_namespace' do - subject { kubernetes.actual_namespace } - - let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } + let(:cluster) { create(:cluster, :project) } let(:project) { cluster.project } - let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) } - context 'when namespace is present' do + let(:platform) do + create(:cluster_platform_kubernetes, + cluster: cluster, + namespace: namespace) + end + + subject { platform.actual_namespace } + + context 'with a namespace assigned' do let(:namespace) { 'namespace-123' } it { is_expected.to eq(namespace) } end - context 'when namespace is not present' do + context 'with no namespace assigned' do let(:namespace) { nil } - it { is_expected.to eq("#{project.path}-#{project.id}") } - end - end - - describe '#default_namespace' do - subject { kubernetes.send(:default_namespace) } + context 'when kubernetes namespace is present' do + let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } - let(:kubernetes) { create(:cluster_platform_kubernetes, :configured) } + before do + kubernetes_namespace + end - context 'when cluster belongs to a project' do - let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } - let(:project) { cluster.project } - - it { is_expected.to eq("#{project.path}-#{project.id}") } - end - - context 'when cluster belongs to nothing' do - let!(:cluster) { create(:cluster, platform_kubernetes: kubernetes) } + it { is_expected.to eq(kubernetes_namespace.namespace) } + end - it { is_expected.to be_nil } + context 'when kubernetes namespace is not present' do + it { is_expected.to eq("#{project.path}-#{project.id}") } + end end end diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb index 7d75d6ab345..82ef5a23c18 100644 --- a/spec/models/clusters/project_spec.rb +++ b/spec/models/clusters/project_spec.rb @@ -3,4 +3,6 @@ require 'spec_helper' describe Clusters::Project do it { is_expected.to belong_to(:cluster) } it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:kubernetes_namespaces) } + it { is_expected.to have_one(:kubernetes_namespace) } end diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb index 729056b6abc..66c1f47d12b 100644 --- a/spec/models/concerns/relative_positioning_spec.rb +++ b/spec/models/concerns/relative_positioning_spec.rb @@ -6,9 +6,13 @@ describe RelativePositioning do let(:issue1) { create(:issue, project: project) } let(:new_issue) { create(:issue, project: project) } - before do - [issue, issue1].each do |issue| - issue.move_to_end && issue.save + describe '.move_to_end' do + it 'moves the object to the end' do + Issue.move_to_end([issue, issue1]) + + expect(issue1.prev_relative_position).to eq issue.relative_position + expect(issue.prev_relative_position).to eq nil + expect(issue1.next_relative_position).to eq nil end end @@ -59,6 +63,12 @@ describe RelativePositioning do end describe '#move_to_end' do + before do + [issue, issue1].each do |issue| + issue.move_to_end && issue.save + end + end + it 'moves issue to the end' do new_issue.move_to_end @@ -67,6 +77,12 @@ describe RelativePositioning do end describe '#shift_after?' do + before do + [issue, issue1].each do |issue| + issue.move_to_end && issue.save + end + end + it 'returns true' do issue.update(relative_position: issue1.relative_position - 1) @@ -81,6 +97,12 @@ describe RelativePositioning do end describe '#shift_before?' do + before do + [issue, issue1].each do |issue| + issue.move_to_end && issue.save + end + end + it 'returns true' do issue.update(relative_position: issue1.relative_position + 1) @@ -95,6 +117,12 @@ describe RelativePositioning do end describe '#move_between' do + before do + [issue, issue1].each do |issue| + issue.move_to_end && issue.save + end + end + it 'positions issue between two other' do new_issue.move_between(issue, issue1) diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 182070781dd..b8364e0cf88 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -57,7 +57,7 @@ describe Deployment do last_deployments = described_class.last_for_environment([staging, production, testing]) expect(last_deployments.size).to eq(2) - expect(last_deployments).to eq(deployments.last(2)) + expect(last_deployments).to match_array(deployments.last(2)) end end end diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb new file mode 100644 index 00000000000..f2eb263c98c --- /dev/null +++ b/spec/models/environment_status_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe EnvironmentStatus do + let(:deployment) { create(:deployment, :review_app) } + let(:environment) { deployment.environment} + let(:project) { deployment.project } + let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } + + subject(:environment_status) { described_class.new(environment, merge_request) } + + it { is_expected.to delegate_method(:id).to(:environment) } + it { is_expected.to delegate_method(:name).to(:environment) } + it { is_expected.to delegate_method(:project).to(:environment) } + it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) } + + describe '#project' do + subject { environment_status.project } + + it { is_expected.to eq(project) } + end + + describe '#merge_request' do + subject { environment_status.merge_request } + + it { is_expected.to eq(merge_request) } + end + + describe '#deployment' do + subject { environment_status.deployment } + + it { is_expected.to eq(deployment) } + end + + # $ git diff --stat pages-deploy-target...pages-deploy + # .gitlab/route-map.yml | 5 +++++ + # files/html/500.html | 13 ------------- + # files/html/page.html | 3 +++ + # files/js/application.js | 3 +++ + # files/markdown/ruby-style-guide.md | 4 ++++ + # pages-deploy.txt | 1 + + # + # $ cat .gitlab/route-map.yml + # - source: /files\/markdown\/(.+)\.md$/ + # public: '\1.html' + # + # - source: /files\/(.+)/ + # public: '\1' + describe '#changes' do + it 'contains only added and modified public pages' do + expect(environment_status.changes).to contain_exactly( + { + path: 'ruby-style-guide.html', + external_url: "#{environment.external_url}/ruby-style-guide.html" + }, { + path: 'html/page.html', + external_url: "#{environment.external_url}/html/page.html" + } + ) + end + end +end diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb deleted file mode 100644 index 32e33e8f42f..00000000000 --- a/spec/models/forked_project_link_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'spec_helper' - -describe ForkedProjectLink, "add link on fork" do - include ProjectForksHelper - - let(:project_from) { create(:project, :repository) } - let(:project_to) { fork_project(project_from, user) } - let(:user) { create(:user) } - - before do - project_from.add_reporter(user) - end - - it 'project_from knows its forks' do - _ = project_to - - expect(project_from.forks.count).to eq(1) - end - - it "project_to knows it is forked" do - expect(project_to.forked?).to be_truthy - end - - it "project knows who it is forked from" do - expect(project_to.forked_from_project).to eq(project_from) - end - - context 'project_to is pending_delete' do - before do - project_to.update!(pending_delete: true) - end - - it { expect(project_from.forks.count).to eq(0) } - end - - context 'project_from is pending_delete' do - before do - project_from.update!(pending_delete: true) - end - - it { expect(project_to.forked_from_project).to be_nil } - end - - describe '#forked?' do - let(:project_to) { create(:project, :repository, forked_project_link: forked_project_link) } - let(:forked_project_link) { create(:forked_project_link) } - - before do - forked_project_link.forked_from_project = project_from - forked_project_link.forked_to_project = project_to - forked_project_link.save! - end - - it "project_to knows it is forked" do - expect(project_to.forked?).to be_truthy - end - - it "project_from is not forked" do - expect(project_from.forked?).to be_falsey - end - - it "project_to.destroy destroys fork_link" do - project_to.destroy - - expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false) - end - end -end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 1783dd3206b..f9be61e4768 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -865,5 +865,29 @@ describe Note do note.save! end end + + describe '#with_notes_filter' do + let!(:comment) { create(:note) } + let!(:system_note) { create(:note, system: true) } + + context 'when notes filter is nil' do + subject { described_class.with_notes_filter(nil) } + + it { is_expected.to include(comment, system_note) } + end + + context 'when notes filter is set to all notes' do + subject { described_class.with_notes_filter(UserPreference::NOTES_FILTERS[:all_notes]) } + + it { is_expected.to include(comment, system_note) } + end + + context 'when notes filter is set to only comments' do + subject { described_class.with_notes_filter(UserPreference::NOTES_FILTERS[:only_comments]) } + + it { is_expected.to include(comment) } + it { is_expected.not_to include(system_note) } + end + end end end diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index f4f7afb1b92..ee84fa95f0e 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -245,6 +245,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do end def bamboo_response(result_key: 42, build_state: 'success', size: 1) - %Q({"results":{"results":{"size":"#{size}","result":{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}}}}) + # reference: https://docs.atlassian.com/atlassian-bamboo/REST/6.2.5/#d2e786 + %Q({"results":{"results":{"size":"#{size}","result":[{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}]}}}) end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 88f70a88210..62a38c66d99 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -23,7 +23,6 @@ describe Project do it { is_expected.to have_many(:deploy_keys) } it { is_expected.to have_many(:hooks) } it { is_expected.to have_many(:protected_branches) } - it { is_expected.to have_one(:forked_project_link) } it { is_expected.to have_one(:slack_service) } it { is_expected.to have_one(:microsoft_teams_service) } it { is_expected.to have_one(:mattermost_service) } @@ -56,7 +55,7 @@ describe Project do it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') } it { is_expected.to have_one(:import_data).class_name('ProjectImportData') } it { is_expected.to have_one(:last_event).class_name('Event') } - it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } + it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) } it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:pipelines) } @@ -77,7 +76,8 @@ describe Project do it { is_expected.to have_many(:lfs_objects_projects) } it { is_expected.to have_many(:project_group_links) } it { is_expected.to have_many(:notification_settings).dependent(:delete_all) } - it { is_expected.to have_many(:forks).through(:forked_project_links) } + it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') } + it { is_expected.to have_many(:forks).through(:forked_to_members) } it { is_expected.to have_many(:uploads) } it { is_expected.to have_many(:pipeline_schedules) } it { is_expected.to have_many(:members_and_requesters) } @@ -1380,7 +1380,7 @@ describe Project do context 'when checking on forked project' do let(:project) { create(:project, :internal) } - let(:forked_project) { create(:project, forked_from_project: project) } + let(:forked_project) { fork_project(project) } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy } @@ -1992,9 +1992,12 @@ describe Project do let(:import_jid) { '123' } context 'forked' do - let(:forked_project_link) { create(:forked_project_link, :forked_to_empty_project) } - let(:forked_from_project) { forked_project_link.forked_from_project } - let(:project) { forked_project_link.forked_to_project } + let(:forked_from_project) { create(:project, :repository) } + let(:project) { create(:project) } + + before do + fork_project(forked_from_project, nil, target_project: project) + end it 'schedules a RepositoryForkWorker job' do expect(RepositoryForkWorker).to receive(:perform_async).with(project.id).and_return(import_jid) @@ -2281,6 +2284,12 @@ describe Project do end end + describe '#forks' do + it 'includes direct forks of the project' do + expect(project.forks).to contain_exactly(forked_project) + end + end + describe '#lfs_storage_project' do it 'returns self for non-forks' do expect(project.lfs_storage_project).to eq project @@ -2956,88 +2965,6 @@ describe Project do end end - describe '#rename_repo' do - before do - # Project#gitlab_shell returns a new instance of Gitlab::Shell on every - # call. This makes testing a bit easier. - allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) - allow(project).to receive(:previous_changes).and_return('path' => ['foo']) - stub_feature_flags(skip_hashed_storage_upgrade: false) - end - - it 'renames a repository' do - stub_container_registry_config(enabled: false) - - expect(gitlab_shell).to receive(:mv_repository) - .ordered - .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}") - .and_return(true) - - expect(gitlab_shell).to receive(:mv_repository) - .ordered - .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki") - .and_return(true) - - expect_any_instance_of(SystemHooksService) - .to receive(:execute_hooks_for) - .with(project, :rename) - - expect_any_instance_of(Gitlab::UploadsTransfer) - .to receive(:rename_project) - .with('foo', project.path, project.namespace.full_path) - - expect(project).to receive(:expire_caches_before_rename) - - project.rename_repo - end - - context 'container registry with images' do - let(:container_repository) { create(:container_repository) } - - before do - stub_container_registry_config(enabled: true) - stub_container_registry_tags(repository: :any, tags: ['tag']) - project.container_repositories << container_repository - end - - subject { project.rename_repo } - - it { expect { subject }.to raise_error(StandardError) } - end - - context 'gitlab pages' do - before do - expect(project_storage).to receive(:rename_repo) { true } - end - - it 'moves pages folder to new location' do - expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) - - project.rename_repo - end - end - - context 'attachments' do - before do - expect(project_storage).to receive(:rename_repo) { true } - end - - it 'moves uploads folder to new location' do - expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project) - - project.rename_repo - end - end - - it 'updates project full path in .git/config' do - allow(project_storage).to receive(:rename_repo).and_return(true) - - project.rename_repo - - expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) - end - end - describe '#pages_path' do it 'returns a path where pages are stored' do expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path)) @@ -3128,91 +3055,6 @@ describe Project do end end - describe '#rename_repo' do - before do - # Project#gitlab_shell returns a new instance of Gitlab::Shell on every - # call. This makes testing a bit easier. - allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) - allow(project).to receive(:previous_changes).and_return('path' => ['foo']) - stub_feature_flags(skip_hashed_storage_upgrade: false) - end - - context 'migration to hashed storage' do - it 'calls HashedStorageMigrationService with correct options' do - project = create(:project, :repository, :legacy_storage) - allow(project).to receive(:previous_changes).and_return('path' => ['foo']) - - expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service| - expect(service).to receive(:execute).and_return(true) - end - - project.rename_repo - end - end - - it 'renames a repository' do - stub_container_registry_config(enabled: false) - - expect(gitlab_shell).not_to receive(:mv_repository) - - expect_any_instance_of(SystemHooksService) - .to receive(:execute_hooks_for) - .with(project, :rename) - - expect(project).to receive(:expire_caches_before_rename) - - project.rename_repo - end - - context 'container registry with images' do - let(:container_repository) { create(:container_repository) } - - before do - stub_container_registry_config(enabled: true) - stub_container_registry_tags(repository: :any, tags: ['tag']) - project.container_repositories << container_repository - end - - subject { project.rename_repo } - - it { expect { subject }.to raise_error(StandardError) } - end - - context 'gitlab pages' do - it 'moves pages folder to new location' do - expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) - - project.rename_repo - end - end - - context 'attachments' do - it 'keeps uploads folder location unchanged' do - expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project) - - project.rename_repo - end - - context 'when not rolled out' do - let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) } - - it 'moves pages folder to hashed storage' do - expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service| - expect(service).to receive(:execute) - end - - project.rename_repo - end - end - end - - it 'updates project full path in .git/config' do - project.rename_repo - - expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) - end - end - describe '#pages_path' do it 'returns a path where pages are stored' do expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path)) diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb new file mode 100644 index 00000000000..64d9d9a78b4 --- /dev/null +++ b/spec/models/user_preference_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UserPreference do + describe '#set_notes_filter' do + let(:issuable) { build_stubbed(:issue) } + let(:user_preference) { create(:user_preference) } + let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] } + + it 'returns updated discussion filter' do + filter_name = + user_preference.set_notes_filter(only_comments, issuable) + + expect(filter_name).to eq(only_comments) + end + + it 'updates discussion filter for issuable class' do + user_preference.set_notes_filter(only_comments, issuable) + + expect(user_preference.reload.issue_notes_filter).to eq(only_comments) + end + + context 'when notes_filter parameter is invalid' do + it 'returns the current notes filter' do + user_preference.set_notes_filter(only_comments, issuable) + + expect(user_preference.set_notes_filter(9999, issuable)).to eq(only_comments) + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 99d17f563d9..b3474e74aa4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -715,6 +715,15 @@ describe User do end end + describe 'ensure user preference' do + it 'has user preference upon user initialization' do + user = build(:user) + + expect(user.user_preference).to be_present + expect(user.user_preference).not_to be_persisted + end + end + describe 'ensure incoming email token' do it 'has incoming email token' do user = create(:user) diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index a42d1f3d399..170e0ac5717 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -40,21 +40,23 @@ describe Ci::BuildRunnerPresenter do context "with reports" do Ci::JobArtifact::DEFAULT_FILE_NAMES.each do |file_type, filename| - let(:report) { { "#{file_type}": [filename] } } - let(:build) { create(:ci_build, options: { artifacts: { reports: report } } ) } - - let(:report_expectation) do - { - name: filename, - artifact_type: :"#{file_type}", - artifact_format: :gzip, - paths: [filename], - when: 'always' - } - end - - it 'presents correct hash' do - expect(presenter.artifacts.first).to include(report_expectation) + context file_type.to_s do + let(:report) { { "#{file_type}": [filename] } } + let(:build) { create(:ci_build, options: { artifacts: { reports: report } } ) } + + let(:report_expectation) do + { + name: filename, + artifact_type: :"#{file_type}", + artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(file_type), + paths: [filename], + when: 'always' + } + end + + it 'presents correct hash' do + expect(presenter.artifacts.first).to include(report_expectation) + end end end end diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb index f56bc932f40..270e12bf201 100644 --- a/spec/requests/api/applications_spec.rb +++ b/spec/requests/api/applications_spec.rb @@ -5,6 +5,7 @@ describe API::Applications, :api do let(:admin_user) { create(:user, admin: true) } let(:user) { create(:user, admin: false) } + let!(:application) { create(:application, name: 'another_application', redirect_uri: 'http://other_application.url', scopes: '') } describe 'POST /applications' do context 'authenticated and authorized user' do @@ -15,7 +16,7 @@ describe API::Applications, :api do application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url') - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status(201) expect(json_response).to be_a Hash expect(json_response['application_id']).to eq application.uid expect(json_response['secret']).to eq application.secret @@ -27,7 +28,7 @@ describe API::Applications, :api do post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: '' end.not_to change { Doorkeeper::Application.count } - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status(400) expect(json_response).to be_a Hash expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.') end @@ -37,7 +38,7 @@ describe API::Applications, :api do post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: '' end.not_to change { Doorkeeper::Application.count } - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status(400) expect(json_response).to be_a Hash expect(json_response['error']).to eq('name is missing') end @@ -47,7 +48,7 @@ describe API::Applications, :api do post api('/applications', admin_user), name: 'application_name', scopes: '' end.not_to change { Doorkeeper::Application.count } - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status(400) expect(json_response).to be_a Hash expect(json_response['error']).to eq('redirect_uri is missing') end @@ -57,7 +58,7 @@ describe API::Applications, :api do post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url' end.not_to change { Doorkeeper::Application.count } - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status(400) expect(json_response).to be_a Hash expect(json_response['error']).to eq('scopes is missing') end @@ -69,7 +70,7 @@ describe API::Applications, :api do post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: '' end.not_to change { Doorkeeper::Application.count } - expect(response).to have_http_status 403 + expect(response).to have_gitlab_http_status(403) end end @@ -79,7 +80,62 @@ describe API::Applications, :api do post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url' end.not_to change { Doorkeeper::Application.count } - expect(response).to have_http_status 401 + expect(response).to have_gitlab_http_status(401) + end + end + end + + describe 'GET /applications' do + context 'authenticated and authorized user' do + it 'can list application' do + get api('/applications', admin_user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_a(Array) + end + end + + context 'authorized user without authorization' do + it 'cannot list application' do + get api('/applications', user) + + expect(response).to have_gitlab_http_status(403) + end + end + + context 'non-authenticated user' do + it 'cannot list application' do + get api('/applications') + + expect(response).to have_gitlab_http_status(401) + end + end + end + + describe 'DELETE /applications/:id' do + context 'authenticated and authorized user' do + it 'can delete an application' do + expect do + delete api("/applications/#{application.id}", admin_user) + end.to change { Doorkeeper::Application.count }.by(-1) + + expect(response).to have_gitlab_http_status(204) + end + end + + context 'authorized user without authorization' do + it 'cannot delete an application' do + delete api("/applications/#{application.id}", user) + + expect(response).to have_gitlab_http_status(403) + end + end + + context 'non-authenticated user' do + it 'cannot delete an application' do + delete api("/applications/#{application.id}") + + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 0a789d58fd8..cca449e9e56 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -368,6 +368,14 @@ describe API::Helpers do it_behaves_like 'successful sudo' end + context 'when providing username (case insensitive)' do + before do + env[API::Helpers::SUDO_HEADER] = user.username.upcase + end + + it_behaves_like 'successful sudo' + end + context 'when providing user ID' do before do env[API::Helpers::SUDO_HEADER] = user.id.to_s @@ -386,6 +394,14 @@ describe API::Helpers do it_behaves_like 'successful sudo' end + context 'when providing username (case insensitive)' do + before do + set_param(API::Helpers::SUDO_PARAM, user.username.upcase) + end + + it_behaves_like 'successful sudo' + end + context 'when providing user ID' do before do set_param(API::Helpers::SUDO_PARAM, user.id.to_s) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index c8e98e6024c..22e5a7c7174 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1196,7 +1196,7 @@ describe API::Projects do expect(response).to have_gitlab_http_status(201) expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) - expect(project_fork_target.forked_project_link).to be_present + expect(project_fork_target.fork_network_member).to be_present expect(project_fork_target).to be_forked end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 09c1d016081..e6d01c9689f 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -51,6 +51,15 @@ describe API::Users do expect(json_response[0]['username']).to eq(user.username) end + it "returns the user when a valid `username` parameter is passed (case insensitive)" do + get api("/users"), username: user.username.upcase + + expect(response).to match_response_schema('public_api/v4/user/basics') + expect(json_response.size).to eq(1) + expect(json_response[0]['id']).to eq(user.id) + expect(json_response[0]['username']).to eq(user.username) + end + it "returns an empty response when an invalid `username` parameter is passed" do get api("/users"), username: 'invalid' @@ -132,6 +141,14 @@ describe API::Users do expect(json_response.first['username']).to eq(omniauth_user.username) end + it "returns one user (case insensitive)" do + get api("/users?username=#{omniauth_user.username.upcase}", user) + + expect(response).to match_response_schema('public_api/v4/user/basics') + expect(response).to include_pagination_headers + expect(json_response.first['username']).to eq(omniauth_user.username) + end + it "returns a 403 when non-admin user searches by external UID" do get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}&provider=#{omniauth_user.identities.first.provider}", user) @@ -343,6 +360,12 @@ describe API::Users do let(:path) { "/users/#{user.username}/status" } end end + + context 'when finding the user by username (case insensitive)' do + it_behaves_like 'rendering user status' do + let(:path) { "/users/#{user.username.upcase}/status" } + end + end end describe "POST /users" do @@ -528,6 +551,18 @@ describe API::Users do expect(json_response['message']).to eq('Username has already been taken') end + 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' + 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') @@ -749,6 +784,14 @@ describe API::Users do expect(response).to have_gitlab_http_status(409) expect(@user.reload.username).to eq(@user.username) end + + 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' + + expect(response).to have_gitlab_http_status(409) + expect(@user.reload.username).to eq(@user.username) + end end end diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb new file mode 100644 index 00000000000..6894c65d639 --- /dev/null +++ b/spec/serializers/environment_status_entity_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe EnvironmentStatusEntity do + let(:user) { create(:user) } + let(:request) { double('request') } + + let(:deployment) { create(:deployment, :review_app) } + let(:environment) { deployment.environment} + let(:project) { deployment.project } + let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } + + let(:environment_status) { EnvironmentStatus.new(environment, merge_request) } + let(:entity) { described_class.new(environment_status, request: request) } + + subject { entity.as_json } + + before do + allow(request).to receive(:current_user).and_return(user) + end + + it { is_expected.to include(:id) } + it { is_expected.to include(:name) } + it { is_expected.to include(:url) } + it { is_expected.to include(:external_url) } + it { is_expected.to include(:external_url_formatted) } + it { is_expected.to include(:deployed_at) } + it { is_expected.to include(:deployed_at_formatted) } + it { is_expected.to include(:changes) } + + it { is_expected.not_to include(:stop_url) } + it { is_expected.not_to include(:metrics_url) } + it { is_expected.not_to include(:metrics_monitoring_url) } + + context 'when :ci_environments_status_changes feature flag is disabled' do + before do + stub_feature_flags(ci_environments_status_changes: false) + end + + it { is_expected.not_to include(:changes) } + end + + context 'when the user is project maintainer' do + before do + project.add_maintainer(user) + end + + it { is_expected.to include(:stop_url) } + end +end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 0ba2539a717..5bf8aa7f23f 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe MergeRequestWidgetEntity do + include ProjectForksHelper + let(:project) { create :project, :repository } let(:resource) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } @@ -206,12 +208,12 @@ describe MergeRequestWidgetEntity do describe 'when source project is deleted' do let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, forked_from_project: project) } - let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) } + let(:forked_project) { fork_project(project) } + let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) } it 'returns a blank rebase_path' do allow(merge_request).to receive(:should_be_rebased?).and_return(true) - fork_project.destroy + forked_project.destroy merge_request.reload entity = described_class.new(merge_request, request: request).as_json diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index 6337ee7d724..daf5dfba6b1 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -10,6 +10,9 @@ describe ApplicationSettings::UpdateService do before do # So the caching behaves like it would in production stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + + # Creating these settings first ensures they're used by other factories + application_settings end describe 'updating terms' do diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb new file mode 100644 index 00000000000..32fd98e6ef9 --- /dev/null +++ b/spec/services/audit_event_service_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AuditEventService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:project_member) { create(:project_member, user: user) } + let(:service) { described_class.new(user, project, { action: :destroy }) } + let(:logger) { instance_double(Gitlab::AuditJsonLogger) } + + describe '#security_event' do + before do + expect(service).to receive(:file_logger).and_return(logger) + end + + it 'creates an event and logs to a file' do + expect(logger).to receive(:info).with(author_id: user.id, + entity_id: project.id, + entity_type: "Project", + action: :destroy) + + expect { service.security_event }.to change(SecurityEvent, :count).by(1) + end + end +end diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb index 9f47439dc4a..9a53b32394d 100644 --- a/spec/services/ci/process_build_service_spec.rb +++ b/spec/services/ci/process_build_service_spec.rb @@ -11,213 +11,135 @@ describe Ci::ProcessBuildService, '#execute' do project.add_maintainer(user) end - shared_examples_for 'Enqueuing properly' do |valid_statuses_for_when| - valid_statuses_for_when.each do |status_for_prior_stages| - context "when status for prior stages is #{status_for_prior_stages}" do - let(:current_status) { status_for_prior_stages } - - %w[created skipped manual scheduled].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) } - - it 'enqueues the build' do - expect { subject }.to change { build.status }.to('pending') - end - end - end + context 'when build has on_success option' do + let(:build) { create(:ci_build, :created, when: :on_success, user: user, project: project) } - %w[pending running success failed canceled].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) } + context 'when current status is success' do + let(:current_status) { 'success' } - it 'does not change the build status' do - expect { subject }.not_to change { build.status } - end - end - end + it 'changes the build status' do + expect { subject }.to change { build.status }.to('pending') end end - (HasStatus::AVAILABLE_STATUSES - valid_statuses_for_when).each do |status_for_prior_stages| - let(:current_status) { status_for_prior_stages } - - context "when status for prior stages is #{status_for_prior_stages}" do - %w[created pending].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) } + context 'when current status is failed' do + let(:current_status) { 'failed' } - it 'skips the build' do - expect { subject }.to change { build.status }.to('skipped') - end - end - end - - (HasStatus::AVAILABLE_STATUSES - %w[created pending]).each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, when: when_option, user: user, project: project) } - - it 'does not change build status' do - expect { subject }.not_to change { build.status } - end - end - end + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('skipped') end end end - shared_examples_for 'Actionizing properly' do |valid_statuses_for_when| - valid_statuses_for_when.each do |status_for_prior_stages| - context "when status for prior stages is #{status_for_prior_stages}" do - let(:current_status) { status_for_prior_stages } - - %w[created].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) } - - it 'enqueues the build' do - expect { subject }.to change { build.status }.to('manual') - end - end - end + context 'when build has on_failure option' do + let(:build) { create(:ci_build, :created, when: :on_failure, user: user, project: project) } - %w[manual skipped pending running success failed canceled scheduled].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) } + context 'when current status is success' do + let(:current_status) { 'success' } - it 'does not change the build status' do - expect { subject }.not_to change { build.status } - end - end - end + it 'changes the build status' do + expect { subject }.to change { build.status }.to('skipped') end end - (HasStatus::AVAILABLE_STATUSES - valid_statuses_for_when).each do |status_for_prior_stages| - let(:current_status) { status_for_prior_stages } - - context "when status for prior stages is #{status_for_prior_stages}" do - %w[created pending].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) } - - it 'skips the build' do - expect { subject }.to change { build.status }.to('skipped') - end - end - end - - (HasStatus::AVAILABLE_STATUSES - %w[created pending]).each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :actionable, user: user, project: project) } + context 'when current status is failed' do + let(:current_status) { 'failed' } - it 'does not change build status' do - expect { subject }.not_to change { build.status } - end - end - end + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('pending') end end end - shared_examples_for 'Scheduling properly' do |valid_statuses_for_when| - valid_statuses_for_when.each do |status_for_prior_stages| - context "when status for prior stages is #{status_for_prior_stages}" do - let(:current_status) { status_for_prior_stages } - - %w[created].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) } - - it 'enqueues the build' do - expect { subject }.to change { build.status }.to('scheduled') - end - end - end + context 'when build has always option' do + let(:build) { create(:ci_build, :created, when: :always, user: user, project: project) } - %w[manual skipped pending running success failed canceled scheduled].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) } + context 'when current status is success' do + let(:current_status) { 'success' } - it 'does not change the build status' do - expect { subject }.not_to change { build.status } - end - end - end + it 'changes the build status' do + expect { subject }.to change { build.status }.to('pending') end end - (HasStatus::AVAILABLE_STATUSES - valid_statuses_for_when).each do |status_for_prior_stages| - let(:current_status) { status_for_prior_stages } + context 'when current status is failed' do + let(:current_status) { 'failed' } - context "when status for prior stages is #{status_for_prior_stages}" do - %w[created pending].each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) } - - it 'skips the build' do - expect { subject }.to change { build.status }.to('skipped') - end - end - end - - (HasStatus::AVAILABLE_STATUSES - %w[created pending]).each do |status| - context "when build status is #{status}" do - let(:build) { create(:ci_build, status.to_sym, :schedulable, user: user, project: project) } - - it 'does not change build status' do - expect { subject }.not_to change { build.status } - end - end - end + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('pending') end end end - context 'when build has on_success option' do - let(:when_option) { :on_success } - - it_behaves_like 'Enqueuing properly', %w[success skipped] - end - - context 'when build has on_failure option' do - let(:when_option) { :on_failure } - - it_behaves_like 'Enqueuing properly', %w[failed] - end + context 'when build has manual option' do + let(:build) { create(:ci_build, :created, :actionable, user: user, project: project) } - context 'when build has always option' do - let(:when_option) { :always } + context 'when current status is success' do + let(:current_status) { 'success' } - it_behaves_like 'Enqueuing properly', %w[success failed skipped] - end + it 'changes the build status' do + expect { subject }.to change { build.status }.to('manual') + end + end - context 'when build has manual option' do - let(:when_option) { :manual } + context 'when current status is failed' do + let(:current_status) { 'failed' } - it_behaves_like 'Actionizing properly', %w[success skipped] + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('skipped') + end + end end context 'when build has delayed option' do - let(:when_option) { :delayed } - before do allow(Ci::BuildScheduleWorker).to receive(:perform_at) { } end + let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) } + context 'when ci_enable_scheduled_build is enabled' do before do stub_feature_flags(ci_enable_scheduled_build: true) end - it_behaves_like 'Scheduling properly', %w[success skipped] + context 'when current status is success' do + let(:current_status) { 'success' } + + it 'changes the build status' do + expect { subject }.to change { build.status }.to('scheduled') + end + end + + context 'when current status is failed' do + let(:current_status) { 'failed' } + + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('skipped') + end + end end - context 'when ci_enable_scheduled_build is enabled' do + context 'when ci_enable_scheduled_build is disabled' do before do stub_feature_flags(ci_enable_scheduled_build: false) end - it_behaves_like 'Actionizing properly', %w[success skipped] + context 'when current status is success' do + let(:current_status) { 'success' } + + it 'changes the build status' do + expect { subject }.to change { build.status }.to('manual') + end + end + + context 'when current status is failed' do + let(:current_status) { 'failed' } + + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('skipped') + end + end end end end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 642de81ed52..368abded448 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -27,6 +27,7 @@ describe Ci::RetryBuildService do job_artifacts_metadata job_artifacts_trace job_artifacts_junit job_artifacts_sast job_artifacts_dependency_scanning job_artifacts_container_scanning job_artifacts_dast + job_artifacts_license_management job_artifacts_performance job_artifacts_codequality scheduled_at].freeze IGNORE_ACCESSORS = diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 7c5c7409cc1..84cfa53ea05 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -24,6 +24,12 @@ describe Groups::UpdateService do expect(TodosDestroyer::GroupPrivateWorker).not_to receive(:perform_in) end + + it "returns false if save failed" do + allow(public_group).to receive(:save).and_return(false) + + expect(service.execute).to be_falsey + end end context "internal group with internal project" do diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb new file mode 100644 index 00000000000..b4718a07204 --- /dev/null +++ b/spec/services/projects/after_rename_service_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::AfterRenameService do + let(:rugged_config) { rugged_repo(project.repository).config } + + describe '#execute' do + context 'using legacy storage' do + let(:project) { create(:project, :repository, :legacy_storage) } + let(:gitlab_shell) { Gitlab::Shell.new } + let(:project_storage) { project.send(:storage) } + + before do + # Project#gitlab_shell returns a new instance of Gitlab::Shell on every + # call. This makes testing a bit easier. + allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + + allow(project) + .to receive(:previous_changes) + .and_return('path' => ['foo']) + + allow(project) + .to receive(:path_was) + .and_return('foo') + + stub_feature_flags(skip_hashed_storage_upgrade: false) + end + + it 'renames a repository' do + stub_container_registry_config(enabled: false) + + expect(gitlab_shell).to receive(:mv_repository) + .ordered + .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}") + .and_return(true) + + expect(gitlab_shell).to receive(:mv_repository) + .ordered + .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki") + .and_return(true) + + expect_any_instance_of(SystemHooksService) + .to receive(:execute_hooks_for) + .with(project, :rename) + + expect_any_instance_of(Gitlab::UploadsTransfer) + .to receive(:rename_project) + .with('foo', project.path, project.namespace.full_path) + + expect(project).to receive(:expire_caches_before_rename) + + described_class.new(project).execute + end + + context 'container registry with images' do + let(:container_repository) { create(:container_repository) } + + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: :any, tags: ['tag']) + project.container_repositories << container_repository + end + + it 'raises a RenameFailedError' do + expect { described_class.new(project).execute } + .to raise_error(described_class::RenameFailedError) + end + end + + context 'gitlab pages' do + before do + expect(project_storage).to receive(:rename_repo) { true } + end + + it 'moves pages folder to new location' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + + described_class.new(project).execute + end + end + + context 'attachments' do + before do + expect(project_storage).to receive(:rename_repo) { true } + end + + it 'moves uploads folder to new location' do + expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project) + + described_class.new(project).execute + end + end + + it 'updates project full path in .git/config' do + allow(project_storage).to receive(:rename_repo).and_return(true) + + described_class.new(project).execute + + expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) + end + end + + context 'using hashed storage' do + let(:project) { create(:project, :repository, skip_disk_validation: true) } + let(:gitlab_shell) { Gitlab::Shell.new } + let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) } + let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) } + let(:hashed_path) { File.join(hashed_prefix, hash) } + + before do + # Project#gitlab_shell returns a new instance of Gitlab::Shell on every + # call. This makes testing a bit easier. + allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + allow(project).to receive(:previous_changes).and_return('path' => ['foo']) + + stub_feature_flags(skip_hashed_storage_upgrade: false) + stub_application_setting(hashed_storage_enabled: true) + end + + context 'migration to hashed storage' do + it 'calls HashedStorageMigrationService with correct options' do + project = create(:project, :repository, :legacy_storage) + allow(project).to receive(:previous_changes).and_return('path' => ['foo']) + + expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service| + expect(service).to receive(:execute).and_return(true) + end + + described_class.new(project).execute + end + end + + it 'renames a repository' do + stub_container_registry_config(enabled: false) + + expect(gitlab_shell).not_to receive(:mv_repository) + + expect_any_instance_of(SystemHooksService) + .to receive(:execute_hooks_for) + .with(project, :rename) + + expect(project).to receive(:expire_caches_before_rename) + + described_class.new(project).execute + end + + context 'container registry with images' do + let(:container_repository) { create(:container_repository) } + + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: :any, tags: ['tag']) + project.container_repositories << container_repository + end + + it 'raises a RenameFailedError' do + expect { described_class.new(project).execute } + .to raise_error(described_class::RenameFailedError) + end + end + + context 'gitlab pages' do + it 'moves pages folder to new location' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + + described_class.new(project).execute + end + end + + context 'attachments' do + it 'keeps uploads folder location unchanged' do + expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project) + + described_class.new(project).execute + end + + context 'when not rolled out' do + let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) } + + it 'moves pages folder to hashed storage' do + expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service| + expect(service).to receive(:execute) + end + + described_class.new(project).execute + end + end + end + + it 'updates project full path in .git/config' do + described_class.new(project).execute + + expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) + end + end + end +end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index a80c8a7fe51..08de27ca44a 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -295,6 +295,17 @@ describe Projects::CreateService, '#execute' do end end + it 'calls the passed block' do + fake_block = double('block') + opts[:relations_block] = fake_block + + expect_next_instance_of(Project) do |project| + expect(fake_block).to receive(:call).with(project) + end + + create_project(user, opts) + end + it 'writes project full path to .git/config' do project = create_project(user, opts) rugged = rugged_repo(project.repository) diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 1d31d26f418..12ddf8447bd 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -259,7 +259,6 @@ describe Projects::DestroyService do before do project.lfs_objects << create(:lfs_object) - forked_project.forked_project_link.destroy forked_project.reload end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 947cb61038d..a3d24ae312a 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -31,6 +31,10 @@ describe Projects::ForkService do it { is_expected.not_to be_persisted } it { expect(subject.errors[:forked_from_project_id]).to eq(['is forbidden']) } + + it 'does not create a fork network' do + expect { subject }.not_to change { @from_project.reload.fork_network } + end end describe "successfully creates project in the user namespace" do @@ -70,6 +74,12 @@ describe Projects::ForkService do expect(fork_network.root_project).to eq(@from_project) expect(fork_network.projects).to contain_exactly(@from_project, to_project) end + + it 'imports the repository of the forked project' do + to_project = fork_project(@from_project, @to_user, repository: true) + + expect(to_project.empty_repo?).to be_falsy + end end context 'creating a fork of a fork' do @@ -247,11 +257,13 @@ describe Projects::ForkService do context 'if project is not forked' do it 'creates fork relation' do - expect(fork_to_project.forked?).to be false + expect(fork_to_project.forked?).to be_falsy expect(forked_from_project(fork_to_project)).to be_nil subject.execute(fork_to_project) + fork_to_project.reload + expect(fork_to_project.forked?).to be true expect(forked_from_project(fork_to_project)).to eq fork_from_project expect(fork_to_project.forked_from_project).to eq fork_from_project @@ -272,6 +284,17 @@ describe Projects::ForkService do .to change { fork_to_project.lfs_objects_projects.count } .to(0) end + + context 'if the fork is not allowed' do + let(:fork_from_project) { create(:project, :private) } + + it 'does not delete the LFS objects' do + create(:lfs_objects_project, project: fork_to_project) + + expect { subject.execute(fork_to_project) } + .not_to change { fork_to_project.lfs_objects_projects.size } + end + end end end end diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 3ec6139bfa6..014aab44281 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -5,13 +5,12 @@ describe Projects::UnlinkForkService do subject { described_class.new(forked_project, user) } - let(:fork_link) { forked_project.forked_project_link } let(:project) { create(:project, :public) } let(:forked_project) { fork_project(project, user) } let(:user) { create(:user) } context 'with opened merge request on the source project' do - let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) } + let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: forked_project.forked_from_project) } let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) } let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) } @@ -35,12 +34,6 @@ describe Projects::UnlinkForkService do end end - it 'remove fork relation' do - expect(forked_project.forked_project_link).to receive(:destroy) - - subject.execute - end - it 'removes the link to the fork network' do expect(forked_project.fork_network_member).to be_present expect(forked_project.fork_network).to be_present diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index ac0ca1f33a5..41a170e4f25 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -380,6 +380,14 @@ describe QuickActions::InterpretService do end end + shared_examples 'assign command' do + it 'assigns to a single user' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(assignee_ids: [developer.id]) + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -474,67 +482,56 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end - context 'assign command' do - let(:content) { "/assign @#{developer.username}" } - - context 'Issue' do - it 'fetches assignee and populates assignee_ids if content contains /assign' do - _, updates = service.execute(content, issue) - - expect(updates[:assignee_ids]).to match_array([developer.id]) - end + context 'assign command with one user' do + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username}" } + let(:issuable) { issue } end - context 'Merge Request' do - it 'fetches assignee and populates assignee_ids if content contains /assign' do - _, updates = service.execute(content, merge_request) - - expect(updates).to eq(assignee_ids: [developer.id]) - end + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username}" } + let(:issuable) { merge_request } end end + # CE does not have multiple assignees context 'assign command with multiple assignees' do - let(:content) { "/assign @#{developer.username} @#{developer2.username}" } - before do project.add_developer(developer2) end - context 'Issue' do - it 'fetches assignee and populates assignee_ids if content contains /assign' do - _, updates = service.execute(content, issue) - - expect(updates[:assignee_ids]).to match_array([developer.id]) - end + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username} @#{developer2.username}" } + let(:issuable) { issue } end - context 'Merge Request' do - it 'fetches assignee and populates assignee_ids if content contains /assign' do - _, updates = service.execute(content, merge_request) - - expect(updates).to eq(assignee_ids: [developer.id]) - end + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username} @#{developer2.username}" } + let(:issuable) { merge_request } end end context 'assign command with me alias' do - let(:content) { "/assign me" } - - context 'Issue' do - it 'fetches assignee and populates assignee_ids if content contains /assign' do - _, updates = service.execute(content, issue) + it_behaves_like 'assign command' do + let(:content) { '/assign me' } + let(:issuable) { issue } + end - expect(updates).to eq(assignee_ids: [developer.id]) - end + it_behaves_like 'assign command' do + let(:content) { '/assign me' } + let(:issuable) { merge_request } end + end - context 'Merge Request' do - it 'fetches assignee and populates assignee_ids if content contains /assign' do - _, updates = service.execute(content, merge_request) + context 'assign command with me alias and whitespace' do + it_behaves_like 'assign command' do + let(:content) { '/assign me ' } + let(:issuable) { issue } + end - expect(updates).to eq(assignee_ids: [developer.id]) - end + it_behaves_like 'assign command' do + let(:content) { '/assign me ' } + let(:issuable) { merge_request } end end diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 622e56e1da5..b1cc6d2eb83 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -97,7 +97,7 @@ describe WebHookService do end it 'handles exceptions' do - exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError] + exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep] exceptions.each do |exception_class| exception = exception_class.new('Exception message') diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb index 1c1b68c12a2..140490f2dc2 100644 --- a/spec/support/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb @@ -22,6 +22,18 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do expect(session[:"#{provider}_access_token"]).to eq(token) expect(controller).to redirect_to(status_import_url) end + + it "strips access token with spaces" do + token = 'asdfasdf9876' + + allow_any_instance_of(Gitlab::LegacyGithubImport::Client) + .to receive(:user).and_return(true) + + post :personal_access_token, personal_access_token: " #{token} " + + expect(session[:"#{provider}_access_token"]).to eq(token) + expect(controller).to redirect_to(status_import_url) + end end shared_examples 'a GitHub-ish import controller: GET new' do diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb new file mode 100644 index 00000000000..ef638d59d2d --- /dev/null +++ b/spec/support/helpers/ci_artifact_metadata_generator.rb @@ -0,0 +1,48 @@ +# frozen_sting_literal: true + +# This generates fake CI metadata .gz for testing +# Based off https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/internal/zipartifacts/metadata.go +class CiArtifactMetadataGenerator + attr_accessor :entries, :output + + ARTIFACT_METADATA = "GitLab Build Artifacts Metadata 0.0.2\n".freeze + + def initialize(stream) + @entries = {} + @output = Zlib::GzipWriter.new(stream) + end + + def add_entry(filename) + @entries[filename] = { CRC: rand(0xfffffff), Comment: FFaker::Lorem.sentence(10) } + end + + def write + write_version + write_errors + write_entries + output.close + end + + private + + def write_version + write_string(ARTIFACT_METADATA) + end + + def write_errors + write_string('{}') + end + + def write_entries + entries.each do |filename, metadata| + write_string(filename) + write_string(metadata.to_json + "\n") + end + end + + def write_string(data) + bytes = [data.length].pack('L>') + output.write(bytes) + output.write(data) + end +end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index c077ca9f15b..a03d9c4045f 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -33,10 +33,11 @@ module KubernetesHelpers WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response) end - def stub_kubeclient_get_secret(api_url, namespace: 'default', **options) + def stub_kubeclient_get_secret(api_url, **options) options[:metadata_name] ||= "default-token-1" + options[:namespace] ||= "default" - WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{options[:metadata_name]}") + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}") .to_return(kube_response(kube_v1_secret_body(options))) end @@ -65,6 +66,21 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_create_role_binding(api_url, namespace: 'default') + WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings") + .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({})) + end + + def stub_kubeclient_get_namespace(api_url, namespace: 'default') + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}") + .to_return(kube_response({})) + end + def kube_v1_secret_body(**options) { "kind" => "SecretList", @@ -87,7 +103,8 @@ module KubernetesHelpers { "name" => "deployments", "namespaced" => true, "kind" => "Deployment" }, { "name" => "secrets", "namespaced" => true, "kind" => "Secret" }, { "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" }, - { "name" => "services", "namespaced" => true, "kind" => "Service" } + { "name" => "services", "namespaced" => true, "kind" => "Service" }, + { "name" => "namespaces", "namespaced" => true, "kind" => "Namespace" } ] } end diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index 2c501a2a27c..6a7132c3093 100644 --- a/spec/support/helpers/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb @@ -24,7 +24,7 @@ module ProjectForksHelper allow(service).to receive(:gitlab_shell).and_return(shell) end - forked_project = service.execute + forked_project = service.execute(params[:target_project]) # Reload the both projects so they know about their newly created fork_network if forked_project.persisted? diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 97875669d0e..71d72ff27e9 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -31,6 +31,8 @@ module TestEnv 'symlink-expand-diff' => '81e6355', 'expand-collapse-files' => '025db92', 'expand-collapse-lines' => '238e82d', + 'pages-deploy' => '7897d5b', + 'pages-deploy-target' => '7975be0', 'video' => '8879059', 'add-balsamiq-file' => 'b89b56d', 'crlf-diff' => '5938907', @@ -68,7 +70,6 @@ module TestEnv TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**') REPOS_STORAGE = 'default'.freeze - BROKEN_STORAGE = 'broken'.freeze # Test environment # @@ -157,10 +158,6 @@ module TestEnv version: Gitlab::GitalyClient.expected_server_version, task: "gitlab:gitaly:install[#{gitaly_dir},#{repos_path}]") do - # Re-create config, to specify the broken storage path - storage_paths = { 'default' => repos_path, 'broken' => broken_path } - Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, storage_paths, force: true) - start_gitaly(gitaly_dir) end end @@ -171,6 +168,8 @@ module TestEnv return end + FileUtils.mkdir_p("tmp/tests/second_storage") unless File.exist?("tmp/tests/second_storage") + spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s Bundler.with_original_env do raise "gitaly spawn failed" unless system(spawn_script) @@ -255,10 +254,6 @@ module TestEnv @repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path end - def broken_path - @broken_path ||= Gitlab.config.repositories.storages[BROKEN_STORAGE].legacy_disk_path - end - def backup_path Gitlab.config.backup.path end 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 new file mode 100644 index 00000000000..9c9d7ad781e --- /dev/null +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -0,0 +1,54 @@ +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 + + expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter) + expect(UserPreference.count).to eq(1) + end + + it 'expires notes e-tag cache for issuable if filter changed' do + notes_filter = UserPreference::NOTES_FILTERS[:only_comments] + + 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 + end + + it 'does not expires notes e-tag cache for issuable if filter did not change' do + notes_filter = UserPreference::NOTES_FILTERS[:only_comments] + user.set_notes_filter(notes_filter, issuable) + + 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 + 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 + + expect(user.reload.notes_filter_for(issuable)).to eq(0) + end + + it 'returns no system note' do + user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) + + get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + + expect(JSON.parse(response.body).count).to eq(1) + end + + context 'when filter is set to "only_comments"' do + it 'does not merge label event notes' do + user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) + + expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new) + + get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + end + end +end diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb index 6a9ad43941d..55212355daa 100644 --- a/spec/support/stored_repositories.rb +++ b/spec/support/stored_repositories.rb @@ -1,8 +1,4 @@ RSpec.configure do |config| - config.before(:all, :broken_storage) do - FileUtils.rm_rf Gitlab.config.repositories.storages.broken.legacy_disk_path - end - config.before(:each, :broken_storage) do allow(Gitlab::GitalyClient).to receive(:call) do raise GRPC::Unavailable.new('Gitaly broken in this spec') diff --git a/spec/views/shared/runners/show.html.haml_spec.rb b/spec/views/shared/runners/show.html.haml_spec.rb new file mode 100644 index 00000000000..5e92928b143 --- /dev/null +++ b/spec/views/shared/runners/show.html.haml_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'shared/runners/show.html.haml' do + include PageLayoutHelper + + let(:runner) do + create(:ci_runner, name: 'test runner', + version: '11.4.0', + ip_address: '127.1.2.3', + revision: 'abcd1234', + architecture: 'amd64' ) + end + + before do + assign(:runner, runner) + end + + subject do + render + rendered + end + + describe 'Page title' do + before do + expect_any_instance_of(PageLayoutHelper).to receive(:page_title).with("#{runner.description} ##{runner.id}", 'Runners') + end + + it 'sets proper page title' do + render + end + end + + describe 'Runner id and type' do + context 'when runner is of type instance' do + it { is_expected.to have_content("Runner ##{runner.id} Shared") } + end + + context 'when runner is of type group' do + let(:runner) { create(:ci_runner, :group) } + + it { is_expected.to have_content("Runner ##{runner.id} Group") } + end + + context 'when runner is of type project' do + let(:runner) { create(:ci_runner, :project) } + + it { is_expected.to have_content("Runner ##{runner.id} Specific") } + end + end + + describe 'Active value' do + context 'when runner is active' do + it { is_expected.to have_content('Active Yes') } + end + + context 'when runner is inactive' do + let(:runner) { create(:ci_runner, :inactive) } + + it { is_expected.to have_content('Active No') } + end + end + + describe 'Protected value' do + context 'when runner is not protected' do + it { is_expected.to have_content('Protected No') } + end + + context 'when runner is protected' do + let(:runner) { create(:ci_runner, :ref_protected) } + + it { is_expected.to have_content('Protected Yes') } + end + end + + describe 'Can run untagged jobs value' do + context 'when runner run untagged job is set' do + it { is_expected.to have_content('Can run untagged jobs Yes') } + end + + context 'when runner run untagged job is unset' do + let(:runner) { create(:ci_runner, :tagged_only) } + + it { is_expected.to have_content('Can run untagged jobs No') } + end + end + + describe 'Locked to this project value' do + context 'when runner locked is not set' do + it { is_expected.to have_content('Locked to this project No') } + + context 'when runner is of type group' do + let(:runner) { create(:ci_runner, :group) } + + it { is_expected.not_to have_content('Locked to this project') } + end + end + + context 'when runner locked is set' do + let(:runner) { create(:ci_runner, :locked) } + + it { is_expected.to have_content('Locked to this project Yes') } + + context 'when runner is of type group' do + let(:runner) { create(:ci_runner, :group, :locked) } + + it { is_expected.not_to have_content('Locked to this project') } + end + end + end + + describe 'Tags value' do + context 'when runner does not have tags' do + it { is_expected.to have_content('Tags') } + it { is_expected.not_to have_selector('span.badge.badge-primary')} + end + + context 'when runner have tags' do + let(:runner) { create(:ci_runner, tag_list: %w(tag2 tag3 tag1)) } + + it { is_expected.to have_content('Tags tag1 tag2 tag3') } + it { is_expected.to have_selector('span.badge.badge-primary')} + end + end + + describe 'Metadata values' do + it { is_expected.to have_content("Name #{runner.name}") } + it { is_expected.to have_content("Version #{runner.version}") } + it { is_expected.to have_content("IP Address #{runner.ip_address}") } + it { is_expected.to have_content("Revision #{runner.revision}") } + it { is_expected.to have_content("Platform #{runner.platform}") } + it { is_expected.to have_content("Architecture #{runner.architecture}") } + it { is_expected.to have_content("Description #{runner.description}") } + end + + describe 'Maximum job timeout value' do + let(:runner) { create(:ci_runner, maximum_timeout: 5400) } + + it { is_expected.to have_content('Maximum job timeout 1h 30m') } + end + + describe 'Last contact value' do + context 'when runner have not contacted yet' do + it { is_expected.to have_content('Last contact Never') } + end + + context 'when runner have already contacted' do + let(:runner) { create(:ci_runner, contacted_at: DateTime.now - 6.days) } + let(:expected_contacted_at) { I18n.localize(runner.contacted_at, format: "%b %d, %Y") } + + it { is_expected.to have_content("Last contact #{expected_contacted_at}") } + end + end +end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index eec110dfbfb..2f21a1321e1 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -71,10 +71,10 @@ describe NamespacelessProjectDestroyWorker do expect(merge_request.reload).to be_closed end - it 'destroys the link' do + it 'destroys fork network members' do subject.perform(project.id) - expect(parent_project.forked_project_links).to be_empty + expect(parent_project.forked_to_members).to be_empty end end end diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb index 20aff020dbb..936b9deaecc 100644 --- a/spec/workers/rebase_worker_spec.rb +++ b/spec/workers/rebase_worker_spec.rb @@ -1,25 +1,23 @@ require 'spec_helper' describe RebaseWorker, '#perform' do + include ProjectForksHelper + context 'when rebasing an MR from a fork where upstream has protected branches' do let(:upstream_project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository) } + let(:forked_project) { fork_project(upstream_project, nil, repository: true) } let(:merge_request) do create(:merge_request, - source_project: fork_project, + source_project: forked_project, source_branch: 'feature_conflict', target_project: upstream_project, target_branch: 'master') end - before do - create(:forked_project_link, forked_to_project: fork_project, forked_from_project: upstream_project) - end - it 'sets the correct project for running hooks' do expect(MergeRequests::RebaseService) - .to receive(:new).with(fork_project, merge_request.author).and_call_original + .to receive(:new).with(forked_project, merge_request.author).and_call_original subject.perform(merge_request, merge_request.author) end diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb index ede271b2cdd..50b93fce2dc 100644 --- a/spec/workers/repository_check/batch_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -51,7 +51,7 @@ describe RepositoryCheck::BatchWorker do it 'does nothing when shard is unhealthy' do shard_name = 'broken' - create(:project, created_at: 1.week.ago, repository_storage: shard_name) + create(:project, :broken_storage, created_at: 1.week.ago) expect(subject.perform(shard_name)).to eq(nil) end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index ac8716ecfb1..781f91ac9ca 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe RepositoryForkWorker do + include ProjectForksHelper + describe 'modules' do it 'includes ProjectImportOptions' do expect(described_class).to include_module(ProjectImportOptions) @@ -8,9 +10,13 @@ describe RepositoryForkWorker do end describe "#perform" do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :public, :repository) } let(:shell) { Gitlab::Shell.new } - let(:fork_project) { create(:project, :repository, :import_scheduled, forked_from_project: project) } + let(:forked_project) { create(:project, :repository, :import_scheduled) } + + before do + fork_project(project, forked_project.creator, target_project: forked_project, repository: true) + end shared_examples 'RepositoryForkWorker performing' do before do @@ -21,8 +27,8 @@ describe RepositoryForkWorker do expect(shell).to receive(:fork_repository).with( 'default', project.disk_path, - fork_project.repository_storage, - fork_project.disk_path + forked_project.repository_storage, + forked_project.disk_path ) end @@ -49,28 +55,28 @@ describe RepositoryForkWorker do perform! - expect(fork_project.protected_branches.first.name).to eq(fork_project.default_branch) + expect(forked_project.protected_branches.first.name).to eq(forked_project.default_branch) end it 'flushes various caches' do expect_fork_repository.and_return(true) # Works around https://github.com/rspec/rspec-mocks/issues/910 - expect(Project).to receive(:find).with(fork_project.id).and_return(fork_project) - expect(fork_project.repository).to receive(:expire_emptiness_caches) + expect(Project).to receive(:find).with(forked_project.id).and_return(forked_project) + expect(forked_project.repository).to receive(:expire_emptiness_caches) .and_call_original - expect(fork_project.repository).to receive(:expire_exists_cache) + expect(forked_project.repository).to receive(:expire_exists_cache) .and_call_original - expect(fork_project.wiki.repository).to receive(:expire_emptiness_caches) + expect(forked_project.wiki.repository).to receive(:expire_emptiness_caches) .and_call_original - expect(fork_project.wiki.repository).to receive(:expire_exists_cache) + expect(forked_project.wiki.repository).to receive(:expire_exists_cache) .and_call_original perform! end it "handles bad fork" do - error_message = "Unable to fork project #{fork_project.id} for repository #{project.disk_path} -> #{fork_project.disk_path}" + error_message = "Unable to fork project #{forked_project.id} for repository #{project.disk_path} -> #{forked_project.disk_path}" expect_fork_repository.and_return(false) @@ -80,7 +86,7 @@ describe RepositoryForkWorker do context 'only project ID passed' do def perform! - subject.perform(fork_project.id) + subject.perform(forked_project.id) end it_behaves_like 'RepositoryForkWorker performing' @@ -88,7 +94,7 @@ describe RepositoryForkWorker do context 'project ID, storage and repo paths passed' do def perform! - subject.perform(fork_project.id, TestEnv.repos_path, project.disk_path) + subject.perform(forked_project.id, TestEnv.repos_path, project.disk_path) end it_behaves_like 'RepositoryForkWorker performing' diff --git a/yarn.lock b/yarn.lock index 292c7128d18..5da401c1d43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -616,11 +616,16 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.32.0": +"@gitlab-org/gitlab-svgs@^1.23.0": version "1.32.0" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.32.0.tgz#a65ab7724fa7d55be8e5cc9b2dbe3f0757432fd3" integrity sha512-L3o8dFUd2nSkVZBwh2hCJWzNzADJ3dTBZxamND8NLosZK9/ohNhccmsQOZGyMCUHaOzm4vifaaXkAXh04UtMKA== +"@gitlab-org/gitlab-svgs@^1.33.0": + version "1.33.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.33.0.tgz#068566e8ee00795f6f09f58236f08e1716f9f04a" + integrity sha512-8ajtUHk6gQ1xosL/CO5IzHSFM/t18hx5pfzQ3cd0VuQXcyR6QKGuXTLwbYdmJDYOw1Etoo5DqDWxPEClHyZpiA== + "@gitlab-org/gitlab-ui@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-ui/-/gitlab-ui-1.8.0.tgz#dee33d78f68c91644273dbd51734b796108263ee" @@ -1325,18 +1330,6 @@ binaryextensions@2: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935" integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA== -blackst0ne-mermaid@^7.1.0-fixed: - version "7.1.0-fixed" - resolved "https://registry.yarnpkg.com/blackst0ne-mermaid/-/blackst0ne-mermaid-7.1.0-fixed.tgz#3707b3a113d78610e3068e18a588f46b4688de49" - integrity sha1-NwezoRPXhhDjBo4YpYj0a0aI3kk= - dependencies: - d3 "3.5.17" - dagre-d3-renderer "^0.4.24" - dagre-layout "^0.8.0" - he "^1.1.1" - lodash "^4.17.4" - moment "^2.18.1" - blob@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" @@ -2304,6 +2297,11 @@ d3-format@1, d3-format@1.2.1: resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f" integrity sha512-U4zRVLDXW61bmqoo+OJ/V687e1T5nVd3TAKAJKgtpZ/P1JsMgyod0y9br+mlQOryTAACdiXI3wCjuERHFNp91w== +d3-format@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" + integrity sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw== + d3-geo@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356" @@ -2376,6 +2374,11 @@ d3-selection@1, d3-selection@1.2.0, d3-selection@^1.1.0, d3-selection@^1.2.0: resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88" integrity sha512-xW2Pfcdzh1gOaoI+LGpPsLR2VpBQxuFoxvrvguK8ZmrJbPIVvfNG6pU6GNfK41D6Qz15sj61sbW/AFYuukwaLQ== +d3-selection@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d" + integrity sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA== + d3-shape@1.2.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" @@ -2428,11 +2431,6 @@ d3-zoom@1.7.1: d3-selection "1" d3-transition "1" -d3@3.5.17: - version "3.5.17" - resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" - integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g= - d3@4.12.2: version "4.12.2" resolved "https://registry.yarnpkg.com/d3/-/d3-4.12.2.tgz#12f775564c6a9de229f63db03446e2cb7bb56c8f" @@ -2469,23 +2467,57 @@ d3@4.12.2: d3-voronoi "1.1.2" d3-zoom "1.7.1" -dagre-d3-renderer@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.4.24.tgz#b36ce2fe4ea20de43e7698627c6ede2a9f15ec45" - integrity sha512-QCrYq80NTyKph+m/+kNeEq2exw5HPo/x7XprJem3wDGJbEAJDKXI2pJpqe0R4k6AsjWVd5NMVL0X7feF24Zh6Q== +d3@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d" + integrity sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ== dependencies: - d3 "3.5.17" - dagre-layout "^0.8.0" - graphlib "^2.1.1" - lodash "^4.17.4" + d3-array "1.2.1" + d3-axis "1.0.8" + d3-brush "1.0.4" + d3-chord "1.0.4" + d3-collection "1.0.4" + d3-color "1.0.3" + d3-dispatch "1.0.3" + d3-drag "1.2.1" + d3-dsv "1.0.8" + d3-ease "1.0.3" + d3-force "1.1.0" + d3-format "1.2.2" + d3-geo "1.9.1" + d3-hierarchy "1.1.5" + d3-interpolate "1.1.6" + d3-path "1.0.5" + d3-polygon "1.0.3" + d3-quadtree "1.0.3" + d3-queue "3.0.7" + d3-random "1.1.0" + d3-request "1.0.6" + d3-scale "1.0.7" + d3-selection "1.3.0" + d3-shape "1.2.0" + d3-time "1.0.8" + d3-time-format "2.1.1" + d3-timer "1.0.7" + d3-transition "1.1.1" + d3-voronoi "1.1.2" + d3-zoom "1.7.1" -dagre-layout@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.0.tgz#7147b6afb655602f855158dfea171db9aa98d4ff" - integrity sha512-vK4WiR6h3whkoW9aM/FCjZTTx10V2YnLOLEj2+uvOQmiEjGmUvkme+Qrjj/7Tq0+AI54yFHT/tbbqM9AadsK4A== +dagre-d3-renderer@^0.5.8: + version "0.5.8" + resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3" + integrity sha512-XH2a86isUHRxzIYbjQVEuZtJnWEufb64H5DuXIUmn8esuB40jgLEbUUclulWOW62/ZoXlj2ZDyL8SJ+YRxs+jQ== dependencies: - graphlib "^2.1.1" - lodash "^4.17.4" + dagre-layout "^0.8.8" + lodash "^4.17.5" + +dagre-layout@^0.8.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.8.tgz#9b6792f24229f402441c14162c1049e3f261f6d9" + integrity sha512-ZNV15T9za7X+fV8Z07IZquUKugCxm5owoiPPxfEx6OJRD331nkiIaF3vSt0JEY5FkrY0KfRQxcpQ3SpXB7pLPQ== + dependencies: + graphlibrary "^2.2.0" + lodash "^4.17.5" date-format@^1.2.0: version "1.2.0" @@ -3004,6 +3036,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escaper@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/escaper/-/escaper-2.5.3.tgz#8b8fe90ba364054151ab7eff18b4ce43b1e13ab5" + integrity sha512-QGb9sFxBVpbzMggrKTX0ry1oiI4CSDAl9vIL702hzl1jGW8VZs7qfqTRX7WDOjoNDoEVGcEtu1ZOQgReSfT2kQ== + escodegen@1.8.x: version "1.8.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" @@ -3893,12 +3930,12 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graphlib@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.1.tgz#42352c52ba2f4d035cb566eb91f7395f76ebc951" - integrity sha1-QjUsUrovTQNctWbrkfc5X3bryVE= +graphlibrary@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/graphlibrary/-/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6" + integrity sha512-XTcvT55L8u4MBZrM37zXoUxsgxs/7sow7YSygd9CIwfWTVO8RVu7AYXhhCiTuFEf+APKgx6Jk4SuQbYR0vYKmQ== dependencies: - lodash "^4.11.1" + lodash "^4.17.5" gzip-size@^5.0.0: version "5.0.0" @@ -4181,11 +4218,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^3.3.7: - version "3.3.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b" - integrity sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg== - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -4567,6 +4599,11 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + is-resolvable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" @@ -5134,7 +5171,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= -lodash@^4.11.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0: +lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -5293,6 +5330,20 @@ merge-source-map@^1.1.0: dependencies: source-map "^0.6.1" +mermaid@^8.0.0-rc.8: + version "8.0.0-rc.8" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72" + integrity sha512-GbF9jHWfqE7YGx9vQySmBxy2Ahlclxmpk4tJ9ntNyafENl96s96ggUK/NQS5ydYoFab6MavTm4YMTIPKqWVvPQ== + dependencies: + d3 "^4.13.0" + dagre-d3-renderer "^0.5.8" + dagre-layout "^0.8.8" + graphlibrary "^2.2.0" + he "^1.1.1" + lodash "^4.17.5" + moment "^2.21.0" + scope-css "^1.0.5" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -5451,11 +5502,16 @@ mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: dependencies: minimist "0.0.8" -moment@2.x, moment@^2.18.1: +moment@2.x: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" integrity sha512-Rf6jiHPEfxp9+dlzxPTmRHbvoFXsh2L/U8hOupUMpnuecHQmI6cF6lUbJl3QqKPko1u6ujO+FxtcajLVfLpAtA== +moment@^2.21.0: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= + monaco-editor-webpack-plugin@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.5.4.tgz#6781a130e3e1379bb8f4cd190132f4af6dcd2c16" @@ -6893,6 +6949,15 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" +scope-css@^1.0.5: + version "1.2.1" + resolved "https://registry.yarnpkg.com/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e" + integrity sha512-UjLRmyEYaDNiOS673xlVkZFlVCtckJR/dKgr434VMm7Lb+AOOqXKdAcY7PpGlJYErjXXJzKN7HWo4uRPiZZG0Q== + dependencies: + escaper "^2.5.3" + slugify "^1.3.1" + strip-css-comments "^3.0.0" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -7066,6 +7131,11 @@ slice-ansi@1.0.0: dependencies: is-fullwidth-code-point "^2.0.0" +slugify@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2" + integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw== + smooshpack@^0.0.48: version "0.0.48" resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.48.tgz#6fbeaaf59226a1fe500f56aa17185eed377d2823" @@ -7439,6 +7509,13 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-css-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-css-comments/-/strip-css-comments-3.0.0.tgz#7a5625eff8a2b226cf8947a11254da96e13dae89" + integrity sha1-elYl7/iisibPiUehElTaluE9rok= + dependencies: + is-regexp "^1.0.0" + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" |