diff options
720 files changed, 6473 insertions, 3947 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 34c10b3b77f..f271ab4c4c8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,7 @@ before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml - bundle --version - - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) $FLAGS' + - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) --clean $FLAGS' - retry gem install knapsack - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql' @@ -206,10 +206,9 @@ rake ee_compat_check: - /^[\d-]+-stable(-ee)?$/ allow_failure: yes cache: - key: "ruby233-ee_compat_check_repo" + key: "ee_compat_check_repo" paths: - - ee_compat_check/repo/ - - vendor/ruby + - ee_compat_check/ee-repo/ artifacts: name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}" when: on_failure @@ -281,6 +280,7 @@ rake karma: BABEL_ENV: "coverage" script: - bundle exec rake karma + coverage: '/^Statements *: (\d+\.\d+%)/' artifacts: name: coverage-javascript expire_in: 31d @@ -312,7 +312,7 @@ bundler:audit: - master@gitlab/gitlabhq - master@gitlab/gitlab-ee script: - - "bundle exec bundle-audit check --update" + - "bundle exec bundle-audit check --update --ignore CVE-2016-4658" migration paths: stage: test @@ -333,6 +333,7 @@ migration paths: - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - bundle exec rake db:drop db:create db:schema:load db:seed_fu - git checkout $CI_COMMIT_SHA + - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - source scripts/prepare_build.sh - bundle exec rake db:migrate @@ -345,6 +346,7 @@ coverage: USE_BUNDLE_INSTALL: "true" script: - bundle exec scripts/merge-simplecov + coverage: '/LOC \((\d+\.\d+%)\) covered.$/' artifacts: name: coverage expire_in: 31d diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e094bdfc6..4291eca8dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,294 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 9.0.0 (2017-03-22) + +- Fix inconsistent naming for services that delete things. !5803 (dixpac) +- UI: Allow a project variable to be set to an empty value. !6044 (Lukáš Nový) +- Align task list checkboxes. !6487 (Jared Deckard <jared.deckard@gmail.com>) +- SanitizationFilter allows html5 details and summary tags. !6568 +- on branch deletion show loading icon and disabled the button. !6761 (wendy0402) +- Use an entity for RepoBranch commits and enhance RepoCommit. !7138 (Ben Boeckel) +- Deleting a user doesn't delete issues they've created/are assigned to. !7393 +- Fix position of counter in milestone panels. !7842 (Andrew Smith (EspadaV8)) +- Added a feature to create a 'directly addressed' Todo when mentioned in the beginning of a line. !7926 (Ershad Kunnakkadan) +- Implement OpenID Connect identity provider. !8018 (Markus Koller) +- Show directory hierarchy when listing wiki pages. !8133 (Alex Braha Stoll) +- Migrate SlackService and MattermostService from build_events to pipeline_events, and migrate BuildsEmailService to PipelinesEmailService. Update Hipchat to use pipeline events rather than build events. !8196 +- Execute web hooks for WikiPage delete operation. !8198 +- Added external environment link to web terminal view. !8303 +- Responsive title in diffs inline, side by side, with and without sidebar. !8475 +- Bypass email domain validation when a user is created by an admin. !8575 (Reza Mohammadi @remohammadi) +- API: Paginate all endpoints that return an array. !8606 (Robert Schilling) +- pass in current_user in MergeRequest and MergeRequestsHelper. !8624 (Dongqing Hu) +- Add user & build links in Slack Notifications. !8641 (Poornima M) +- Todo done clicking is kind of unusable. !8691 (Jacopo Beschi @jacopo-beschi) +- Filter todos by manual add. !8691 (Jacopo Beschi @jacopo-beschi) +- Add runner version to /admin/runners view. !8733 (Jonathon Reinhart) +- API: remove `public` param for projects. !8736 +- Allow creating nested groups via UI. !8786 +- API: Add environment stop action. !8808 +- Add discussion events to contributions calendar. !8821 +- Unify issues search behavior by always filtering when ALL labels matches. !8849 +- V3 deprecated templates endpoints removal. !8853 +- Expose pipelines as PipelineBasic `api/v3/projects/:id/pipelines`. !8875 +- Alphabetically sort tags on runner list. !8922 (blackst0ne) +- Added documentation for permalinks to most recent build artifacts. !8934 (Christian Godenschwager) +- Standardize branch name params as branch on V4 API. !8936 +- Move /projects/fork/:id to /projects/:id/fork. !8940 +- Fix small height of activity header page. !8952 (Pavel Sorokin) +- Optionally make users created via the API set their password. !8957 (Joost Rijneveld) +- GitHub Importer - Find users based on GitHub email address. !8958 +- API: Consolidate /projects endpoint. !8962 +- Add filtered search visual tokens. !8969 +- Store group and project full name and full path in routes table. !8979 +- Add internal API to notify Gitaly of post receive. !8983 +- Remove inactive default email services. !8987 +- Option to prevent signing in from multiple ips. !8998 +- Download snippets with LF line-endings by default. !8999 +- Fixes dropdown width in admin project page. !9002 +- fixes issue number alignment problem in MR and issue list. !9020 +- Fix CI/CD pipeline retry and take stages order into account. !9021 +- Make stuck builds detection more performant. !9025 +- Filter by projects in the end of search. !9030 +- Add nested groups to the API. !9034 +- Use ETag to improve performance of issue notes polling. !9036 +- Add the oauth2_generic OmniAuth strategy. !9048 (Joe Marty) +- Brand header logo for pipeline emails. !9049 (Alexis Reigel) +- replace npm with yarn and add yarn.lock. !9055 +- Fix displaying error messages for create label dropdown. !9058 (Tom Koole) +- Set dropdown height fixed to 250px and make it scrollable. !9063 +- Update API docs for new namespace format. !9073 (Markus Koller) +- Replace static fixture for behaviors/quick_submit_spec.js. !9086 (winniehell) +- Use iids as filter parameter. !9096 +- Manage user personal access tokens through api and add impersonation tokens. !9099 (Simon Vocella) +- Added the ability to copy a branch name to the clipboard. !9103 (Glenn Sayers) +- Rename Files::DeleteService to Files::DestroyService. !9110 (dixpac) +- Fixes FE Doc broken link. !9120 +- Add git version to gitlab:env:info. !9128 (Semyon Pupkov) +- Replace static fixture for new_branch_spec.js. !9131 (winniehell) +- Reintroduce coverage report for JavaScript. !9133 (winniehell) +- Fix MR widget jump. !9146 +- Avoid calling Build#trace_with_state for performance. !9149 (Takuya Noguchi) +- fix background color for labels mention in todo. !9155 (mhasbini) +- Replace static fixture for behaviors/requires_input_spec.js. !9162 (winniehell) +- Added AsciiDoc Snippet to CI/CD Badges. !9164 (Jan Christophersen) +- Make Karma output look nicer for CI. !9165 (winniehell) +- show 99+ for large count in todos notification bell. !9171 (mhasbini) +- Replace static fixture for header_spec.js. !9174 (winniehell) +- Replace static fixture for project_title_spec.js. !9175 (winniehell) +- Fixes markdown in activity-feed is gray. !9179 +- Show notifications settings dropdown even if repository feature is disabled. !9180 +- Fixes job dropdown action throws error in js console. !9182 +- Set maximum width for mini pipeline graph text so it is not truncated to early. !9188 +- Added 'Most Recent Activity' header to the User Profile page. !9189 (Jan Christophersen) +- Show Issues mentioned / being closed from a Merge Requests title below the 'Accept Merge Request' button. !9194 (Jan Christophersen) +- Stop linking to deleted Branches in Activity tabs. !9203 (Jan Christophersen) +- Make it possible to pass coverage value to commit status API. !9214 (wendy0402) +- Add admin setting for default artifacts expiration. !9219 +- add :iids param to IssuableFinder (resolve technical dept). !9222 (mhasbini) +- Add Links to Branches in Calendar Activity. !9224 (Jan Christophersen) +- Fix pipeline retry and cancel buttons on pipeline details page. !9225 +- Remove es6 file extension from JavaScript files. !9241 (winniehell) +- Add Runner's registration/deletion v4 API. !9246 +- Add merge request count to each issue on issues list. !9252 (blackst0ne) +- Fix error in MR widget after /merge slash command. !9259 +- Clean-up Project navigation order. !9272 +- Add Runner's jobs v4 API. !9273 +- Add pipeline trigger API with user permissions. !9277 +- Enhanced filter issues layout for better mobile experiance. !9280 (Pratik Borsadiya) +- Move babel config for instanbul to karma config. !9286 (winniehell) +- Document U2F limitations with multiple URLs. !9300 +- Wrap long Project and Group titles. !9301 +- Clean-up Groups navigation order. !9309 +- Truncate long Todo titles for non-mobile screens. !9311 +- add rake tasks to handle yarn dependencies and update documentation. !9316 +- API: - Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. !9325 (Robert Schilling) +- API: Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar`. !9328 (Robert Schilling) +- API: Use `visibility` as string parameter everywhere. !9337 +- Add the Username to the HTTP(S) clone URL of a Repository. !9347 (Jan Christophersen) +- Add spec for todo with target_type Commit. !9351 (George Andrinopoulos) +- API: Remove `DELETE projects/:id/deploy_keys/:key_id/disable`. !9365 (Robert Schilling) +- Fixes includes line number during unfold copy n paste in parallel diff view. !9365 +- API: Use POST to (un)block a user. !9371 (Robert Schilling) +- Remove markup that was showing in tooltip for renamed files. !9374 +- Drop unused ci_projects table and some unused project_id columns, then rename gl_project_id to project_id. Stop exporting job trace when exporting projects. !9378 (David Wagner) +- Adds remote logout functionality to the Authentiq OAuth provider. !9381 (Alexandros Keramidas) +- Introduce /award slash command; Allow posting of just an emoji in comment. !9382 (mhasbini) +- API: Remove deprecated fields Notes#upvotes and Notes#downvotes. !9384 (Robert Schilling) +- Redo internals of Incoming Mail Support. !9385 +- update Vue to v2.1.10. !9386 +- Add button to create issue for failing build. !9391 (Alex Sanford) +- test compiling production assets and generate webpack bundle report in CI. !9396 +- API: Return 204 for all delete endpoints. !9397 (Robert Schilling) +- Add KUBE_CA_PEM_FILE, deprecate KUBE_CA_PEM. !9398 +- API: Use POST requests to mark todos as done. !9410 (Robert Schilling) +- API project create: Make name or path required. !9416 +- Add housekeeping endpoint for Projects API. !9421 +- Fixes delimiter removes when todo marked as done. !9435 +- Document when current coverage configuration option was introduced. !9443 +- Uploaded files which content can change now require revalidation on each page load. !9453 +- Only add a newline in the Markdown Editor if the current line is not empty. !9455 (Jan Christophersen) +- Rename builds to job for the v4 API. !9463 +- API: Remove /groups/owned endpoint. !9505 (Robert Schilling) +- API: Return 400 for all validation erros in the mebers API. !9523 (Robert Schilling) +- Fixes large file name tooltip cutoff in diff header. !9529 +- Keep consistent in handling indexOf results. !9531 (Takuya Noguchi) +- Make documentation of list repository tree API call more detailed. !9532 (Marius Kleiner) +- Fix Sort dropdown reflow issue. !9533 (Jarkko Tuunanen) +- Improve grammar in GitLab flow documentation. !9552 (infogrind) +- Change default project view for user from readme to files view. !9584 +- Make it possible to configure blocking manual actions. !9585 +- Show public RSS feeds to anonymous users. !9596 (Michael Kozono) +- Update storage settings to allow extra values per repository storage. !9597 +- Enable filtering milestones by search criteria in the API. !9606 +- Ensure archive download is only one directory deep. !9616 +- Fix updaing commit status when using optional attributes. !9618 +- Add filter and sorting to dashboard groups page. !9619 +- Remove deprecated build status badge and related services. !9620 +- Remove the newrelic gem. !9622 (Robert Schilling) +- Rename table ci_commits to ci_pipelines. !9638 +- Remove various unused CI tables and columns. !9639 +- Use webpack CommonsChunkPlugin to place common javascript libraries in their own bundles. !9647 +- CORS: Whitelist pagination headers. !9651 (Robert Schilling) +- Remove "subscribed" field from API responses returning list of issues or merge requests. !9661 +- Highlight line number if specified on diff pages when page loads. !9664 +- Set default cache key to "default" for jobs. !9666 +- Set max height to screen height for Zen mode. !9667 +- GET 'projects/:id/repository/commits' endpoint improvements. !9679 (George Andrinopoulos, Jordan Ryan Reuter) +- Restore keyboard shortcuts for "Activity" and "Charts". !9680 +- Added commit array to Syshook json. !9685 (Gabriele Pongelli) +- Document ability to list issues with no labels using API. !9697 (Vignesh Ravichandran) +- Fix typo in Gitlab config file. !9702 (medied) +- Fix json response in branches controller. !9710 (George Andrinopoulos) +- Refactor dropdown_assignee_spec. !9711 (George Andrinopoulos) +- Delete artifacts for pages unless expiry date is specified. !9716 +- Use gitlab-workhorse 1.4.0. !9724 +- Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint. !9727 +- Restrict nested group names to prevent ambiguous routes. !9738 +- Rename job environment variables to new terminology. !9756 +- Deprecate usage of `types` configuration entry to describe CI/CD stages. !9766 +- Moved project settings from the gear drop-down menu to a tab. !9786 +- Fix "passed with warnings" stage status on MySQL installations. !9802 +- Fix for creating a project through API when import_url is nil. !9841 +- Use GitLab Pages v0.4.0. !9896 +- Reserve few project and nested group paths that have wildcard routes associated. !9898 +- Speed up project dashboard by caching pipeline status and eager loading routes. !9903 +- Fixes n+1 query for tags and branches index page. !9905 +- Hide ancestor groups in the share group dropdown list. !9965 +- Allow creating merge request even if target branch is not specified in query params. !9968 +- Removed d3 from the main application.js bundle. !10062 +- Return 404 in project issues API endpoint when project cannot be found. !10093 +- Fix positioning of `Scroll to top` button. +- Add limit to the number of events showed in cycle analytics. +- Only run timeago loops after rendering timeago components. +- Increase right side of file header to button stays on same line. +- Centers loading icon vertically and horizontally in pipelines table in commit view. +- Fix issues mentioned but not closed for external issue trackers. +- fix milestone does not automatically assign when create issue from milestone. +- Re-add Assign to me link to Merge Request and Issues. +- Format timeago date to short format. +- Fix errors in slash commands matcher, add simple test coverage. (YarNayar) +- Make Git history follow renames again by performing the --skip in Ruby. +- Added option to update to owner for group members. +- Pick up option from GDK to disable webpack dev server livereload. +- Introduce Pipeline Triggers that are user-aware. +- Fixed loading spinner position on issue template toggle. +- Removed duplicate "Visibility Level" label on New Project page. (Robert Marcano) +- Fix 'New Tag' layout on Tags page. (Robert Marcano) +- Update API endpoints for raw files. +- Fix issuable stale object error handler for js when updating tasklists. +- Gather issuable metadata to avoid n+1 queries on index view. +- Remove JIRA closed status icon. +- Fix z index issues with sidebar. +- Fixed long file names overflowing under action buttons. +- Only show public emails in atom feeds. +- Add Mock CI service/integration for development. +- Move tag services to Tags namespace. (dixpac) +- Set Auto-Submitted header to mails. (Semyon Pupkov) +- Improved diff comment button UX. +- Adds API endpoint to fetch all merge request for a single milestone. (Joren De Groof) +- Only create unmergeable todos once when MR fails to merge. +- Only yield valid references in ReferenceFilter.references_in. +- Add member: Always return 409 when a member exists. +- Remove plus icon from MR button on compare view. +- Re-add the New Project button in nav bar. +- Default to subtle MR mege button until CI status is available. +- Rename priority sorting option to label priority. +- Added headers to protected branch access dropdowns. +- Hide issue info when project issues are disabled. (George Andrinopoulos) +- removed unused parameter 'status_only: true'. +- Left align logo. +- Replaced jQuery UI datepicker. +- Removed jQuery UI highlight & autocomplete. +- Replaced jQuery UI sortable. +- Remove readme-only project view preference. +- Remove tooltips from label subscription buttons. +- Rename retry failed button on pipeline page to just retry. +- Align bulk update issues button to the right. +- Remove remnants of git annex support. +- Dispatch needed JS when creating a new MR in diff view. +- Change project count limit from 10 to 100000. +- Remove repeated routes.path check for postgresql database. (mhasbini) +- Fixed RSS button alignment on activity pages. +- Seed abuse reports for development. +- Bump Hashie to 3.5.5 and omniauth to 1.4.2 to eliminate warning noise. +- Add user deletion permission check in `Users::DestroyService`. +- Fix snippets search result spacing. +- Sort builds in stage dropdown. +- SSH key field updates title after pasting key. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. +- Remove fixed positioning from top nav. +- Deduplicate markdown task lists. +- update issue count when closing/reopening an issue. +- Update code editor (ACE) to 1.2.6, to fix input problems with compose key. +- Improves a11y in sidebar by adding aria-hidden attributes in i tags and by fixing two broken aria-hidden attributes. +- Use redis channel to post notifications. +- Removed top border from user contribution calendar. +- Added user callouts to the projects dashboard and user profile. +- Removes label when moving issue to another list that it is currently in. +- Return 202 with JSON body on async removals on V4 API. +- Add filtered search to MR page. +- Add frequently used emojis back to awards menu. +- don't animate logo when downloading files. +- Stop setting Strict-Transport-Securty header from within the app. +- Use "branch_name" instead "branch" on V3 branch creation API. +- Fix archive prefix bug for refs containing dots. +- ensure MR widget dropdown is same color as button. +- Adds Pending and Finished tabs to pipelines page. +- Decrease tanuki logo size. +- Add all available statuses to scope filter for project builds endpoint. (George Andrinopoulos) +- Add filter param for project membership for current_user in API v4. +- Remove help link from right dropdown. +- Fix jobs table header height. +- Combined deploy keys, push rules, protect branches and mirror repository settings options into a single one called Repository. +- Add storage class configuration option for Amazon S3 remote backups. (Jon Keys) +- Specify in the documentation that only projects owners can transfer projects. +- Use native unicode emojis. +- Clear ActiveRecord connections before starting Sidekiq. +- Update account view to display new username. +- Narrow environment payload by using basic project details resource. +- Creating a new branch from an issue will automatically initialize a repository if one doesn't already exist. +- Dashboard project search keeps selected sort & filters. +- Visually show expanded diff lines cant have comments. +- Use full group name in GFM group reference title. +- Make a default namespace of Kubernetes service to contain project ID. +- Present GitLab version for each V3 to V4 API change on v3_to_v4.md. +- Add badges to global dropdown. +- Changed coverage reg expression placeholder text to be more like a placeholder. +- Show members of parent groups on project members page. +- Fix grammer issue in admin/runners. +- Allow slashes in slash command arguments. +- Adds paginationd and folders view to environments table. +- hide loading spinners for server-rendered sidebar fields. +- Change development tanuki favicon colors to match logo color order. +- API issues - support filtering by iids. + +## 8.17.4 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.17.3 (2017-03-07) - Fix the redirect to custom home page URL. !9518 @@ -210,6 +498,11 @@ entry. - Remove deprecated GitlabCiService. - Requeue pending deletion projects. +## 8.16.8 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.16.7 (2017-02-27) - No changes. @@ -411,6 +704,11 @@ entry. - Add margin to markdown math blocks. - Add hover state to MR comment reply button. +## 8.15.8 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.15.7 (2017-02-15) - No changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a285e8ab74f..275c0cd1777 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,7 +82,7 @@ If a contributor is no longer actively working on a submitted merge request we can decide that the merge request will be finished by one of our [Merge request coaches][team] or close the merge request. We make this decision based on how important the change is for our product vision. If a Merge request -coach is going to finish the merge request we assign the +coach is going to finish the merge request we assign the ~"coach will finish" label. ## Helping others @@ -479,8 +479,7 @@ merge request: 1. [Rails](https://github.com/bbatsov/rails-style-guide) 1. [Newlines styleguide][newlines-styleguide] 1. [Testing](doc/development/testing.md) -1. [JavaScript (ES6)](https://github.com/airbnb/javascript) -1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) +1. [JavaScript styleguide][js-styleguide] 1. [SCSS styleguide][scss-styleguide] 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security @@ -549,7 +548,8 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming [changelog]: doc/development/changelog.md "Generate a changelog entry" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" -[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" +[js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide" +[scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" [UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/ [license-finder-doc]: doc/development/licensing.md diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 0d91a54c7d4..1d0ba9ea182 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 347f5833ee6..9df886c42a1 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -1.4.1 +1.4.2 @@ -327,6 +327,7 @@ group :test do gem 'test_after_commit', '~> 1.1' gem 'sham_rack', '~> 1.3.6' gem 'timecop', '~> 0.8.0' + gem 'concurrent-ruby', '~> 1.0.5' end gem 'octokit', '~> 4.6.2' diff --git a/Gemfile.lock b/Gemfile.lock index 043ca4f8800..07be5d7aded 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -128,7 +128,7 @@ GEM execjs coffee-script-source (1.10.0) colorize (0.7.7) - concurrent-ruby (1.0.4) + concurrent-ruby (1.0.5) connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -868,6 +868,7 @@ DEPENDENCIES chronic (~> 0.10.2) chronic_duration (~> 0.10.6) coffee-rails (~> 4.1.0) + concurrent-ruby (~> 1.0.5) connection_pool (~> 2.0) creole (~> 0.5.0) d3_rails (~> 3.5.0) diff --git a/README.md b/README.md index 09e08adbb73..f0e3b52ef6f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # GitLab [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) +[![Overall test coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg)](https://gitlab.com/gitlab-org/gitlab-ce/pipelines) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) +## Test coverage + +- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby +- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript + ## Canonical source The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). diff --git a/app/assets/images/icon-merge-request-unmerged.svg b/app/assets/images/icon-merge-request-unmerged.svg index c4d8e65122d..d53a7470243 100644 --- a/app/assets/images/icon-merge-request-unmerged.svg +++ b/app/assets/images/icon-merge-request-unmerged.svg @@ -1 +1 @@ -<svg width="12" height="15" viewBox="0 0 12 15" xmlns="http://www.w3.org/2000/svg"><path d="M10.267 11.028V5.167c-.028-.728-.318-1.372-.878-1.923-.56-.55-1.194-.85-1.922-.877h-.934V.5l-2.8 2.8 2.8 2.8V4.233h.934a.976.976 0 0 1 .644.29.88.88 0 0 1 .289.644v5.861a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472zM3.733 3.3a1.86 1.86 0 0 0-1.866-1.867 1.86 1.86 0 0 0-.934 3.472v6.123a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472V4.905c.55-.317.933-.914.933-1.605z" fill-rule="nonzero"/></svg> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m5 5.563v4.875c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-4.875c-1.024-.4-1.75-1.397-1.75-2.563 0-1.519 1.231-2.75 2.75-2.75 1.519 0 2.75 1.231 2.75 2.75 0 1.166-.726 2.162-1.75 2.563m-1 8.687c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25m0-10c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/><path d="m10.501 2c1.381.001 2.499 1.125 2.499 2.506v5.931c1.024.4 1.75 1.397 1.75 2.563 0 1.519-1.231 2.75-2.75 2.75-1.519 0-2.75-1.231-2.75-2.75 0-1.166.726-2.162 1.75-2.563v-5.931c0-.279-.225-.506-.499-.506v.926c0 .346-.244.474-.569.271l-2.952-1.844c-.314-.196-.325-.507 0-.71l2.952-1.844c.314-.196.569-.081.569.271v.93m1.499 12.25c.69 0 1.25-.56 1.25-1.25 0-.69-.56-1.25-1.25-1.25-.69 0-1.25.56-1.25 1.25 0 .69.56 1.25 1.25 1.25"/></svg>
\ No newline at end of file diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index aebda7780e1..d816df831eb 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -1,6 +1,7 @@ /* eslint-disable no-param-reassign, class-methods-use-this */ /* global Pager */ -/* global Cookies */ + +import Cookies from 'js-cookie'; class Activities { constructor() { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 9349918f7a0..c743dd551d7 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,4 +1,4 @@ -/* global Cookies */ +import Cookies from 'js-cookie'; import emojiMap from 'emojis/digests.json'; import emojiAliases from 'emojis/aliases.json'; diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 92f3bb3ff52..86927314dd4 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -24,7 +24,7 @@ $('body').on('click', '.js-toggle-button', function(e) { toggleContainer($(this).closest('.js-toggle-container')); - const targetTag = e.target.tagName.toLowerCase(); + const targetTag = e.currentTarget.tagName.toLowerCase(); if (targetTag === 'a' || targetTag === 'button') { e.preventDefault(); } diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js b/app/assets/javascripts/blob/blob_ci_yaml.js deleted file mode 100644 index ec1c018424d..00000000000 --- a/app/assets/javascripts/blob/blob_ci_yaml.js +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable no-param-reassign, comma-dangle */ -/* global Api */ - -require('./template_selector'); - -((global) => { - class BlobCiYamlSelector extends gl.TemplateSelector { - requestFile(query) { - return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); - } - - requestFileSuccess(file) { - return super.requestFileSuccess(file); - } - } - - global.BlobCiYamlSelector = BlobCiYamlSelector; - - class BlobCiYamlSelectors { - constructor({ editor, $dropdowns } = {}) { - this.editor = editor; - this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector'); - this.initSelectors(); - } - - initSelectors() { - const editor = this.editor; - this.$dropdowns.each((i, dropdown) => { - const $dropdown = $(dropdown); - return new BlobCiYamlSelector({ - editor, - pattern: /(.gitlab-ci.yml)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown - }); - }); - } - } - - global.BlobCiYamlSelectors = BlobCiYamlSelectors; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob/blob_dockerfile_selector.js b/app/assets/javascripts/blob/blob_dockerfile_selector.js deleted file mode 100644 index d4f60cc6ecd..00000000000 --- a/app/assets/javascripts/blob/blob_dockerfile_selector.js +++ /dev/null @@ -1,19 +0,0 @@ -/* global Api */ - -require('./template_selector'); - -(() => { - const global = window.gl || (window.gl = {}); - - class BlobDockerfileSelector extends gl.TemplateSelector { - requestFile(query) { - return Api.dockerfileYml(query.name, this.requestFileSuccess.bind(this)); - } - - requestFileSuccess(file) { - return super.requestFileSuccess(file); - } - } - - global.BlobDockerfileSelector = BlobDockerfileSelector; -})(); diff --git a/app/assets/javascripts/blob/blob_dockerfile_selectors.js b/app/assets/javascripts/blob/blob_dockerfile_selectors.js deleted file mode 100644 index 9cee79fa5d5..00000000000 --- a/app/assets/javascripts/blob/blob_dockerfile_selectors.js +++ /dev/null @@ -1,27 +0,0 @@ -(() => { - const global = window.gl || (window.gl = {}); - - class BlobDockerfileSelectors { - constructor({ editor, $dropdowns } = {}) { - this.editor = editor; - this.$dropdowns = $dropdowns || $('.js-dockerfile-selector'); - this.initSelectors(); - } - - initSelectors() { - const editor = this.editor; - this.$dropdowns.each((i, dropdown) => { - const $dropdown = $(dropdown); - return new gl.BlobDockerfileSelector({ - editor, - pattern: /(Dockerfile)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'), - dropdown: $dropdown, - }); - }); - } - } - - global.BlobDockerfileSelectors = BlobDockerfileSelectors; -})(); diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 8f6bf162d6e..c9fe23aec75 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,66 +1,63 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, max-len */ +/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ /* global Dropzone */ -(function() { - this.BlobFileDropzone = (function() { - function BlobFileDropzone(form, method) { - var dropzone, form_dropzone, submitButton; - form_dropzone = form.find('.dropzone'); - Dropzone.autoDiscover = false; - dropzone = form_dropzone.dropzone({ - autoDiscover: false, - autoProcessQueue: false, - url: form.attr('action'), - // Rails uses a hidden input field for PUT - // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails - method: method, - clickable: true, - uploadMultiple: false, - paramName: "file", - maxFilesize: gon.max_file_size || 10, - parallelUploads: 1, - maxFiles: 1, - addRemoveLinks: true, - previewsContainer: '.dropzone-previews', - headers: { - "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") - }, - init: function() { - this.on('addedfile', function(file) { - $('.dropzone-alerts').html('').hide(); - }); - this.on('success', function(header, response) { - window.location.href = response.filePath; - }); - this.on('maxfilesexceeded', function(file) { - this.removeFile(file); - }); - return this.on('sending', function(file, xhr, formData) { - formData.append('target_branch', form.find('input[name="target_branch"]').val()); - formData.append('create_merge_request', form.find('.js-create-merge-request').val()); - formData.append('commit_message', form.find('.js-commit-message').val()); - }); - }, - // Override behavior of adding error underneath preview - error: function(file, errorMessage) { - var stripped; - stripped = $("<div/>").html(errorMessage).text(); - $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show(); +export default class BlobFileDropzone { + constructor(form, method) { + const formDropzone = form.find('.dropzone'); + Dropzone.autoDiscover = false; + + const dropzone = formDropzone.dropzone({ + autoDiscover: false, + autoProcessQueue: false, + url: form.attr('action'), + // Rails uses a hidden input field for PUT + // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails + method: method, + clickable: true, + uploadMultiple: false, + paramName: 'file', + maxFilesize: gon.max_file_size || 10, + parallelUploads: 1, + maxFiles: 1, + addRemoveLinks: true, + previewsContainer: '.dropzone-previews', + headers: { + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), + }, + init: function () { + this.on('addedfile', function () { + $('.dropzone-alerts').html('').hide(); + }); + this.on('success', function (header, response) { + window.location.href = response.filePath; + }); + this.on('maxfilesexceeded', function (file) { this.removeFile(file); - } - }); - submitButton = form.find('#submit-all')[0]; - submitButton.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation(); - if (dropzone[0].dropzone.getQueuedFiles().length === 0) { - alert("Please select a file"); - } - dropzone[0].dropzone.processQueue(); - return false; - }); - } + }); + this.on('sending', function (file, xhr, formData) { + formData.append('target_branch', form.find('input[name="target_branch"]').val()); + formData.append('create_merge_request', form.find('.js-create-merge-request').val()); + formData.append('commit_message', form.find('.js-commit-message').val()); + }); + }, + // Override behavior of adding error underneath preview + error: function (file, errorMessage) { + const stripped = $('<div/>').html(errorMessage).text(); + $('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show(); + this.removeFile(file); + }, + }); - return BlobFileDropzone; - })(); -}).call(window); + const submitButton = form.find('#submit-all')[0]; + submitButton.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + if (dropzone[0].dropzone.getQueuedFiles().length === 0) { + // eslint-disable-next-line no-alert + alert('Please select a file'); + } + dropzone[0].dropzone.processQueue(); + return false; + }); + } +} diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js deleted file mode 100644 index de20eab9cd1..00000000000 --- a/app/assets/javascripts/blob/blob_gitignore_selector.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */ -/* global Api */ - -require('./template_selector'); - -(function() { - var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - this.BlobGitignoreSelector = (function(superClass) { - extend(BlobGitignoreSelector, superClass); - - function BlobGitignoreSelector() { - return BlobGitignoreSelector.__super__.constructor.apply(this, arguments); - } - - BlobGitignoreSelector.prototype.requestFile = function(query) { - return Api.gitignoreText(query.name, this.requestFileSuccess.bind(this)); - }; - - return BlobGitignoreSelector; - })(gl.TemplateSelector); -}).call(window); diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js deleted file mode 100644 index 43e5c0a5641..00000000000 --- a/app/assets/javascripts/blob/blob_gitignore_selectors.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, max-len */ -/* global BlobGitignoreSelector */ - -(function() { - this.BlobGitignoreSelectors = (function() { - function BlobGitignoreSelectors(opts) { - var ref; - this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitignore-selector'), this.editor = opts.editor; - this.$dropdowns.each((function(_this) { - return function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return new BlobGitignoreSelector({ - pattern: /(.gitignore)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-gitignore-selector-wrap'), - dropdown: $dropdown, - editor: _this.editor - }); - }; - })(this)); - } - - return BlobGitignoreSelectors; - })(); -}).call(window); diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js deleted file mode 100644 index b582052a76e..00000000000 --- a/app/assets/javascripts/blob/blob_license_selector.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */ -/* global Api */ - -require('./template_selector'); - -(function() { - var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - this.BlobLicenseSelector = (function(superClass) { - extend(BlobLicenseSelector, superClass); - - function BlobLicenseSelector() { - return BlobLicenseSelector.__super__.constructor.apply(this, arguments); - } - - BlobLicenseSelector.prototype.requestFile = function(query) { - var data; - data = { - project: this.dropdown.data('project'), - fullname: this.dropdown.data('fullname') - }; - return Api.licenseText(query.id, data, this.requestFileSuccess.bind(this)); - }; - - return BlobLicenseSelector; - })(gl.TemplateSelector); -}).call(window); diff --git a/app/assets/javascripts/blob/blob_license_selectors.js b/app/assets/javascripts/blob/blob_license_selectors.js deleted file mode 100644 index c5067b0feae..00000000000 --- a/app/assets/javascripts/blob/blob_license_selectors.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable no-unused-vars, no-param-reassign */ -/* global BlobLicenseSelector */ - -((global) => { - class BlobLicenseSelectors { - constructor({ $dropdowns, editor }) { - this.$dropdowns = $('.js-license-selector'); - this.editor = editor; - this.$dropdowns.each((i, dropdown) => { - const $dropdown = $(dropdown); - return new BlobLicenseSelector({ - editor, - pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-license-selector-wrap'), - dropdown: $dropdown, - }); - }); - } - } - - global.BlobLicenseSelectors = BlobLicenseSelectors; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js deleted file mode 100644 index 7e03ec3b391..00000000000 --- a/app/assets/javascripts/blob/template_selector.js +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, no-param-reassign, max-len */ - -((global) => { - class TemplateSelector { - constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) { - this.onClick = this.onClick.bind(this); - this.dropdown = dropdown; - this.data = data; - this.pattern = pattern; - this.wrapper = wrapper; - this.editor = editor; - this.fileEndpoint = fileEndpoint; - this.$input = $input || $('#file_name'); - this.dropdownIcon = $('.fa-chevron-down', this.dropdown); - this.buildDropdown(); - this.bindEvents(); - this.onFilenameUpdate(); - - this.autosizeUpdateEvent = document.createEvent('Event'); - this.autosizeUpdateEvent.initEvent('autosize:update', true, false); - } - - buildDropdown() { - return this.dropdown.glDropdown({ - data: this.data, - filterable: true, - selectable: true, - toggleLabel: this.toggleLabel, - search: { - fields: ['name'] - }, - clicked: this.onClick, - text: function(item) { - return item.name; - } - }); - } - - bindEvents() { - return this.$input.on('keyup blur', (e) => this.onFilenameUpdate()); - } - - toggleLabel(item) { - return item.name; - } - - onFilenameUpdate() { - var filenameMatches; - if (!this.$input.length) { - return; - } - filenameMatches = this.pattern.test(this.$input.val().trim()); - if (!filenameMatches) { - this.wrapper.addClass('hidden'); - return; - } - return this.wrapper.removeClass('hidden'); - } - - onClick(item, el, e) { - e.preventDefault(); - return this.requestFile(item); - } - - requestFile(item) { - // This `requestFile` method is an abstract method that should - // be added by all subclasses. - } - - // To be implemented on the extending class - // e.g. - // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - requestFileSuccess(file, { skipFocus } = {}) { - if (!file) return; - - const oldValue = this.editor.getValue(); - const newValue = file.content; - - this.editor.setValue(newValue, 1); - if (!skipFocus) this.editor.focus(); - - if (this.editor instanceof jQuery) { - this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); - } - } - - startLoadingSpinner() { - this.dropdownIcon - .addClass('fa-spinner fa-spin') - .removeClass('fa-chevron-down'); - } - - stopLoadingSpinner() { - this.dropdownIcon - .addClass('fa-chevron-down') - .removeClass('fa-spinner fa-spin'); - } - } - - global.TemplateSelector = TemplateSelector; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js new file mode 100644 index 00000000000..5a5954e7751 --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selector.js @@ -0,0 +1,9 @@ +/* global Api */ + +import TemplateSelector from './template_selector'; + +export default class BlobCiYamlSelector extends TemplateSelector { + requestFile(query) { + return Api.gitlabCiYml(query.name, (file, config) => this.setEditorContent(file, config)); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js new file mode 100644 index 00000000000..7a4d6a42a03 --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_ci_yaml_selectors.js @@ -0,0 +1,23 @@ +/* global Api */ + +import BlobCiYamlSelector from './blob_ci_yaml_selector'; + +export default class BlobCiYamlSelectors { + constructor({ editor, $dropdowns }) { + this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector'); + this.initSelectors(editor); + } + + initSelectors(editor) { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + return new BlobCiYamlSelector({ + editor, + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + }); + }); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js new file mode 100644 index 00000000000..19f8820a0cb --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selector.js @@ -0,0 +1,9 @@ +/* global Api */ + +import TemplateSelector from './template_selector'; + +export default class BlobDockerfileSelector extends TemplateSelector { + requestFile(query) { + return Api.dockerfileYml(query.name, (file, config) => this.setEditorContent(file, config)); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js new file mode 100644 index 00000000000..da067035b43 --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_dockerfile_selectors.js @@ -0,0 +1,23 @@ +import BlobDockerfileSelector from './blob_dockerfile_selector'; + +export default class BlobDockerfileSelectors { + constructor({ editor, $dropdowns }) { + this.editor = editor; + this.$dropdowns = $dropdowns || $('.js-dockerfile-selector'); + this.initSelectors(); + } + + initSelectors() { + const editor = this.editor; + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + return new BlobDockerfileSelector({ + editor, + pattern: /(Dockerfile)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-dockerfile-selector-wrap'), + dropdown: $dropdown, + }); + }); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js new file mode 100644 index 00000000000..0b6b02fc2b3 --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selector.js @@ -0,0 +1,9 @@ +/* global Api */ + +import TemplateSelector from './template_selector'; + +export default class BlobGitignoreSelector extends TemplateSelector { + requestFile(query) { + return Api.gitignoreText(query.name, (file, config) => this.setEditorContent(file, config)); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js new file mode 100644 index 00000000000..dc485d97677 --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_gitignore_selectors.js @@ -0,0 +1,23 @@ +import BlobGitignoreSelector from './blob_gitignore_selector'; + +export default class BlobGitignoreSelectors { + constructor({ editor, $dropdowns }) { + this.$dropdowns = $dropdowns || $('.js-gitignore-selector'); + this.editor = editor; + this.initSelectors(); + } + + initSelectors() { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + + return new BlobGitignoreSelector({ + pattern: /(.gitignore)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitignore-selector-wrap'), + dropdown: $dropdown, + editor: this.editor, + }); + }); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selector.js b/app/assets/javascripts/blob/template_selectors/blob_license_selector.js new file mode 100644 index 00000000000..e9cb31cc2dc --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_license_selector.js @@ -0,0 +1,13 @@ +/* global Api */ + +import TemplateSelector from './template_selector'; + +export default class BlobLicenseSelector extends TemplateSelector { + requestFile(query) { + const data = { + project: this.dropdown.data('project'), + fullname: this.dropdown.data('fullname'), + }; + return Api.licenseText(query.id, data, (file, config) => this.setEditorContent(file, config)); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js b/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js new file mode 100644 index 00000000000..a44f4f78b2d --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/blob_license_selectors.js @@ -0,0 +1,24 @@ +/* eslint-disable no-unused-vars, no-param-reassign */ + +import BlobLicenseSelector from './blob_license_selector'; + +export default class BlobLicenseSelectors { + constructor({ $dropdowns, editor }) { + this.$dropdowns = $dropdowns || $('.js-license-selector'); + this.initSelectors(editor); + } + + initSelectors(editor) { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + + return new BlobLicenseSelector({ + editor, + pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-license-selector-wrap'), + dropdown: $dropdown, + }); + }); + } +} diff --git a/app/assets/javascripts/blob/template_selectors/template_selector.js b/app/assets/javascripts/blob/template_selectors/template_selector.js new file mode 100644 index 00000000000..d7c1c32efbd --- /dev/null +++ b/app/assets/javascripts/blob/template_selectors/template_selector.js @@ -0,0 +1,92 @@ +/* eslint-disable class-methods-use-this, no-unused-vars */ + +export default class TemplateSelector { + constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) { + this.pattern = pattern; + this.editor = editor; + this.dropdown = dropdown; + this.$dropdownContainer = wrapper; + this.$filenameInput = $input || $('#file_name'); + this.$dropdownIcon = $('.fa-chevron-down', dropdown); + + this.initDropdown(dropdown, data); + this.listenForFilenameInput(); + this.renderMatchedDropdown(); + this.initAutosizeUpdateEvent(); + } + + initDropdown(dropdown, data) { + return $(dropdown).glDropdown({ + data, + filterable: true, + selectable: true, + toggleLabel: item => item.name, + search: { + fields: ['name'], + }, + clicked: (item, el, e) => this.fetchFileTemplate(item, el, e), + text: item => item.name, + }); + } + + initAutosizeUpdateEvent() { + this.autosizeUpdateEvent = document.createEvent('Event'); + this.autosizeUpdateEvent.initEvent('autosize:update', true, false); + } + + listenForFilenameInput() { + return this.$filenameInput.on('keyup blur', e => this.renderMatchedDropdown(e)); + } + + renderMatchedDropdown() { + if (!this.$filenameInput.length) { + return null; + } + + const filenameMatches = this.pattern.test(this.$filenameInput.val().trim()); + + if (!filenameMatches) { + return this.$dropdownContainer.addClass('hidden'); + } + return this.$dropdownContainer.removeClass('hidden'); + } + + fetchFileTemplate(item, el, e) { + e.preventDefault(); + return this.requestFile(item); + } + + requestFile(item) { + // This `requestFile` method is an abstract method that should + // be added by all subclasses. + } + + // To be implemented on the extending class + // e.g. Api.gitlabCiYml(query.name, file => this.setEditorContent(file)); + + setEditorContent(file, { skipFocus } = {}) { + if (!file) return; + + const newValue = file.content; + + this.editor.setValue(newValue, 1); + + if (!skipFocus) this.editor.focus(); + + if (this.editor instanceof jQuery) { + this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); + } + } + + startLoadingSpinner() { + this.$dropdownIcon + .addClass('fa-spinner fa-spin') + .removeClass('fa-chevron-down'); + } + + stopLoadingSpinner() { + this.$dropdownIcon + .addClass('fa-chevron-down') + .removeClass('fa-spinner fa-spin'); + } +} diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js new file mode 100644 index 00000000000..c5deccf631e --- /dev/null +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -0,0 +1,32 @@ +/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */ +/* global EditBlob */ +/* global NewCommitForm */ + +import EditBlob from './edit_blob'; +import BlobFileDropzone from '../blob/blob_file_dropzone'; + +$(() => { + const editBlobForm = $('.js-edit-blob-form'); + const uploadBlobForm = $('.js-upload-blob-form'); + + if (editBlobForm.length) { + const urlRoot = editBlobForm.data('relative-url-root'); + const assetsPath = editBlobForm.data('assets-prefix'); + const blobLanguage = editBlobForm.data('blob-language'); + + new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage); + new NewCommitForm(editBlobForm); + } + + if (uploadBlobForm.length) { + const method = uploadBlobForm.data('method'); + + new BlobFileDropzone(uploadBlobForm, method); + new NewCommitForm(uploadBlobForm); + + window.gl.utils.disableButtonIfEmptyField( + uploadBlobForm.find('.js-commit-message'), + '.btn-upload-file', + ); + } +}); diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js deleted file mode 100644 index 0436bbb0eaf..00000000000 --- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */ -/* global EditBlob */ -/* global NewCommitForm */ - -require('./edit_blob'); - -(function() { - $(function() { - var url = $(".js-edit-blob-form").data("relative-url-root"); - url += $(".js-edit-blob-form").data("assets-prefix"); - - var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language')); - new NewCommitForm($('.js-edit-blob-form')); - }); -}).call(window); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index a1127b9e30e..d3560d5df3b 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -1,88 +1,99 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, max-len */ /* global ace */ -/* global BlobGitignoreSelectors */ - -(function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - - this.EditBlob = (function() { - function EditBlob(assets_path, ace_mode) { - if (ace_mode == null) { - ace_mode = null; - } - this.editModeLinkClickHandler = bind(this.editModeLinkClickHandler, this); - ace.config.set("modePath", assets_path + "/ace"); - ace.config.loadModule("ace/ext/searchbox"); - this.editor = ace.edit("editor"); - this.editor.focus(); - if (ace_mode) { - this.editor.getSession().setMode("ace/mode/" + ace_mode); - } - $('form').submit((function(_this) { - return function() { - return $("#file-content").val(_this.editor.getValue()); - }; - // Before a form submission, move the content from the Ace editor into the - // submitted textarea - })(this)); - this.initModePanesAndLinks(); - this.initSoftWrap(); - new gl.BlobLicenseSelectors({ - editor: this.editor - }); + +import BlobLicenseSelectors from '../blob/template_selectors/blob_license_selectors'; +import BlobGitignoreSelectors from '../blob/template_selectors/blob_gitignore_selectors'; +import BlobCiYamlSelectors from '../blob/template_selectors/blob_ci_yaml_selectors'; +import BlobDockerfileSelectors from '../blob/template_selectors/blob_dockerfile_selectors'; + +export default class EditBlob { + constructor(assetsPath, aceMode) { + this.configureAceEditor(aceMode, assetsPath); + this.prepFileContentForSubmit(); + this.initModePanesAndLinks(); + this.initSoftWrap(); + this.initFileSelectors(); + } + + configureAceEditor(aceMode, assetsPath) { + ace.config.set('modePath', `${assetsPath}/ace`); + ace.config.loadModule('ace/ext/searchbox'); + + this.editor = ace.edit('editor'); + this.editor.focus(); + + if (aceMode) { + this.editor.getSession().setMode(`ace/mode/${aceMode}`); + } + } + + prepFileContentForSubmit() { + $('form').submit(() => { + $('#file-content').val(this.editor.getValue()); + }); + } + + initFileSelectors() { + this.blobTemplateSelectors = [ + new BlobLicenseSelectors({ + editor: this.editor, + }), new BlobGitignoreSelectors({ - editor: this.editor - }); - new gl.BlobCiYamlSelectors({ - editor: this.editor - }); - new gl.BlobDockerfileSelectors({ - editor: this.editor + editor: this.editor, + }), + new BlobCiYamlSelectors({ + editor: this.editor, + }), + new BlobDockerfileSelectors({ + editor: this.editor, + }), + ]; + } + + initModePanesAndLinks() { + this.$editModePanes = $('.js-edit-mode-pane'); + this.$editModeLinks = $('.js-edit-mode a'); + this.$editModeLinks.on('click', e => this.editModeLinkClickHandler(e)); + } + + editModeLinkClickHandler(e) { + e.preventDefault(); + + const currentLink = $(e.target); + const paneId = currentLink.attr('href'); + const currentPane = this.$editModePanes.filter(paneId); + + this.$editModeLinks.parent().removeClass('active hover'); + + currentLink.parent().addClass('active hover'); + + this.$editModePanes.hide(); + + currentPane.fadeIn(200); + + if (paneId === '#preview') { + this.$toggleButton.hide(); + return $.post(currentLink.data('preview-url'), { + content: this.editor.getValue(), + }, (response) => { + currentPane.empty().append(response); + return currentPane.renderGFM(); }); } - EditBlob.prototype.initModePanesAndLinks = function() { - this.$editModePanes = $(".js-edit-mode-pane"); - this.$editModeLinks = $(".js-edit-mode a"); - return this.$editModeLinks.click(this.editModeLinkClickHandler); - }; - - EditBlob.prototype.editModeLinkClickHandler = function(event) { - var currentLink, currentPane, paneId; - event.preventDefault(); - currentLink = $(event.target); - paneId = currentLink.attr("href"); - currentPane = this.$editModePanes.filter(paneId); - this.$editModeLinks.parent().removeClass("active hover"); - currentLink.parent().addClass("active hover"); - this.$editModePanes.hide(); - currentPane.fadeIn(200); - if (paneId === "#preview") { - this.$toggleButton.hide(); - return $.post(currentLink.data("preview-url"), { - content: this.editor.getValue() - }, function(response) { - currentPane.empty().append(response); - return currentPane.renderGFM(); - }); - } else { - this.$toggleButton.show(); - return this.editor.focus(); - } - }; - - EditBlob.prototype.initSoftWrap = function() { - this.isSoftWrapped = false; - this.$toggleButton = $('.soft-wrap-toggle'); - this.$toggleButton.on('click', this.toggleSoftWrap.bind(this)); - }; - - EditBlob.prototype.toggleSoftWrap = function(e) { - this.isSoftWrapped = !this.isSoftWrapped; - this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped); - this.editor.getSession().setUseWrapMode(this.isSoftWrapped); - }; - - return EditBlob; - })(); -}).call(window); + this.$toggleButton.show(); + + return this.editor.focus(); + } + + initSoftWrap() { + this.isSoftWrapped = false; + this.$toggleButton = $('.soft-wrap-toggle'); + this.$toggleButton.on('click', () => this.toggleSoftWrap()); + } + + toggleSoftWrap() { + this.isSoftWrapped = !this.isSoftWrapped; + this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped); + this.editor.getSession().setUseWrapMode(this.isSoftWrapped); + } +} diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 3874c2819a5..149bfbc8e8b 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -1,12 +1,11 @@ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ -/* global Vue */ /* global BoardService */ +import Vue from 'vue'; +import VueResource from 'vue-resource'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; -window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); require('./models/issue'); require('./models/label'); require('./models/list'); @@ -24,6 +23,8 @@ require('./components/new_list_dropdown'); require('./components/modal/index'); require('../vue_shared/vue_resource_interceptor'); +Vue.use(VueResource); + $(() => { const $boardApp = document.getElementById('board-app'); const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 67c0c419713..35b3205cca7 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -1,7 +1,7 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var */ -/* global Vue */ /* global Sortable */ +import Vue from 'vue'; import boardBlankState from './board_blank_state'; require('./board_delete'); diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index 52893d4642b..3fc68457961 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -1,5 +1,7 @@ /* global ListLabel */ -/* global Cookies */ + +import Cookies from 'js-cookie'; + const Store = gl.issueBoards.BoardsStore; export default { diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js index 4b72090df31..9320848bcca 100644 --- a/app/assets/javascripts/boards/components/board_card.js +++ b/app/assets/javascripts/boards/components/board_card.js @@ -1,4 +1,3 @@ -/* global Vue */ require('./issue_card_inner'); const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js index 861600424a5..af621cfd57f 100644 --- a/app/assets/javascripts/boards/components/board_delete.js +++ b/app/assets/javascripts/boards/components/board_delete.js @@ -1,5 +1,6 @@ /* eslint-disable comma-dangle, space-before-function-paren, no-alert */ -/* global Vue */ + +import Vue from 'vue'; (() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js index 1330d4ae840..86e6c26e570 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.js @@ -1,7 +1,7 @@ /* eslint-disable comma-dangle, space-before-function-paren, max-len */ -/* global Vue */ /* global Sortable */ +import Vue from 'vue'; import boardNewIssue from './board_new_issue'; import boardCard from './board_card'; @@ -48,7 +48,7 @@ import boardCard from './board_card'; this.list.getIssues(false); } - if (this.scrollHeight() > this.listHeight()) { + if (this.scrollHeight() > Math.ceil(this.listHeight())) { this.showCount = true; } else { this.showCount = false; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index dfc6eed785c..3c080008244 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -1,10 +1,11 @@ /* eslint-disable comma-dangle, space-before-function-paren, no-new */ -/* global Vue */ /* global IssuableContext */ /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ +import Vue from 'vue'; + require('./sidebar/remove_issue'); (() => { diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index 69e30cec4c5..ba44dc5ed94 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -1,4 +1,4 @@ -/* global Vue */ +import Vue from 'vue'; import eventHub from '../eventhub'; (() => { diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js index e6973c3fd59..823319df6e7 100644 --- a/app/assets/javascripts/boards/components/modal/empty_state.js +++ b/app/assets/javascripts/boards/components/modal/empty_state.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { const ModalStore = gl.issueBoards.ModalStore; diff --git a/app/assets/javascripts/boards/components/modal/filters.js b/app/assets/javascripts/boards/components/modal/filters.js index bd394a2318c..b214b5a7199 100644 --- a/app/assets/javascripts/boards/components/modal/filters.js +++ b/app/assets/javascripts/boards/components/modal/filters.js @@ -14,8 +14,10 @@ export default { this.filteredSearch = new FilteredSearchBoards(this.store); this.filteredSearch.removeTokens(); + this.filteredSearch.handleInputPlaceholder(); + this.filteredSearch.toggleClearSearchButton(); }, - beforeDestroy() { + destroyed() { this.filteredSearch.cleanup(); FilteredSearchContainer.container = document; this.store.path = ''; diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js index 1cbc422c961..887ce373096 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js +++ b/app/assets/javascripts/boards/components/modal/footer.js @@ -1,7 +1,8 @@ /* eslint-disable no-new */ -/* global Vue */ /* global Flash */ +import Vue from 'vue'; + require('./lists_dropdown'); (() => { diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 1b66c8b922d..91c08cde13a 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -1,5 +1,6 @@ -/* global Vue */ /* global ListIssue */ + +import Vue from 'vue'; import queryData from '../../utils/query_data'; require('./header'); @@ -64,7 +65,15 @@ require('./empty_state'); }, filter: { handler() { - this.loadIssues(true); + if (this.$el.tagName) { + this.page = 1; + this.filterLoading = true; + + this.loadIssues(true) + .then(() => { + this.filterLoading = false; + }); + } }, deep: true, }, @@ -115,6 +124,9 @@ require('./empty_state'); return this.activeTab === 'selected' && this.selectedIssues.length === 0; }, }, + created() { + this.page = 1; + }, components: { 'modal-header': gl.issueBoards.ModalHeader, 'modal-list': gl.issueBoards.ModalList, @@ -135,14 +147,14 @@ require('./empty_state'); :image="blankStateImage" :issue-link-base="issueLinkBase" :root-path="rootPath" - v-if="!loading && showList"></modal-list> + v-if="!loading && showList && !filterLoading"></modal-list> <empty-state v-if="showEmptyState" :image="blankStateImage" :new-issue-path="newIssuePath"></empty-state> <section class="add-issues-list text-center" - v-if="loading"> + v-if="loading || filterLoading"> <div class="add-issues-list-loading"> <i class="fa fa-spinner fa-spin"></i> </div> diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js index 3730c1ecaeb..aba56d4aa31 100644 --- a/app/assets/javascripts/boards/components/modal/list.js +++ b/app/assets/javascripts/boards/components/modal/list.js @@ -1,6 +1,8 @@ -/* global Vue */ /* global ListIssue */ /* global bp */ + +import Vue from 'vue'; + (() => { const ModalStore = gl.issueBoards.ModalStore; diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js index 3c05120a2da..9e9ed46ab8d 100644 --- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js +++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { const ModalStore = gl.issueBoards.ModalStore; diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js index 1cd6ca0ee88..23cb1b13d11 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.js +++ b/app/assets/javascripts/boards/components/modal/tabs.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { const ModalStore = gl.issueBoards.ModalStore; diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index e74935e1cb0..d8322b34d44 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -1,6 +1,8 @@ /* eslint-disable no-new */ -/* global Vue */ /* global Flash */ + +import Vue from 'vue'; + (() => { const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index 101732309ea..1264280284c 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -28,6 +28,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager { [].forEach.call(tokens, (el) => { el.parentNode.removeChild(el); }); + + this.filteredSearchInput.value = ''; } updateTokens() { diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js b/app/assets/javascripts/boards/filters/due_date_filters.js index 03425bb145b..70132dbfa6f 100644 --- a/app/assets/javascripts/boards/filters/due_date_filters.js +++ b/app/assets/javascripts/boards/filters/due_date_filters.js @@ -1,6 +1,7 @@ -/* global Vue */ /* global dateFormat */ +import Vue from 'vue'; + Vue.filter('due-date', (value) => { const date = new Date(value); return dateFormat(date, 'mmm d, yyyy', true); diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index ca5e6fa7e9d..d6175069e37 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -1,9 +1,10 @@ /* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, arrow-parens, comma-dangle, max-len */ -/* global Vue */ /* global ListLabel */ /* global ListMilestone */ /* global ListUser */ +import Vue from 'vue'; + class ListIssue { constructor (obj) { this.globalId = obj.id; diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index e54102814d6..db9bced2f89 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -1,5 +1,6 @@ /* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars */ -/* global Vue */ + +import Vue from 'vue'; class BoardService { constructor (root, bulkUpdatePath, boardId) { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 28ecb322df7..8912f234aa6 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -1,7 +1,8 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */ -/* global Cookies */ /* global List */ +import Cookies from 'js-cookie'; + (() => { window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js index 7ee266a831f..9b009483a3c 100644 --- a/app/assets/javascripts/boards/stores/modal_store.js +++ b/app/assets/javascripts/boards/stores/modal_store.js @@ -15,6 +15,7 @@ searchTerm: '', loading: false, loadingNewPage: false, + filterLoading: false, page: 1, perPage: 50, filter: { diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index a9f2d462c31..a92e068ca5a 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -1,8 +1,10 @@ /* eslint-disable no-param-reassign */ + +import Vue from 'vue'; +import VueResource from 'vue-resource'; import CommitPipelinesTable from './pipelines_table'; -window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); +Vue.use(VueResource); /** * Commits View > Pipelines Tab > Pipelines Table. diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 832c4b1bd2a..a20e5bc3b1b 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -1,10 +1,10 @@ -/* eslint-disable no-new*/ -/* global Flash */ import Vue from 'vue'; import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import eventHub from '../../vue_pipelines_index/event_hub'; +import EmptyState from '../../vue_pipelines_index/components/empty_state'; +import ErrorState from '../../vue_pipelines_index/components/error_state'; import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; @@ -22,6 +22,8 @@ import '../../vue_shared/vue_resource_interceptor'; export default Vue.component('pipelines-table', { components: { 'pipelines-table-component': PipelinesTableComponent, + 'error-state': ErrorState, + 'empty-state': EmptyState, }, /** @@ -36,12 +38,24 @@ export default Vue.component('pipelines-table', { return { endpoint: pipelinesTableData.endpoint, + helpPagePath: pipelinesTableData.helpPagePath, store, state: store.state, isLoading: false, + hasError: false, }; }, + computed: { + shouldRenderErrorState() { + return this.hasError && !this.isLoading; + }, + + shouldRenderEmptyState() { + return !this.state.pipelines.length && !this.isLoading; + }, + }, + /** * When the component is about to be mounted, tell the service to fetch the data * @@ -80,26 +94,25 @@ export default Vue.component('pipelines-table', { this.isLoading = false; }) .catch(() => { + this.hasError = true; this.isLoading = false; - new Flash('An error occurred while fetching the pipelines, please reload the page again.'); }); }, }, template: ` - <div class="pipelines"> + <div class="content-list pipelines"> <div class="realtime-loading" v-if="isLoading"> <i class="fa fa-spinner fa-spin"></i> </div> - <div class="blank-state blank-state-no-icon" - v-if="!isLoading && state.pipelines.length === 0"> - <h2 class="blank-state-title js-blank-state-title"> - No pipelines to show - </h2> - </div> + <empty-state + v-if="shouldRenderEmptyState" + :help-page-path="helpPagePath" /> + + <error-state v-if="shouldRenderErrorState" /> - <div class="table-holder pipelines" + <div class="table-holder" v-if="!isLoading && state.pipelines.length > 0"> <pipelines-table-component :pipelines="state.pipelines" diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index fbd0db64ca7..3253eebd9b5 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -1,9 +1,11 @@ // ECMAScript polyfills import 'core-js/fn/array/find'; +import 'core-js/fn/array/from'; import 'core-js/fn/object/assign'; import 'core-js/fn/promise'; import 'core-js/fn/string/code-point-at'; import 'core-js/fn/string/from-code-point'; +import 'core-js/fn/symbol'; // Browser polyfills import './polyfills/custom_event'; diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js index b83a4c63fad..9947f355aca 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js index cb1687dcc7a..6ad4805e8c5 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js index 73f4205b578..da80450a32c 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js index 501ffb1fac9..2200f43914f 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js +++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js b/app/assets/javascripts/cycle_analytics/components/total_time_component.js index 0d85e1a4678..b4442ea5566 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index beff293b587..ae17d05e679 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -1,9 +1,8 @@ -/* global Vue */ -/* global Cookies */ /* global Flash */ -window.Vue = require('vue'); -window.Cookies = require('js-cookie'); +import Vue from 'vue'; +import Cookies from 'js-cookie'; + require('./components/stage_code_component'); require('./components/stage_issue_component'); require('./components/stage_plan_component'); diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index cfa60325fcc..88180149715 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -33,11 +33,7 @@ class Diff { handleClickUnfold(e) { const $target = $(e.target); - // current babel config relies on iterators implementation, so we cannot simply do: - // const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent()); - const ref = this.lineNumbers($target.parent()); - const oldLineNumber = ref[0]; - const newLineNumber = ref[1]; + const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent()); const offset = newLineNumber - oldLineNumber; const bottom = $target.hasClass('js-unfold-bottom'); let since; @@ -105,10 +101,11 @@ class Diff { } lineNumbers(line) { - if (!line.children().length) { + const children = line.find('.diff-line-num').toArray(); + if (children.length !== 2) { return [0, 0]; } - return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10)); + return children.map(elm => parseInt($(elm).data('linenumber'), 10) || 0); } highlightSelectedLine() { diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js index d948dff58ec..fc2f20e3bcb 100644 --- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js @@ -1,6 +1,7 @@ /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */ /* global CommentsStore */ -const Vue = require('vue'); + +import Vue from 'vue'; (() => { const CommentAndResolveBtn = Vue.extend({ diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js index dd7081aefb7..0297add94d5 100644 --- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js +++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js @@ -1,4 +1,6 @@ -/* global CommentsStore Cookies notes */ +/* global CommentsStore */ +/* global notes */ + import Vue from 'vue'; import collapseIcon from '../icons/collapse_icon.svg'; diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 283dc330cad..8edc45130fc 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -1,7 +1,8 @@ /* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */ /* global DiscussionMixins */ /* global CommentsStore */ -const Vue = require('vue'); + +import Vue from 'vue'; (() => { const JumpToDiscussion = Vue.extend({ diff --git a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js index e86bef47172..8eb0e10b832 100644 --- a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js +++ b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js @@ -1,6 +1,7 @@ -/* global Vue */ /* global CommentsStore */ +import Vue from 'vue'; + (() => { const NewIssueForDiscussion = Vue.extend({ props: { diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index fbd980f0fce..312f38ce241 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -2,7 +2,8 @@ /* global CommentsStore */ /* global ResolveService */ /* global Flash */ -const Vue = require('vue'); + +import Vue from 'vue'; (() => { const ResolveBtn = Vue.extend({ diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js index de9367f2136..27147ac6b5c 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js @@ -1,7 +1,8 @@ /* eslint-disable comma-dangle, object-shorthand, func-names, no-param-reassign */ /* global DiscussionMixins */ /* global CommentsStore */ -const Vue = require('vue'); + +import Vue from 'vue'; ((w) => { w.ResolveCount = Vue.extend({ diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js index 7c5fcd04d2d..a964b7d0c6b 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js @@ -2,7 +2,7 @@ /* global CommentsStore */ /* global ResolveService */ -const Vue = require('vue'); +import Vue from 'vue'; (() => { const ResolveDiscussionBtn = Vue.extend({ diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js index 4f6b86a917c..b6b47e2da6f 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js @@ -1,8 +1,8 @@ /* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */ -/* global Vue */ /* global ResolveCount */ -const Vue = require('vue'); +import Vue from 'vue'; + require('./models/discussion'); require('./models/note'); require('./stores/comments'); diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js index dce1a9b58bd..dc43e4b2cc7 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js +++ b/app/assets/javascripts/diff_notes/models/discussion.js @@ -1,7 +1,8 @@ /* eslint-disable space-before-function-paren, camelcase, guard-for-in, no-restricted-syntax, no-unused-vars, max-len */ -/* global Vue */ /* global NoteModel */ +import Vue from 'vue'; + class DiscussionModel { constructor (discussionId) { this.id = discussionId; diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index 090c454e9e4..bfa4fc9037a 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -2,10 +2,13 @@ /* global Flash */ /* global CommentsStore */ -const Vue = window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); +import Vue from 'vue'; +import VueResource from 'vue-resource'; + require('../../vue_shared/vue_resource_interceptor'); +Vue.use(VueResource); + (() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js index 69c4d7a8434..e6cbda56c91 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js +++ b/app/assets/javascripts/diff_notes/stores/comments.js @@ -1,7 +1,8 @@ /* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */ -/* global Vue */ /* global DiscussionModel */ +import Vue from 'vue'; + ((w) => { w.CommentsStore = { state: {}, diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 3557f6f617e..d1a662459e1 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -41,9 +41,9 @@ import GroupsList from './groups_list'; import ProjectsList from './projects_list'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; +import UserCallout from './user_callout'; const ShortcutsBlob = require('./shortcuts_blob'); -const UserCallout = require('./user_callout'); (function() { var Dispatcher; diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 9b40a3f20a4..7f7d93f3e27 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -56,10 +56,12 @@ require('../window')(function(w){ this.hookInput = hookInput; this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper); + this.hookInput.trigger.addEventListener('mousedown.dl', this.keydownWrapper); }, destroy: function destroy(){ this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper); + this.hookInput.trigger.removeEventListener('mousedown.dl', this.keydownWrapper); } }; }); diff --git a/app/assets/javascripts/environments/components/environment_external_url.js b/app/assets/javascripts/environments/components/environment_external_url.js index a554998f52c..b4f9eb357fd 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js +++ b/app/assets/javascripts/environments/components/environment_external_url.js @@ -14,6 +14,7 @@ export default { class="btn external_url" :href="externalUrl" target="_blank" + rel="noopener noreferrer" title="Environment external URL"> <i class="fa fa-external-link" aria-hidden="true"></i> </a> diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 7ace51748aa..22352950452 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -40,6 +40,8 @@ import FilteredSearchContainer from './container'; this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this); this.editTokenWrapper = this.editToken.bind(this); this.tokenChange = this.tokenChange.bind(this); + this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this); + this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this); this.filteredSearchInputForm = this.filteredSearchInput.form; this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit); @@ -51,11 +53,13 @@ import FilteredSearchContainer from './container'; this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); this.filteredSearchInput.addEventListener('click', this.tokenChange); this.filteredSearchInput.addEventListener('keyup', this.tokenChange); + this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper); this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken); this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper); this.clearSearchButton.addEventListener('click', this.clearSearchWrapper); document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); document.addEventListener('click', this.unselectEditTokensWrapper); + document.addEventListener('click', this.removeInputContainerFocusWrapper); document.addEventListener('keydown', this.removeSelectedTokenWrapper); } @@ -69,11 +73,13 @@ import FilteredSearchContainer from './container'; this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); this.filteredSearchInput.removeEventListener('click', this.tokenChange); this.filteredSearchInput.removeEventListener('keyup', this.tokenChange); + this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper); this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken); this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper); this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper); document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens); document.removeEventListener('click', this.unselectEditTokensWrapper); + document.removeEventListener('click', this.removeInputContainerFocusWrapper); document.removeEventListener('keydown', this.removeSelectedTokenWrapper); } @@ -124,6 +130,26 @@ import FilteredSearchContainer from './container'; } } + addInputContainerFocus() { + const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container'); + + if (inputContainer) { + inputContainer.classList.add('focus'); + } + } + + removeInputContainerFocus(e) { + const inputContainer = this.filteredSearchInput.closest('.filtered-search-input-container'); + const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); + const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null; + const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null; + + if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown && + !isElementInStaticFilterDropdown && inputContainer) { + inputContainer.classList.remove('focus'); + } + } + static selectToken(e) { const button = e.target.closest('.selectable'); @@ -358,7 +384,7 @@ import FilteredSearchContainer from './container'; paths.push(`search=${sanitized}`); } - const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`; + const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`; if (this.updateObject) { this.updateObject(parameterizedUrl); diff --git a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js index 357b3487ca9..aec13e78f42 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js +++ b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js @@ -1,4 +1,4 @@ -/* global Vue */ +import Vue from 'vue'; import stopwatchSvg from 'icons/_icon_stopwatch.svg'; require('../../../lib/utils/pretty_time'); diff --git a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js index 750468c679b..c55e263f6f4 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js +++ b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + require('../../../lib/utils/pretty_time'); (() => { diff --git a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js index 309e9f2f9ef..a7fbd704c40 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js +++ b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { Vue.component('time-tracking-estimate-only-pane', { name: 'time-tracking-estimate-only-pane', diff --git a/app/assets/javascripts/issuable/time_tracking/components/help_state.js b/app/assets/javascripts/issuable/time_tracking/components/help_state.js index d7ced6d7151..344b29ebea4 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/help_state.js +++ b/app/assets/javascripts/issuable/time_tracking/components/help_state.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { Vue.component('time-tracking-help-state', { name: 'time-tracking-help-state', diff --git a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js index 1d2ca643b5b..b081adf5e64 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js +++ b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { Vue.component('time-tracking-no-tracking-pane', { name: 'time-tracking-no-tracking-pane', diff --git a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js index ed283fec3c3..edb9169112f 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js +++ b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js @@ -1,4 +1,5 @@ -/* global Vue */ +import Vue from 'vue'; + (() => { Vue.component('time-tracking-spent-only-pane', { name: 'time-tracking-spent-only-pane', diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js index 1fae2d62b14..0213522f551 100644 --- a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js +++ b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js @@ -1,4 +1,4 @@ -/* global Vue */ +import Vue from 'vue'; require('./help_state'); require('./collapsed_state'); diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js index 0134b7cb6f3..1689a69e1ed 100644 --- a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js +++ b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js @@ -1,11 +1,12 @@ -/* global Vue */ +import Vue from 'vue'; +import VueResource from 'vue-resource'; -window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); require('./components/time_tracker'); require('../../smart_interval'); require('../../subbable_resource'); +Vue.use(VueResource); + (() => { /* This Vue instance represents what will become the parent instance for the * sidebar. It will be responsible for managing `issuable` state and propagating diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index 115312d4b83..834b98e8601 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -1,8 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ /* global UsersSelect */ -/* global Cookies */ /* global bp */ +import Cookies from 'js-cookie'; + (function() { this.IssuableContext = (function() { function IssuableContext(currentUser) { diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js new file mode 100644 index 00000000000..c30a1fcb5da --- /dev/null +++ b/app/assets/javascripts/lib/utils/poll.js @@ -0,0 +1,73 @@ +import httpStatusCodes from './http_status'; + +/** + * Polling utility for handling realtime updates. + * Service for vue resouce and method need to be provided as props + * + * @example + * new poll({ + * resource: resource, + * method: 'name', + * data: {page: 1, scope: 'all'}, + * successCallback: () => {}, + * errorCallback: () => {}, + * }).makeRequest(); + * + * this.service = new BoardsService(endpoint); + * new poll({ + * resource: this.service, + * method: 'get', + * data: {page: 1, scope: 'all'}, + * successCallback: () => {}, + * errorCallback: () => {}, + * }).makeRequest(); + * + * + * 1. Checks for response and headers before start polling + * 2. Interval is provided by `Poll-Interval` header. + * 3. If `Poll-Interval` is -1, we stop polling + * 4. If HTTP response is 200, we poll. + * 5. If HTTP response is different from 200, we stop polling. + * + */ +export default class Poll { + constructor(options = {}) { + this.options = options; + this.options.data = options.data || {}; + + this.intervalHeader = 'POLL-INTERVAL'; + this.timeoutID = null; + this.canPoll = true; + } + + checkConditions(response) { + const headers = gl.utils.normalizeHeaders(response.headers); + const pollInterval = headers[this.intervalHeader]; + + if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) { + this.timeoutID = setTimeout(() => { + this.makeRequest(); + }, pollInterval); + } + + this.options.successCallback(response); + } + + makeRequest() { + const { resource, method, data, errorCallback } = this.options; + + return resource[method](data) + .then(response => this.checkConditions(response)) + .catch(error => errorCallback(error)); + } + + /** + * Stops the polling recursive chain + * and guarantees if the timeout is already running it won't make another request by + * cancelling the previously established timeout. + */ + stop() { + this.canPoll = false; + clearTimeout(this.timeoutID); + } +} diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 81d5748191d..9007d661d01 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -1,6 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */ /* global bp */ -/* global Cookies */ /* global Flash */ /* global ConfirmDangerModal */ /* global Aside */ @@ -24,7 +23,6 @@ import './extensions/array'; window.jQuery = jQuery; window.$ = jQuery; window._ = _; -window.Cookies = Cookies; window.Pikaday = Pikaday; window.Dropzone = Dropzone; window.Sortable = Sortable; @@ -49,15 +47,6 @@ import { installGlEmojiElement } from './behaviors/gl_emoji'; installGlEmojiElement(); // blob -import './blob/blob_ci_yaml'; -import './blob/blob_dockerfile_selector'; -import './blob/blob_dockerfile_selectors'; -import './blob/blob_file_dropzone'; -import './blob/blob_gitignore_selector'; -import './blob/blob_gitignore_selectors'; -import './blob/blob_license_selector'; -import './blob/blob_license_selectors'; -import './blob/template_selector'; import './blob/create_branch_dropdown'; import './blob/target_branch_dropdown'; diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js index c7e78fed8fe..645045fea88 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js @@ -1,8 +1,9 @@ /* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-new, no-param-reassign, max-len */ -/* global Vue */ /* global ace */ /* global Flash */ +import Vue from 'vue'; + ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js index 240c8f98932..56d6678e1bd 100644 --- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js +++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign, comma-dangle */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js index 97753c50b60..0fc4a13450a 100644 --- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign, comma-dangle */ -/* global Vue */ + +import Vue from 'vue'; ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js index 74587df22c5..c4e379a4a0b 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js @@ -1,6 +1,7 @@ /* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */ -/* global Cookies */ -/* global Vue */ + +import Vue from 'vue'; +import Cookies from 'js-cookie'; ((global) => { global.mergeConflicts = global.mergeConflicts || {}; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js index 653e52fb6bf..15992460146 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js @@ -1,8 +1,8 @@ /* eslint-disable new-cap, comma-dangle, no-new */ -/* global Vue */ /* global Flash */ -window.Vue = require('vue'); +import Vue from 'vue'; + require('./merge_conflict_store'); require('./merge_conflict_service'); require('./mixins/line_conflict_utils'); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 190336dbd20..811f90c5a87 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -1,10 +1,10 @@ /* eslint-disable no-new, class-methods-use-this */ /* global Breakpoints */ -/* global Cookies */ /* global Flash */ +import Cookies from 'js-cookie'; + require('./breakpoints'); -window.Cookies = require('js-cookie'); require('./flash'); /* eslint-disable max-len */ @@ -127,9 +127,6 @@ require('./flash'); if (this.diffViewType() === 'parallel') { this.expandViewContainer(); } - $.scrollTo('.merge-request-details .merge-request-tabs', { - offset: 0, - }); } else if (action === 'pipelines') { if (this.pipelinesLoaded) { return; diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js index 94a4f24f1d7..0e2af3df071 100644 --- a/app/assets/javascripts/merge_request_widget.js +++ b/app/assets/javascripts/merge_request_widget.js @@ -14,13 +14,13 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; <%= ci_success_icon %> <span> Deployed to - <a href="<%- url %>" target="_blank" class="environment"> + <a href="<%- url %>" target="_blank" rel="noopener noreferrer" class="environment"> <%- name %> </a> <span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>"> <%- deployed_at %> </span> - <a class="js-environment-link" href="<%- external_url %>" target="_blank"> + <a class="js-environment-link" href="<%- external_url %>" target="_blank" rel="noopener noreferrer"> <i class="fa fa-external-link"></i> View on <%- external_url_formatted %> </a> diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 40e977df693..ac4fad88fe5 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,8 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ -/* global Vue */ /* global Issuable */ /* global ListMilestone */ +import Vue from 'vue'; + (function() { this.MilestoneSelect = (function() { function MilestoneSelect(currentProject, els) { diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js index fcffc11a2df..844a0785bc9 100644 --- a/app/assets/javascripts/monitoring/prometheus_graph.js +++ b/app/assets/javascripts/monitoring/prometheus_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable no-new */ +/* eslint-disable no-new*/ /* global Flash */ import d3 from 'd3'; @@ -180,7 +180,7 @@ class PrometheusGraph { // Metric Usage axisLabelContainer.append('rect') .attr('x', this.originalWidth - 170) - .attr('y', (this.originalHeight / 2) - 80) + .attr('y', (this.originalHeight / 2) - 60) .style('fill', graphSpecifics.area_fill_color) .attr('width', 20) .attr('height', 35); @@ -188,13 +188,13 @@ class PrometheusGraph { axisLabelContainer.append('text') .attr('class', 'label-axis-text') .attr('x', this.originalWidth - 140) - .attr('y', (this.originalHeight / 2) - 65) - .text(graphSpecifics.graph_legend_title); + .attr('y', (this.originalHeight / 2) - 50) + .text('Average'); axisLabelContainer.append('text') .attr('class', 'text-metric-usage') .attr('x', this.originalWidth - 140) - .attr('y', (this.originalHeight / 2) - 50); + .attr('y', (this.originalHeight / 2) - 25); } handleMouseOverGraph(x, y, valuesToPlot, chart, prometheusGraphContainer, key) { @@ -263,12 +263,12 @@ class PrometheusGraph { cpu_values: { area_fill_color: '#edf3fc', line_color: '#5b99f7', - graph_legend_title: 'CPU Usage (Cores)', + graph_legend_title: 'CPU utilization (%)', }, memory_values: { area_fill_color: '#fca326', line_color: '#fc6d26', - graph_legend_title: 'Memory Usage (MB)', + graph_legend_title: 'Memory usage (MB)', }, }; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 47cc34e7a20..1d563c63f39 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,14 +1,14 @@ /* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */ /* global Flash */ /* global Autosave */ -/* global Cookies */ /* global ResolveService */ /* global mrRefreshWidgetUrl */ +import Cookies from 'js-cookie'; + require('./autosave'); window.autosize = require('vendor/autosize'); window.Dropzone = require('dropzone'); -window.Cookies = require('js-cookie'); require('./dropzone_input'); require('./gfm_auto_complete'); require('vendor/jquery.caret'); // required by jquery.atwho diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index db7ceaa2421..f944fcc5a58 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -1,7 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ -/* global Cookies */ /* global ProjectSelect */ +import Cookies from 'js-cookie'; + (function() { this.Project = (function() { function Project() { diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js index 48cae8a4fa9..ea91aaa10a6 100644 --- a/app/assets/javascripts/render_gfm.js +++ b/app/assets/javascripts/render_gfm.js @@ -1,4 +1,5 @@ -/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, max-len */ +/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */ + // Render Gitlab flavoured Markdown // // Delegates to syntax highlight and render math diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js index 76c61c001ba..8b3fee49cb9 100644 --- a/app/assets/javascripts/render_math.js +++ b/app/assets/javascripts/render_math.js @@ -1,4 +1,6 @@ -/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, max-len, no-console */ +/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len, no-console */ +/* global katex */ + // Renders math using KaTeX in any element with the // `js-render-math` class // diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 903862cac6b..64a68d56962 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */ -/* global Cookies */ + +import Cookies from 'js-cookie'; (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; @@ -199,7 +200,7 @@ Sidebar.prototype.setSidebarHeight = function() { const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight(); const $rightSidebar = $('.js-right-sidebar'); - const diff = $navHeight - $('body').scrollTop(); + const diff = $navHeight - $(window).scrollTop(); if (diff > 0) { $rightSidebar.outerHeight($(window).height() - diff); } else { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 81766f4bd55..fd5097696ad 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -33,6 +33,10 @@ }; Shortcuts.prototype.toggleMarkdownPreview = function(e) { + // Check if short-cut was triggered while in Write Mode + if ($(e.target).hasClass('js-note-text')) { + $('.js-md-preview-button').focus(); + } return $(document).triggerHandler('markdown-preview:toggle', [e]); }; diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js index e7baea894f6..4f1a19924a4 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js @@ -22,6 +22,9 @@ require('./shortcuts'); Mousetrap.bind('g m', function() { return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'); }); + Mousetrap.bind('g t', function() { + return ShortcutsDashboardNavigation.findAndFollowLink('.shortcuts-todos'); + }); Mousetrap.bind('g p', function() { return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'); }); diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 09a58cad2b2..3f5d6724417 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -43,6 +43,9 @@ require('./shortcuts'); Mousetrap.bind('g m', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); }); + Mousetrap.bind('g t', function() { + return ShortcutsNavigation.findAndFollowLink('.shortcuts-todos'); + }); Mousetrap.bind('g w', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'); }); diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js index 62d1604fe9e..9c307915ec4 100644 --- a/app/assets/javascripts/subscription.js +++ b/app/assets/javascripts/subscription.js @@ -1,4 +1,4 @@ -/* global Vue */ +import Vue from 'vue'; (() => { class Subscription { diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js index e9e9aafd71a..32067ed1fee 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js +++ b/app/assets/javascripts/templates/issuable_template_selector.js @@ -1,15 +1,15 @@ /* eslint-disable comma-dangle, max-len, no-useless-return, no-param-reassign, max-len */ /* global Api */ -require('../blob/template_selector'); +import TemplateSelector from '../blob/template_selectors/template_selector'; ((global) => { - class IssuableTemplateSelector extends gl.TemplateSelector { + class IssuableTemplateSelector extends TemplateSelector { constructor(...args) { super(...args); this.projectPath = this.dropdown.data('project-path'); this.namespacePath = this.dropdown.data('namespace-path'); - this.issuableType = this.wrapper.data('issuable-type'); + this.issuableType = this.$dropdownContainer.data('issuable-type'); this.titleInput = $(`#${this.issuableType}_title`); const initialQuery = { @@ -41,16 +41,16 @@ require('../blob/template_selector'); } setInputValueToTemplateContent() { - // `this.requestFileSuccess` sets the value of the description input field + // `this.setEditorContent` sets the value of the description input field // to the content of the template selected. if (this.titleInput.val() === '') { // If the title has not yet been set, focus the title input and // skip focusing the description input by setting `true` as the - // `skipFocus` option to `requestFileSuccess`. - this.requestFileSuccess(this.currentTemplate, { skipFocus: true }); + // `skipFocus` option to `setEditorContent`. + this.setEditorContent(this.currentTemplate, { skipFocus: true }); this.titleInput.focus(); } else { - this.requestFileSuccess(this.currentTemplate, { skipFocus: false }); + this.setEditorContent(this.currentTemplate, { skipFocus: false }); } return; } diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index 059e6c628b3..19c9efe7fbd 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -1,5 +1,6 @@ /* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */ -/* global Cookies */ + +import Cookies from 'js-cookie'; ((global) => { global.User = class { diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js index 99419e85b20..b27d252a3ef 100644 --- a/app/assets/javascripts/user_callout.js +++ b/app/assets/javascripts/user_callout.js @@ -1,4 +1,4 @@ -/* global Cookies */ +import Cookies from 'js-cookie'; const userCalloutElementName = '.user-callout'; const closeButton = '.close-user-callout'; @@ -27,7 +27,7 @@ const USER_CALLOUT_TEMPLATE = ` </div> </div>`; -class UserCallout { +export default class UserCallout { constructor() { this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE); this.userCalloutBody = $(userCalloutElementName); @@ -56,5 +56,3 @@ class UserCallout { } } } - -module.exports = UserCallout; diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js index 465618e3d53..5db0d936ad8 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/user_tabs.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign */ +/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */ /* UserTabs @@ -82,8 +82,19 @@ content on the Users#show page. } bindEvents() { - return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') + this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this); + + this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); + + this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper); + } + + changeProjectsPage(e) { + e.preventDefault(); + + $('.tab-pane.active').empty(); + this.loadTab($(e.target).attr('href'), this.getCurrentAction()); } tabShown(event) { @@ -119,7 +130,7 @@ content on the Users#show page. complete: () => this.toggleLoading(false), dataType: 'json', type: 'GET', - url: `${source}.json`, + url: source, success: (data) => { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); @@ -153,6 +164,10 @@ content on the Users#show page. }, document.title, new_state); return new_state; } + + getCurrentAction() { + return this.$parentEl.find('.nav-links .active a').data('action'); + } } global.UserTabs = UserTabs; })(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index eb897e9dfe9..48e20cf501f 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,8 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ -/* global Vue */ /* global Issuable */ /* global ListUser */ +import Vue from 'vue'; + (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, slice = [].slice; diff --git a/app/assets/javascripts/vue_pipelines_index/components/empty_state.js b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js new file mode 100644 index 00000000000..56b4858f4b4 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/empty_state.js @@ -0,0 +1,33 @@ +import pipelinesEmptyStateSVG from 'empty_states/icons/_pipelines_empty.svg'; + +export default { + props: { + helpPagePath: { + type: String, + required: true, + }, + }, + + template: ` + <div class="row empty-state"> + <div class="col-xs-12"> + <div class="svg-content"> + ${pipelinesEmptyStateSVG} + </div> + </div> + + <div class="col-xs-12 text-center"> + <div class="text-content"> + <h4>Build with confidence</h4> + <p> + Continous Integration can help catch bugs by running your tests automatically, + while Continuous Deployment can help you deliver code to your product environment. + </p> + <a :href="helpPagePath" class="btn btn-info"> + Get started with Pipelines + </a> + </div> + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/error_state.js b/app/assets/javascripts/vue_pipelines_index/components/error_state.js new file mode 100644 index 00000000000..e5d228bddf8 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/error_state.js @@ -0,0 +1,19 @@ +import pipelinesErrorStateSVG from 'empty_states/icons/_pipelines_failed.svg'; + +export default { + template: ` + <div class="row empty-state js-pipelines-error-state"> + <div class="col-xs-12"> + <div class="svg-content"> + ${pipelinesErrorStateSVG} + </div> + </div> + + <div class="col-xs-12 text-center"> + <div class="text-content"> + <h4>The API failed to fetch the pipelines.</h4> + </div> + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js b/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js new file mode 100644 index 00000000000..6aa10531034 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js @@ -0,0 +1,52 @@ +export default { + props: { + newPipelinePath: { + type: String, + required: true, + }, + + hasCiEnabled: { + type: Boolean, + required: true, + }, + + helpPagePath: { + type: String, + required: true, + }, + + ciLintPath: { + type: String, + required: true, + }, + + canCreatePipeline: { + type: Boolean, + required: true, + }, + }, + + template: ` + <div class="nav-controls"> + <a + v-if="canCreatePipeline" + :href="newPipelinePath" + class="btn btn-create"> + Run Pipeline + </a> + + <a + v-if="!hasCiEnabled" + :href="helpPagePath" + class="btn btn-info"> + Get started with Pipelines + </a> + + <a + :href="ciLintPath" + class="btn btn-default"> + CI Lint + </a> + </div> + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js b/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js new file mode 100644 index 00000000000..b4480bd98c7 --- /dev/null +++ b/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js @@ -0,0 +1,68 @@ +export default { + props: { + scope: { + type: String, + required: true, + }, + + count: { + type: Object, + required: true, + }, + + paths: { + type: Object, + required: true, + }, + }, + + template: ` + <ul class="nav-links"> + <li + class="js-pipelines-tab-all" + :class="{ 'active': scope === 'all'}"> + <a :href="paths.allPath"> + All + <span class="badge js-totalbuilds-count"> + {{count.all}} + </span> + </a> + </li> + <li class="js-pipelines-tab-pending" + :class="{ 'active': scope === 'pending'}"> + <a :href="paths.pendingPath"> + Pending + <span class="badge"> + {{count.pending}} + </span> + </a> + </li> + <li class="js-pipelines-tab-running" + :class="{ 'active': scope === 'running'}"> + <a :href="paths.runningPath"> + Running + <span class="badge"> + {{count.running}} + </span> + </a> + </li> + <li class="js-pipelines-tab-finished" + :class="{ 'active': scope === 'finished'}"> + <a :href="paths.finishedPath"> + Finished + <span class="badge"> + {{count.finished}} + </span> + </a> + </li> + <li class="js-pipelines-tab-branches" + :class="{ 'active': scope === 'branches'}"> + <a :href="paths.branchesPath">Branches</a> + </li> + <li class="js-pipelines-tab-tags" + :class="{ 'active': scope === 'tags'}"> + <a :href="paths.tagsPath">Tags</a> + </li> + </ul> + `, +}; diff --git a/app/assets/javascripts/vue_pipelines_index/index.js b/app/assets/javascripts/vue_pipelines_index/index.js index b4e2d3a1143..48f9181a8d9 100644 --- a/app/assets/javascripts/vue_pipelines_index/index.js +++ b/app/assets/javascripts/vue_pipelines_index/index.js @@ -1,28 +1,22 @@ +import Vue from 'vue'; import PipelinesStore from './stores/pipelines_store'; import PipelinesComponent from './pipelines'; import '../vue_shared/vue_resource_interceptor'; -const Vue = window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); - $(() => new Vue({ - el: document.querySelector('.vue-pipelines-index'), + el: document.querySelector('#pipelines-list-vue'), data() { - const project = document.querySelector('.pipelines'); const store = new PipelinesStore(); return { store, - endpoint: project.dataset.url, }; }, components: { 'vue-pipelines': PipelinesComponent, }, template: ` - <vue-pipelines - :endpoint="endpoint" - :store="store" /> + <vue-pipelines :store="store" /> `, })); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/vue_pipelines_index/pipelines.js index f389e5e4950..48f0e9036e8 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js @@ -1,19 +1,15 @@ -/* global Flash */ -/* eslint-disable no-new */ -import '~/flash'; import Vue from 'vue'; import PipelinesService from './services/pipelines_service'; import eventHub from './event_hub'; import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; import TablePaginationComponent from '../vue_shared/components/table_pagination'; +import EmptyState from './components/empty_state'; +import ErrorState from './components/error_state'; +import NavigationTabs from './components/navigation_tabs'; +import NavigationControls from './components/nav_controls'; export default { props: { - endpoint: { - type: String, - required: true, - }, - store: { type: Object, required: true, @@ -23,17 +19,109 @@ export default { components: { 'gl-pagination': TablePaginationComponent, 'pipelines-table-component': PipelinesTableComponent, + 'empty-state': EmptyState, + 'error-state': ErrorState, + 'navigation-tabs': NavigationTabs, + 'navigation-controls': NavigationControls, }, data() { + const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; + return { + endpoint: pipelinesData.endpoint, + cssClass: pipelinesData.cssClass, + helpPagePath: pipelinesData.helpPagePath, + newPipelinePath: pipelinesData.newPipelinePath, + canCreatePipeline: pipelinesData.canCreatePipeline, + allPath: pipelinesData.allPath, + pendingPath: pipelinesData.pendingPath, + runningPath: pipelinesData.runningPath, + finishedPath: pipelinesData.finishedPath, + branchesPath: pipelinesData.branchesPath, + tagsPath: pipelinesData.tagsPath, + hasCi: pipelinesData.hasCi, + ciLintPath: pipelinesData.ciLintPath, state: this.store.state, apiScope: 'all', pagenum: 1, - pageRequest: false, + isLoading: false, + hasError: false, }; }, + computed: { + canCreatePipelineParsed() { + return gl.utils.convertPermissionToBoolean(this.canCreatePipeline); + }, + + scope() { + const scope = gl.utils.getParameterByName('scope'); + return scope === null ? 'all' : scope; + }, + + shouldRenderErrorState() { + return this.hasError && !this.isLoading; + }, + + /** + * The empty state should only be rendered when the request is made to fetch all pipelines + * and none is returned. + * + * @return {Boolean} + */ + shouldRenderEmptyState() { + return !this.isLoading && + !this.hasError && + !this.state.pipelines.length && + (this.scope === 'all' || this.scope === null); + }, + + /** + * When a specific scope does not have pipelines we render a message. + * + * @return {Boolean} + */ + shouldRenderNoPipelinesMessage() { + return !this.isLoading && + !this.hasError && + !this.state.pipelines.length && + this.scope !== 'all' && + this.scope !== null; + }, + + shouldRenderTable() { + return !this.hasError && + !this.isLoading && this.state.pipelines.length; + }, + + /** + * Pagination should only be rendered when there is more than one page. + * + * @return {Boolean} + */ + shouldRenderPagination() { + return !this.isLoading && + this.state.pipelines.length && + this.state.pageInfo.total > this.state.pageInfo.perPage; + }, + + hasCiEnabled() { + return this.hasCi !== undefined; + }, + + paths() { + return { + allPath: this.allPath, + pendingPath: this.pendingPath, + finishedPath: this.finishedPath, + runningPath: this.runningPath, + branchesPath: this.branchesPath, + tagsPath: this.tagsPath, + }; + }, + }, + created() { this.service = new PipelinesService(this.endpoint); @@ -69,7 +157,7 @@ export default { const pageNumber = gl.utils.getParameterByName('page') || this.pagenum; const scope = gl.utils.getParameterByName('scope') || this.apiScope; - this.pageRequest = true; + this.isLoading = true; return this.service.getPipelines(scope, pageNumber) .then(resp => ({ headers: resp.headers, @@ -81,41 +169,72 @@ export default { this.store.storePagination(response.headers); }) .then(() => { - this.pageRequest = false; + this.isLoading = false; }) .catch(() => { - this.pageRequest = false; - new Flash('An error occurred while fetching the pipelines, please reload the page again.'); + this.hasError = true; + this.isLoading = false; }); }, }, - template: ` - <div> - <div class="pipelines realtime-loading" v-if="pageRequest"> - <i class="fa fa-spinner fa-spin" aria-hidden="true"></i> - </div> - <div class="blank-state blank-state-no-icon" - v-if="!pageRequest && state.pipelines.length === 0"> - <h2 class="blank-state-title js-blank-state-title"> - No pipelines to show - </h2> + template: ` + <div :class="cssClass"> + + <div + class="top-area" + v-if="!isLoading && !shouldRenderEmptyState"> + <navigation-tabs + :scope="scope" + :count="state.count" + :paths="paths" /> + + <navigation-controls + :new-pipeline-path="newPipelinePath" + :has-ci-enabled="hasCiEnabled" + :help-page-path="helpPagePath" + :ciLintPath="ciLintPath" + :can-create-pipeline="canCreatePipelineParsed " /> </div> - <div class="table-holder" v-if="!pageRequest && state.pipelines.length"> - <pipelines-table-component - :pipelines="state.pipelines" - :service="service"/> + <div class="content-list pipelines"> + + <div + class="realtime-loading" + v-if="isLoading"> + <i + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + </div> + + <empty-state + v-if="shouldRenderEmptyState" + :help-page-path="helpPagePath" /> + + <error-state v-if="shouldRenderErrorState" /> + + <div + class="blank-state blank-state-no-icon" + v-if="shouldRenderNoPipelinesMessage"> + <h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2> + </div> + + <div + class="table-holder" + v-if="shouldRenderTable"> + + <pipelines-table-component + :pipelines="state.pipelines" + :service="service"/> + </div> + + <gl-pagination + v-if="shouldRenderPagination" + :pagenum="pagenum" + :change="change" + :count="state.count.all" + :pageInfo="state.pageInfo"/> </div> - - <gl-pagination - v-if="!pageRequest && state.pipelines.length && state.pageInfo.total > state.pageInfo.perPage" - :pagenum="pagenum" - :change="change" - :count="state.count.all" - :pageInfo="state.pageInfo" - > - </gl-pagination> </div> `, }; diff --git a/app/assets/javascripts/vue_shared/common_vue.js b/app/assets/javascripts/vue_shared/common_vue.js new file mode 100644 index 00000000000..eb2a6071fda --- /dev/null +++ b/app/assets/javascripts/vue_shared/common_vue.js @@ -0,0 +1,6 @@ +import Vue from 'vue'; +import './vue_resource_interceptor'; + +if (process.env.NODE_ENV !== 'production') { + Vue.config.productionTip = false; +} diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js index f1c1e553b16..d5f87588c28 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js @@ -1,20 +1,23 @@ -/* eslint-disable no-param-reassign, no-plusplus */ import Vue from 'vue'; import VueResource from 'vue-resource'; Vue.use(VueResource); +// Maintain a global counter for active requests +// see: spec/support/wait_for_vue_resource.rb Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; + window.activeVueResources = window.activeVueResources || 0; + window.activeVueResources += 1; next(() => { - Vue.activeResources--; + window.activeVueResources -= 1; }); }); +// Inject CSRF token so we don't break any tests. Vue.http.interceptors.push((request, next) => { - // needed in order to not break the tests. if ($.rails) { + // eslint-disable-next-line no-param-reassign request.headers['X-CSRF-Token'] = $.rails.csrfToken(); } next(); diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index 546718ddaf8..1ae144fb471 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -92,6 +92,10 @@ .award-menu-holder { display: inline-block; position: relative; + + .tooltip { + white-space: nowrap; + } } .award-control { @@ -124,6 +128,10 @@ &:focus { outline: 0; } + + .award-control-icon { + margin: 0; + } } &.is-loading { @@ -153,6 +161,7 @@ .award-control-icon { color: $border-gray-normal; margin-top: 1px; + padding: 0 2px; } .award-control-text { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index cda46223492..50849e95541 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -68,23 +68,19 @@ } @mixin btn-green { - @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, $white-light); + @include btn-color($green-500, $green-600, $green-600, $green-700, $green-700, $green-800, $white-light); } @mixin btn-blue { - @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, $white-light); -} - -@mixin btn-blue-medium { - @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, $white-light); + @include btn-color($blue-500, $blue-600, $blue-600, $blue-700, $blue-700, $blue-800, $white-light); } @mixin btn-orange { - @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, $white-light); + @include btn-color($orange-500, $orange-600, $orange-600, $orange-700, $orange-700, $orange-800, $white-light); } @mixin btn-red { - @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, $white-light); + @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light); } @mixin btn-gray { @@ -145,11 +141,11 @@ &.btn-new, &.btn-create, &.btn-save { - @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal); + @include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700); } &.btn-remove { - @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); + @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); } } @@ -157,11 +153,8 @@ @include btn-gray; } - &.btn-primary { - @include btn-blue-medium; - } - &.btn-info, + &.btn-primary, &.btn-register { @include btn-blue; } @@ -171,11 +164,11 @@ } &.btn-close { - @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal); + @include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700); } &.btn-spam { - @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); + @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); } &.btn-danger, @@ -360,7 +353,7 @@ .btn-inverted { &-secondary { - @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal); + @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700); } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 186bb9ac616..da5b754aec7 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -195,7 +195,7 @@ text-decoration: none; .badge { - background-color: darken($row-hover, 5%); + background-color: darken($dropdown-link-hover-bg, 5%); } } diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 6bdaa7cdd6f..51805c5d734 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -173,17 +173,26 @@ } } + &:hover { + @extend .form-control:hover; + } + + &.focus, + &.focus:hover { + border-color: $dropdown-input-focus-border; + box-shadow: 0 0 4px $search-input-focus-shadow-color; + } + + &.focus .fa-filter { + color: $common-gray-dark; + } + .form-control { position: relative; min-width: 200px; - padding-left: 0; - padding-right: 25px; + padding: 5px 25px 6px 0; border-color: transparent; - &:focus ~ .fa-filter { - color: $common-gray-dark; - } - &:focus, &:hover { outline: none; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 25d6fbe465a..432024779fd 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -177,37 +177,45 @@ label { } .gl-field-error { - color: $red-normal; + color: $red-500; } .gl-show-field-errors { .gl-field-success-outline { - border: 1px solid $green-normal; + border: 1px solid $green-600; &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-normal; + box-shadow: 0 0 0 1px $green-600 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-600; border: 0 none; } } .gl-field-error-outline { - border: 1px solid $red-normal; + border: 1px solid $red-500; &:focus { - box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error; + box-shadow: 0 0 0 1px $red-500 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error; border: 0 none; } } .gl-field-success-message { - color: $green-normal; + color: $green-600; } .gl-field-error-message { - color: $red-normal; + color: $red-500; } .gl-field-hint { color: $gl-text-color; } } + +@media(max-width: $screen-xs-max) { + .remember-me { + .remember-me-checkbox { + margin-top: 0; + } + } +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 6660a022260..fa02598760f 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -265,7 +265,7 @@ header { } .impersonation i { - color: $red-normal; + color: $red-500; } } diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index db8d231a82a..87667f39ab8 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -1,8 +1,8 @@ .ci-status-icon-success { - color: $gl-success; + color: $green-500; svg { - fill: $gl-success; + fill: $green-500; } } @@ -17,18 +17,18 @@ .ci-status-icon-pending, .ci-status-icon-failed_with_warnings, .ci-status-icon-success_with_warnings { - color: $gl-warning; + color: $orange-500; svg { - fill: $gl-warning; + fill: $orange-500; } } .ci-status-icon-running { - color: $blue-normal; + color: $blue-400; svg { - fill: $blue-normal; + fill: $blue-400; } } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 46632f15f35..1537b0744cc 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -33,7 +33,7 @@ } &.status-box-open { - background-color: $green-light; + background-color: $green-500; } &.status-box-expired { diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 0a42b17c1f5..20c7bc93c28 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -23,6 +23,10 @@ body { } } +.content-wrapper { + padding-bottom: 100px; +} + .container { padding-top: 0; z-index: 5; @@ -72,28 +76,28 @@ body { /* Stripe the background colors so that adjacent alert-warnings are distinct from one another */ .alert-warning { transition: background-color 0.15s, border-color 0.15s; - background-color: lighten($gl-warning, 4%); - border-color: lighten($gl-warning, 4%); + background-color: $orange-500; + border-color: $orange-500; } .alert-warning + .alert-warning { - background-color: $gl-warning; - border-color: $gl-warning; + background-color: $orange-600; + border-color: $orange-600; } .alert-warning + .alert-warning + .alert-warning { - background-color: darken($gl-warning, 4%); - border-color: darken($gl-warning, 4%); + background-color: $orange-700; + border-color: $orange-700; } .alert-warning + .alert-warning + .alert-warning + .alert-warning { - background-color: darken($gl-warning, 8%); - border-color: darken($gl-warning, 8%); + background-color: $orange-800; + border-color: $orange-800; } .alert-warning:only-of-type { - background-color: $gl-warning; - border-color: $gl-warning; + background-color: $orange-500; + border-color: $orange-500; } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 7adbb0a4188..15dc0aa6a52 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -122,7 +122,7 @@ ul.content-list { } .member-group-link { - color: $blue-normal; + color: $blue-600; } .description { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 205d23b1329..5ab505034b6 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -416,14 +416,16 @@ .page-with-layout-nav { .right-sidebar { - top: ($header-height * 2) + 2; + top: ($header-height + 1) * 2; } - .build-sidebar { - top: ($header-height * 3) + 3; + &.page-with-sub-nav { + .right-sidebar { + top: ($header-height + 1) * 3; - &.affix { - top: 0; + &.affix { + top: 0; + } } } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index 0fc89d5976a..c9f345d24be 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -31,6 +31,7 @@ $border-radius-small: 3px !default; // $text-color: $gl-text-color; $link-color: $gl-link-color; +$link-hover-color: $gl-link-hover-color; //== Typography @@ -73,7 +74,7 @@ $pagination-hover-color: $gl-text-color; $pagination-hover-bg: $row-hover; $pagination-hover-border: $border-color; -$pagination-active-color: $blue-dark; +$pagination-active-color: $blue-600; $pagination-active-bg: $white-light; $pagination-active-border: $border-color; @@ -135,8 +136,8 @@ $well-border: #eee; // //## -$code-color: #c7254e; -$code-bg: #f9f2f4; +$code-color: $red-600; +$code-bg: lighten($red-50, 2%); $kbd-color: $white-light; $kbd-bg: #333; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 6841adb637e..97794a47df8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -26,27 +26,49 @@ $gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c4c4c4; -$green-light: #3cbd70; -$green-normal: darken($green-light, $darken-normal-factor); -$green-dark: darken($green-light, $darken-dark-factor); - -$blue-light: #2ea8e5; -$blue-normal: darken($blue-light, $darken-normal-factor); -$blue-dark: darken($blue-light, $darken-dark-factor); - -$blue-medium-light: #3498cb; -$blue-medium: darken($blue-medium-light, $darken-normal-factor); -$blue-medium-dark: darken($blue-medium-light, $darken-dark-factor); - -$blue-light-transparent: rgba(44, 159, 216, 0.05); - -$orange-light: #fc8a51; -$orange-normal: darken($orange-light, $darken-normal-factor); -$orange-dark: darken($orange-light, $darken-dark-factor); - -$red-light: #e52c5a; -$red-normal: darken($red-light, $darken-normal-factor); -$red-dark: darken($red-light, $darken-dark-factor); +$green-50: #e4f5eb; +$green-100: #bae6cc; +$green-200: #8dd5aa; +$green-300: #5fc488; +$green-400: #3cb76f; +$green-500: #1aaa55; +$green-600: #168f48; +$green-700: #12753a; +$green-800: #0e5a2d; +$green-900: #0a4020; + +$blue-50: #e4eff9; +$blue-100: #bcd7f1; +$blue-200: #8fbce8; +$blue-300: #62a1df; +$blue-400: #418cd8; +$blue-500: #1f78d1; +$blue-600: #1b69b6; +$blue-700: #17599c; +$blue-800: #134a81; +$blue-900: #0f3b66; + +$orange-50: #fff2e1; +$orange-100: #fedfb3; +$orange-200: #feca81; +$orange-300: #fdb44f; +$orange-400: #fca429; +$orange-500: #fc9403; +$orange-600: #de7e00; +$orange-700: #c26700; +$orange-800: #a35100; +$orange-900: #853b00; + +$red-50: #fbe7e4; +$red-100: #f4c4bc; +$red-200: #ed9d90; +$red-300: #e67664; +$red-400: #e05842; +$red-500: #db3b21; +$red-600: #c0341d; +$red-700: #a62d19; +$red-800: #8b2615; +$red-900: #711e11; $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); @@ -58,33 +80,11 @@ $border-gray-light: darken($gray-light, $darken-border-factor); $border-gray-normal: darken($gray-normal, $darken-border-factor); $border-gray-dark: darken($white-normal, $darken-border-factor); -$border-green-extra-light: #9adb84; -$border-green-light: darken($green-light, $darken-border-factor); -$border-green-normal: darken($green-normal, $darken-border-factor); -$border-green-dark: darken($green-dark, $darken-border-factor); - -$border-blue-light: darken($blue-light, $darken-border-factor); -$border-blue-normal: darken($blue-normal, $darken-border-factor); -$border-blue-dark: darken($blue-dark, $darken-border-factor); - -$border-orange-light: darken($orange-light, $darken-border-factor); -$border-orange-normal: darken($orange-normal, $darken-border-factor); -$border-orange-dark: darken($orange-dark, $darken-border-factor); - -$border-red-light: darken($red-light, $darken-border-factor); -$border-red-normal: darken($red-normal, $darken-border-factor); -$border-red-dark: darken($red-dark, $darken-border-factor); - -$warning-message-bg: #fbf2d9; -$warning-message-color: #9e8e60; -$warning-message-border: #f0e2bb; - /* * UI elements */ $border-color: #e5e5e5; -$focus-border-color: #3aabf0; -$sidebar-collapsed-icon-color: #999; +$focus-border-color: $blue-300; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; $well-light-border: #f1f1f1; @@ -97,10 +97,11 @@ $gl-font-size: 14px; $gl-text-color: rgba(0, 0, 0, .85); $gl-text-color-secondary: rgba(0, 0, 0, .55); $gl-text-color-disabled: rgba(0, 0, 0, .35); -$gl-text-green: #4a2; -$gl-text-red: #d12f19; -$gl-text-orange: #d90; -$gl-link-color: #3777b0; +$gl-text-green: $green-600; +$gl-text-red: $red-500; +$gl-text-orange: $orange-600; +$gl-link-color: $blue-600; +$gl-link-hover-color: $blue-800; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-gray-dark: #313236; @@ -117,9 +118,9 @@ $list-text-disabled-color: $gl-text-color-disabled; $list-border-light: #eee; $list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; -$list-warning-row-bg: #fcf8e3; -$list-warning-row-border: #faebcc; -$list-warning-row-color: #8a6d3b; +$list-warning-row-bg: $orange-50; +$list-warning-row-border: $orange-100; +$list-warning-row-color: $orange-700; /* * Markdown @@ -146,24 +147,24 @@ $gl-sidebar-padding: 22px; /* * Misc */ -$row-hover: #f7faff; -$row-hover-border: #b2d7ff; +$row-hover: lighten($blue-50, 2%); +$row-hover-border: $blue-100; $progress-color: #c0392b; $header-height: 50px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; $gl-avatar-size: 40px; -$error-exclamation-point: #e62958; +$error-exclamation-point: $red-500; $border-radius-default: 2px; $settings-icon-size: 18px; -$provider-btn-not-active-color: #4688f1; -$link-underline-blue: #4a8bee; -$active-item-blue: #4a8bee; +$provider-btn-not-active-color: $blue-500; +$link-underline-blue: $blue-500; +$active-item-blue: $blue-500; $layout-link-gray: #7e7c7c; $btn-side-margin: 10px; $btn-sm-side-margin: 7px; $btn-xs-side-margin: 5px; -$issue-status-expired: #cea61b; +$issue-status-expired: $orange-500; $issuable-sidebar-color: $gl-text-color-secondary; $show-aside-bg: #eee; $show-aside-color: #777; @@ -192,10 +193,10 @@ $user-mention-color: #2fa0bb; $time-color: #999; $project-member-show-color: #aaa; $gl-promo-color: #aaa; -$error-bg: #c67; -$warning-message-bg: #ffffe6; -$warning-message-border: #ed9; -$warning-message-color: #b90; +$error-bg: $red-400; +$warning-message-bg: $orange-50; +$warning-message-border: $orange-100; +$warning-message-color: $orange-700; $control-group-descr-color: #666; $table-permission-x-bg: #d9edf7; $username-color: #666; @@ -210,30 +211,30 @@ $tanuki-yellow: #fca326; /* * State colors: */ -$gl-primary: $blue-normal; -$gl-success: $green-normal; +$gl-primary: $blue-500; +$gl-success: $green-500; $gl-success-focus: rgba($gl-success, .4); -$gl-info: $blue-normal; -$gl-warning: $orange-normal; -$gl-danger: $red-normal; +$gl-info: $blue-500; +$gl-warning: $orange-500; +$gl-danger: $red-500; $gl-btn-active-background: rgba(0, 0, 0, 0.16); $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; /* * Commit Diff Colors */ -$added: #63c363; -$deleted: #f77; -$line-added: #ecfdf0; -$line-added-dark: #c7f0d2; -$line-removed: #fbe9eb; -$line-removed-dark: #fac5cd; -$line-number-old: #f9d7dc; -$line-number-new: #ddfbe6; -$line-number-select: #fbf2da; -$line-target-blue: #f6faff; -$line-select-yellow: #fcf8e7; -$line-select-yellow-dark: #f0e2bd; +$added: $green-300; +$deleted: $red-300; +$line-added: $green-50; +$line-added-dark: $green-100; +$line-removed: $red-50; +$line-removed-dark: $red-100; +$line-number-old: lighten($red-100, 5%); +$line-number-new: lighten($green-100, 5%); +$line-number-select: lighten($orange-100, 5%); +$line-target-blue: $blue-50; +$line-select-yellow: $orange-50; +$line-select-yellow-dark: $orange-100; $dark-diff-match-bg: rgba(255, 255, 255, 0.3); $dark-diff-match-color: rgba(255, 255, 255, 0.1); $file-mode-changed: #777; @@ -273,7 +274,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%); /* * Filtered Search */ -$dropdown-hover-color: #3b86ff; +$dropdown-hover-color: $blue-400; /* * Buttons @@ -296,10 +297,10 @@ $award-emoji-menu-shadow: rgba(0,0,0,.175); /* * Search Box */ -$search-input-border-color: rgba(#4688f1, .8); +$search-input-border-color: rgba($blue-400, .8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-width: 220px; -$location-badge-active-bg: #4f91f8; +$location-badge-active-bg: $blue-500; $location-icon-color: #e7e9ed; /* @@ -361,18 +362,18 @@ $builds-trace-bg: #111; /* * Callout */ -$callout-danger-bg: #fdf7f7; -$callout-danger-border: #eed3d7; -$callout-danger-color: #b94a48; -$callout-warning-bg: #faf8f0; -$callout-warning-border: #faebcc; -$callout-warning-color: #8a6d3b; -$callout-info-bg: #f4f8fa; -$callout-info-border: #bce8f1; -$callout-info-color: #34789a; -$callout-success-bg: #dff0d8; -$callout-success-border: #5ca64d; -$callout-success-color: #3c763d; +$callout-danger-bg: $red-50; +$callout-danger-border: $red-100; +$callout-danger-color: $red-700; +$callout-warning-bg: $orange-50; +$callout-warning-border: $orange-100; +$callout-warning-color: $orange-700; +$callout-info-bg: $blue-50; +$callout-info-border: $blue-100; +$callout-info-color: $blue-700; +$callout-success-bg: $green-50; +$callout-success-border: $green-100; +$callout-success-color: $green-700; /* * Commit Page @@ -392,7 +393,7 @@ $common-green: $gl-text-green; /* * Editor */ -$editor-cancel-color: #b94a48; +$editor-cancel-color: $red-600; /* * Events @@ -416,10 +417,10 @@ $logs-p-color: #333; * Forms */ $input-danger-bg: #f2dede; -$input-danger-border: #d66; +$input-danger-border: $red-400; $input-group-addon-bg: #f7f8fa; $gl-field-focus-shadow: rgba(0, 0, 0, 0.075); -$gl-field-focus-shadow-error: rgba(210, 40, 82, 0.6); +$gl-field-focus-shadow-error: rgba($red-500, 0.6); /* * Help @@ -453,14 +454,14 @@ $label-border-radius: 100px; /* * Lint */ -$lint-incorrect-color: red; -$lint-correct-color: #47a447; +$lint-incorrect-color: $red-500; +$lint-correct-color: $green-500; /* * Login */ $login-brand-holder-color: #888; -$login-devise-error-color: #a00; +$login-devise-error-color: $red-700; /* * Nav @@ -474,33 +475,33 @@ $nav-toggle-gray: #666; */ $notify-details: #777; $notify-footer: #777; -$notify-new-file: #090; -$notify-deleted-file: #b00; +$notify-new-file: $green-600; +$notify-deleted-file: $red-700; /* * Projects */ $project-option-descr-color: #54565b; $project-breadcrumb-color: #999; -$project-private-forks-notice-odd: #2aa056; +$project-private-forks-notice-odd: $green-600; $project-network-controls-color: #888; /* * Runners */ -$runner-state-shared-bg: #32b186; -$runner-state-specific-bg: #3498db; -$runner-status-online-color: $green-normal; +$runner-state-shared-bg: $green-400; +$runner-state-specific-bg: $blue-400; +$runner-status-online-color: $green-600; $runner-status-offline-color: $gray-darkest; -$runner-status-paused-color: $red-normal; +$runner-status-paused-color: $red-500; /* Stat Graph */ $stat-graph-common-bg: #f3f3f3; -$stat-graph-area-fill: #1db34f; +$stat-graph-area-fill: $green-500; $stat-graph-axis-fill: #aaa; -$stat-graph-orange-fill: #f17f49; +$stat-graph-orange-fill: $orange-500; $stat-graph-selection-fill: #333; $stat-graph-selection-stroke: #333; @@ -514,7 +515,7 @@ $select2-drop-shadow2: rgba(31, 37, 50, 0.317647); /* * Todo */ -$todo-alert-blue: #428bca; +$todo-alert-blue: $blue-500; $todo-body-pre-color: #777; $todo-body-border: #ddd; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index f9ee33019cd..575d32b1a23 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -487,9 +487,9 @@ right: -3px; top: -3px; width: 17px; - background-color: $blue-light; + background-color: $blue-500; color: $white-light; - border: 1px solid $border-blue-light; + border: 1px solid $blue-600; font-size: 9px; line-height: 15px; border-radius: 50%; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index a24292a7c8c..969fc75c6eb 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -366,9 +366,3 @@ right: 0; margin-top: -17px; } - -@media (min-width: $screen-md-min) { - .sub-nav.build { - width: calc(100% + #{$gutter_width}); - } -} diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 73a5da715f2..25be7f408d0 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -18,7 +18,10 @@ .environments-container { .table-holder { width: 100%; - overflow: auto; + + @media (max-width: $screen-sm-max) { + overflow: auto; + } } .table.ci-table { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 4426169ef5a..c1a9bc4be28 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -498,7 +498,7 @@ svg { width: 16px; height: 16px; - fill: $sidebar-collapsed-icon-color; + fill: $issuable-sidebar-color; } &:hover svg { @@ -520,12 +520,12 @@ &.over_estimate { .meter-fill { - background: $red-light; + background: $red-500; } .time-remaining, .compare-value.spent { - color: $red-light; + color: $red-500; } } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index cb7ebd61504..b2f45625a2a 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -46,6 +46,10 @@ ul.related-merge-requests > li { .merge-request-id { flex-shrink: 0; } + + .merge-request-info { + margin-left: 5px; + } } .merge-requests-title, @@ -58,10 +62,6 @@ ul.related-merge-requests > li { display: inline-block; } -.merge-request-info { - margin-left: 5px; -} - .merge-request-status { font-size: 13px; padding: 0 5px; @@ -69,21 +69,17 @@ ul.related-merge-requests > li { height: 20px; border-radius: 3px; line-height: 18px; - border: 1px solid; &.merged { - border-color: darken($blue-normal, 10%); - background: $blue-normal; + background: $blue-500; } &.closed { - border-color: darken($red-normal, 10%); - background: $red-normal; + background: $red-500; } &.open { - border: 1px solid darken($green-normal, 10%); - background: $green-normal; + background: $green-500; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 71ed5b1361a..8249e02b64a 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -85,11 +85,11 @@ } .username .validation-success { - color: $green-normal; + color: $green-600; } .username .validation-error { - color: $red-normal; + color: $red-500; } } } diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5a9f199fb34..35cefd449f1 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -255,7 +255,7 @@ $colors: ( &.saved { .editor { - border-top: solid 2px $border-green-extra-light; + border-top: solid 2px $green-200; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7c3172421c1..6630904ec92 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -535,7 +535,7 @@ } .fa-info-circle { - color: $orange-normal; + color: $orange-500; padding-right: 5px; } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 27c47d36818..efbd9365fd9 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -63,7 +63,7 @@ } .remaining-days { - color: $orange-light; + color: $orange-600; } .milestone-stats-and-buttons { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e238f0865f6..a2129722633 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -462,17 +462,18 @@ ul.notes { background: $white-light; padding: 1px 5px; font-size: 12px; - color: $gl-link-color; + color: $blue-500; margin-left: -55px; position: absolute; z-index: 10; width: 23px; height: 23px; - border: 1px solid $border-color; + border: 1px solid $blue-500; transition: transform .1s ease-in-out; &:hover { - background: $gl-info; + background: $blue-500; + border-color: $blue-600; color: $white-light; transform: scale(1.15); } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 33b38ca6923..a4fe652b52f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -2,6 +2,7 @@ .realtime-loading { font-size: 40px; text-align: center; + margin: 0 auto; } .stage { @@ -13,9 +14,16 @@ white-space: nowrap; } + .empty-state { + margin: 5% auto 0; + } + .table-holder { width: 100%; - overflow: auto; + + @media (max-width: $screen-sm-max) { + overflow: auto; + } } .commit-title { @@ -99,8 +107,6 @@ @media (max-width: $screen-md-max) { .content-list { - &.pipelines, - &.environments-container, &.builds-content-list { width: 100%; overflow: auto; @@ -667,51 +673,71 @@ // Dropdown button animation in mini pipeline graph &.ci-status-icon-success { - border-color: $gl-success; - color: $gl-success; + border-color: $green-500; + color: $green-500; &:hover, &:focus, &:active { - background-color: rgba($gl-success, 0.1); - border-color: $gl-success; + background-color: $green-50; + border-color: $green-600; + color: $green-600; + + svg { + fill: $green-600; + } } } &.ci-status-icon-failed { - border-color: $gl-danger; - color: $gl-danger; + border-color: $red-500; + color: $red-500; &:hover, &:focus, &:active { - background-color: rgba($gl-danger, 0.1); - border-color: $gl-danger; + background-color: $red-50; + border-color: $red-600; + color: $red-600; + + svg { + fill: $red-600; + } } } &.ci-status-icon-pending, &.ci-status-icon-success_with_warnings { - border-color: $gl-warning; - color: $gl-warning; + border-color: $orange-500; + color: $orange-500; &:hover, &:focus, &:active { - background-color: rgba($gl-warning, 0.1); - border-color: $gl-warning; + background-color: $orange-50; + border-color: $orange-600; + color: $orange-600; + + svg { + fill: $orange-600; + } } } &.ci-status-icon-running { - border-color: $blue-normal; - color: $blue-normal; + border-color: $blue-400; + color: $blue-400; &:hover, &:focus, &:active { - background-color: rgba($blue-normal, 0.1); - border-color: $blue-normal; + background-color: $blue-50; + border-color: $blue-600; + color: $blue-600; + + svg { + fill: $blue-600; + } } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 1a983d8c9ef..703c5fc8869 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -74,7 +74,6 @@ display: inline; a { - color: $blue-dark; text-decoration: none; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index efa47be9a73..949d52cffa2 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -582,54 +582,55 @@ pre.light-well { /* * Projects list rendered on dashboard and user page */ - .projects-list { @include basic-list; + display: flex; + flex-direction: column; .project-row { - border-color: $white-normal; - - .project-full-name { - @include str-truncated; + display: flex; + align-items: center; + } - @media (max-width: $screen-xs-max) { - max-width: 50%; - } - } + h3 { + font-size: $gl-font-size; + } - .controls { - line-height: $list-text-height; + a { + color: $gl-text-color; + } - .badge { - @media (max-width: $screen-xs-max) { - display: none; - } - } + .avatar-container, + .controls { + flex: 0 0 auto; + } - a:hover { - text-decoration: none; - } + .avatar-container { + align-self: flex-start; + } - > span { - margin-left: 10px; - } + .project-details { + min-width: 0; - svg { - position: relative; - top: 2px; - } + p, + .commit-row-message { + @include str-truncated(100%); + margin-bottom: 0; } + } - .description p { - @media (max-width: $screen-xs-max) { - max-width: 50%; - } - } + .controls { + margin-left: auto; } - .bottom { - padding-top: $gl-padding; - padding-bottom: 0; + .ci-status-link { + display: inline-block; + line-height: 17px; + vertical-align: middle; + + &:hover { + text-decoration: none; + } } } diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss index bed6470dbd3..23a9c2ada80 100644 --- a/app/assets/stylesheets/pages/sherlock.scss +++ b/app/assets/stylesheets/pages/sherlock.scss @@ -28,6 +28,6 @@ table .sherlock-code { } .sherlock-line-samples-table .slow { - color: $red-light; + color: $red-500; font-weight: bold; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 6f31d4ed789..4a284247143 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -21,42 +21,41 @@ &.ci-failed, &.ci-failed_with_warnings { - color: $gl-danger; - border-color: $gl-danger; + color: $red-500; + border-color: $red-500; &:not(span):hover { - background-color: rgba($gl-danger, .07); + background-color: $red-50; + color: $red-600; + border-color: $red-600; + + svg { + fill: $red-600; + } } svg { - fill: $gl-danger; + fill: $red-500; } } &.ci-success, &.ci-success_with_warnings { - color: $gl-success; - border-color: $gl-success; + color: $green-600; + border-color: $green-500; &:not(span):hover { - background-color: rgba($gl-success, .07); - } - - svg { - fill: $gl-success; - } - } - - &.ci-info { - color: $gl-info; - border-color: $gl-info; + background-color: $green-50; + color: $green-700; + border-color: $green-600; - &:not(span):hover { - background-color: rgba($gl-info, .07); + svg { + fill: $green-600; + } } svg { - fill: $gl-info; + fill: $green-500; } } @@ -75,28 +74,41 @@ } &.ci-pending { - color: $gl-warning; - border-color: $gl-warning; + color: $orange-600; + border-color: $orange-500; &:not(span):hover { - background-color: rgba($gl-warning, .07); + background-color: $orange-50; + color: $orange-700; + border-color: $orange-600; + + svg { + fill: $orange-600; + } } svg { - fill: $gl-warning; + fill: $orange-500; } } + &.ci-info, &.ci-running { - color: $blue-normal; - border-color: $blue-normal; + color: $blue-500; + border-color: $blue-500; &:not(span):hover { - background-color: rgba($blue-normal, .07); + background-color: $blue-50; + color: $blue-600; + border-color: $blue-600; + + svg { + fill: $blue-600; + } } svg { - fill: $blue-normal; + fill: $blue-500; } } diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 5f0aede4f5e..b071d7f18cd 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -47,6 +47,7 @@ .todo-avatar, .todo-actions { + @include transition(opacity); -webkit-flex: 0 0 auto; flex: 0 0 auto; } @@ -67,21 +68,34 @@ flex: 0 1 100%; min-width: 0; } -} -.todos-list > .todo.todo-pending.done-reversible { - background-color: $gray-light; + &.todo-pending.done-reversible { + background-color: $white-light; - &:hover { - border-color: $border-color; - } + &:hover { + border-color: $white-dark; + background-color: $gray-light; - .title { - font-weight: normal; + .todo-avatar, + .todo-item { + opacity: .6; + } + } + + .todo-avatar, + .todo-item { + opacity: .2; + } + + .btn { + background-color: $gray-light; + } } } .todo-item { + @include transition(opacity); + .todo-title { display: flex; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b7ce081a5cd..6a6e335d314 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -64,8 +64,11 @@ class ApplicationController < ActionController::Base # This filter handles both private tokens and personal access tokens def authenticate_user_from_private_token! - token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence - user = User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string) + token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence + + return unless token.present? + + user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) if user && can?(user, :log_in) # Notice we are passing store false, so the user is not diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 096de8032ae..498690e8f11 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -51,7 +51,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController private def find_todos - @todos ||= TodosFinder.new(current_user, params.merge(include_associations: true)).execute + @todos ||= TodosFinder.new(current_user, params).execute end def todos_counts diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 8e42cdf415f..5ad1e116e4e 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -44,15 +44,15 @@ class Import::BitbucketController < Import::BaseController repo_owner = repo.owner repo_owner = current_user.username if repo_owner == bitbucket_client.user.username - @target_namespace = params[:new_namespace].presence || repo_owner + namespace_path = params[:new_namespace].presence || repo_owner - namespace = find_or_create_namespace(@target_namespace, current_user) + @target_namespace = find_or_create_namespace(namespace_path, current_user) - if current_user.can?(:create_projects, namespace) + if current_user.can?(:create_projects, @target_namespace) # The token in a session can be expired, we need to get most recent one because # Bitbucket::Connection class refreshes it. session[:bitbucket_token] = bitbucket_client.connection.token - @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @project_name, namespace, current_user, credentials).execute + @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, credentials).execute else render 'unauthorized' end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index e2f81b09adc..f1a93ccb3ad 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -89,4 +89,9 @@ class Projects::ApplicationController < ApplicationController def builds_enabled return render_404 unless @project.feature_available?(:builds, current_user) end + + def update_ref + branch_exists = @repository.find_branch(@target_branch) + @ref = @target_branch if branch_exists + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 52fc67d162c..80a95c6158b 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -89,11 +89,6 @@ class Projects::BlobController < Projects::ApplicationController private - def update_ref - branch_exists = @repository.find_branch(@target_branch) - @ref = @target_branch if branch_exists - end - def blob @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path)) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 886934a3f67..f1e4246e7fb 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -1,7 +1,7 @@ class Projects::BuildsController < Projects::ApplicationController before_action :build, except: [:index, :cancel_all] before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play] - before_action :authorize_update_build!, except: [:index, :show, :status, :raw] + before_action :authorize_update_build!, except: [:index, :show, :status, :raw, :trace] layout 'project' def index diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 1502b734f37..d0c44e297e3 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -31,8 +31,10 @@ class Projects::DeployKeysController < Projects::ApplicationController end def disable - @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy + deploy_key_project = @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]) + return render_404 unless deploy_key_project + deploy_key_project.destroy! redirect_to_repository_settings(@project) end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 491cd5dc351..0d6d9f492c1 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -148,7 +148,14 @@ class Projects::IssuesController < Projects::ApplicationController end format.json do - render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) + if @issue.valid? + render json: @issue.to_json(methods: [:task_status, :task_status_short], + include: { milestone: {}, + assignee: { only: [:name, :username], methods: [:avatar_url] }, + labels: { methods: :text_color } }) + else + render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity + end end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 82f9b6e06db..2fadf7c8c81 100644..100755 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -308,7 +308,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end format.json do - render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) + render json: @merge_request.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) end end rescue ActiveRecord::StaleObjectError @@ -402,7 +402,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if params[:ref].present? @ref = params[:ref] - @commit = @repository.commit(@ref) + @commit = @repository.commit("refs/heads/#{@ref}") end render layout: false @@ -413,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController if params[:ref].present? @ref = params[:ref] - @commit = @target_project.commit(@ref) + @commit = @target_project.commit("refs/heads/#{@ref}") end render layout: false diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index be52b0fa7cf..5922e686cd0 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -13,11 +13,14 @@ class Projects::MilestonesController < Projects::ApplicationController def index @milestones = case params[:state] - when 'all' then @project.milestones.reorder(due_date: :desc, title: :asc) - when 'closed' then @project.milestones.closed.reorder(due_date: :desc, title: :asc) - else @project.milestones.active.reorder(due_date: :asc, title: :asc) + when 'all' then @project.milestones + when 'closed' then @project.milestones.closed + else @project.milestones.active end + @sort = params[:sort] || 'due_date_asc' + @milestones = @milestones.sort(@sort) + @milestones = @milestones.includes(:project) respond_to do |format| format.html do diff --git a/app/controllers/projects/settings/members_controller.rb b/app/controllers/projects/settings/members_controller.rb index cbfa2afa959..54f9dceddef 100644 --- a/app/controllers/projects/settings/members_controller.rb +++ b/app/controllers/projects/settings/members_controller.rb @@ -9,6 +9,7 @@ module Projects @skip_groups = @group_links.pluck(:group_id) @skip_groups << @project.namespace_id unless @project.personal? + @skip_groups += @project.group.ancestors.pluck(:id) if @project.group @project_members = MembersFinder.new(@project, current_user).execute diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 4f094146348..637b61504d8 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -34,6 +34,7 @@ class Projects::TreeController < Projects::ApplicationController def create_dir return render_404 unless @commit_params.values.all? + update_ref create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)), failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6e29f1e8a65..2683614d2e8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -39,7 +39,7 @@ class UsersController < ApplicationController format.html { render 'show' } format.json do render json: { - html: view_to_html_string("shared/projects/_list", projects: @projects, remote: true) + html: view_to_html_string("shared/projects/_list", projects: @projects) } end end @@ -65,7 +65,7 @@ class UsersController < ApplicationController format.html { render 'show' } format.json do render json: { - html: view_to_html_string("snippets/_snippets", collection: @snippets, remote: true) + html: view_to_html_string("snippets/_snippets", collection: @snippets) } end end diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 13d33a1c31b..b7f091f334d 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -24,7 +24,6 @@ class TodosFinder def execute items = current_user.todos - items = include_associations(items) items = by_action_id(items) items = by_action(items) items = by_author(items) @@ -39,17 +38,6 @@ class TodosFinder private - def include_associations(items) - return items unless params[:include_associations] - - items.includes( - [ - target: { project: [:route, namespace: :route] }, - author: { namespace: :route }, - ] - ) - end - def action_id? action_id.present? && Todo::ACTION_NAMES.has_key?(action_id.to_i) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 0b0c6a07efd..8631bc54509 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -215,6 +215,6 @@ module BlobHelper end def open_raw_file_button(path) - link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', title: 'Open raw', data: { container: 'body' } + link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' } end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 8aad39e148b..cef624430da 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -211,7 +211,7 @@ module CommitsHelper external_url = environment.external_url_for(diff_new_path, commit_sha) return unless external_url - link_to(external_url, class: 'btn btn-file-option has-tooltip', target: '_blank', title: "View on #{environment.formatted_external_url}", data: { container: 'body' }) do + link_to(external_url, class: 'btn btn-file-option has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: "View on #{environment.formatted_external_url}", data: { container: 'body' }) do icon('external-link') end end diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index a0642a1894b..a57b5a8fea5 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -7,7 +7,7 @@ module ImportHelper def provider_project_link(provider, path_with_namespace) url = __send__("#{provider}_project_url", path_with_namespace) - link_to path_with_namespace, url, target: '_blank' + link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer' end private diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 2e3a15bc1b9..7f656b8caae 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -6,7 +6,13 @@ module NamespacesHelper def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) groups = current_user.owned_groups + current_user.masters_groups - groups << extra_group if extra_group && !Group.exists?(name: extra_group.name) + unless extra_group.nil? || extra_group.is_a?(Group) + extra_group = Group.find(extra_group) if Namespace.find(extra_group).kind == 'group' + end + + if extra_group && extra_group.is_a?(Group) && (!Group.exists?(name: extra_group.name) || Ability.allowed?(current_user, :read_group, extra_group)) + groups |= [extra_group] + end users = [current_user.namespace] diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index a8f167cbff2..991fd949b94 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -31,7 +31,11 @@ module NavHelper end def layout_nav_class - "page-with-layout-nav" if defined?(nav) && nav + class_name = '' + class_name << " page-with-layout-nav" if defined?(nav) && nav + class_name << " page-with-sub-nav" if content_for?(:sub_nav) + + class_name end def nav_control_class diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 959ee310867..5c89cbea3fc 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -2,6 +2,7 @@ module SortingHelper def sort_options_hash { sort_value_name => sort_title_name, + sort_value_name_desc => sort_title_name_desc, sort_value_recently_updated => sort_title_recently_updated, sort_value_oldest_updated => sort_title_oldest_updated, sort_value_recently_created => sort_title_recently_created, @@ -50,6 +51,17 @@ module SortingHelper } end + def milestone_sort_options_hash + { + sort_value_name => sort_title_name_asc, + sort_value_name_desc => sort_title_name_desc, + sort_value_due_date_soon => sort_title_due_date_soon, + sort_value_due_date_later => sort_title_due_date_later, + sort_value_start_date_soon => sort_title_start_date_soon, + sort_value_start_date_later => sort_title_start_date_later, + } + end + def sort_title_priority 'Priority' end @@ -90,6 +102,14 @@ module SortingHelper 'Due later' end + def sort_title_start_date_soon + 'Start soon' + end + + def sort_title_start_date_later + 'Start later' + end + def sort_title_name 'Name' end @@ -202,6 +222,14 @@ module SortingHelper 'due_date_desc' end + def sort_value_start_date_soon + 'start_date_asc' + end + + def sort_value_start_date_later + 'start_date_desc' + end + def sort_value_name 'name_asc' end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 847a8fdfca6..4f5adf623f2 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -39,13 +39,9 @@ module TodosHelper namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project, todo.target, anchor: anchor) else - if todo.build_failed? - # associated namespace and route would be loaded from the db again if todo.project was used - project = todo.target.project - path = [:pipelines, project.namespace.becomes(Namespace), project, todo.target] - else - path = [todo.target] - end + path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target] + + path.unshift(:pipelines) if todo.build_failed? polymorphic_path(path, anchor: anchor) end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 00000000000..9c623c9ba7c --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,7 @@ +module UsersHelper + def user_link(user) + link_to(user.name, user_path(user), + title: user.email, + class: 'has-tooltip commit-committer-link') + end +end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index be632930895..671a0fe98cc 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -163,6 +163,8 @@ class ApplicationSetting < ActiveRecord::Base end def self.current + ensure_cache_setup + Rails.cache.fetch(CACHE_KEY) do ApplicationSetting.last end @@ -176,9 +178,16 @@ class ApplicationSetting < ActiveRecord::Base end def self.cached + ensure_cache_setup Rails.cache.fetch(CACHE_KEY) end + def self.ensure_cache_setup + # This is a workaround for a Rails bug that causes attribute methods not + # to be loaded when read from cache: https://github.com/rails/rails/issues/27348 + ApplicationSetting.define_attribute_methods + end + def self.defaults_ce { after_sign_up_text: nil, diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 5101cc7e687..0a1a65da05a 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -31,6 +31,7 @@ module HasStatus WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' WHEN (#{running})+(#{pending})>0 THEN 'running' WHEN (#{manual})>0 THEN 'manual' + WHEN (#{created})>0 THEN 'running' ELSE 'failed' END)" end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 91f4eb13ecc..4d54426b79e 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -48,11 +48,14 @@ module Issuable delegate :name, :email, + :public_email, to: :author, + allow_nil: true, prefix: true delegate :name, :email, + :public_email, to: :assignee, allow_nil: true, prefix: true diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 107e6764ba2..647a6cad3d7 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -41,7 +41,7 @@ module Spammable def check_for_spam error_msg = if Gitlab::Recaptcha.enabled? "Your #{spammable_entity_type} has been recognized as spam. "\ - "You can still submit it by solving Captcha." + "Please, change the content or solve the reCAPTCHA to proceed." else "Your #{spammable_entity_type} has been recognized as spam and has been discarded." end diff --git a/app/models/event.rb b/app/models/event.rb index d7ca8e3c599..5c34844b5d3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -16,7 +16,7 @@ class Event < ActiveRecord::Base RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour - delegate :name, :email, to: :author, prefix: true, allow_nil: true + delegate :name, :email, :public_email, to: :author, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :merge_request, prefix: true, allow_nil: true delegate :title, to: :note, prefix: true, allow_nil: true diff --git a/app/models/group.rb b/app/models/group.rb index bd0ecae3da4..60274386103 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -207,7 +207,7 @@ class Group < Namespace end def members_with_parents - GroupMember.non_request.where(source_id: ancestors.map(&:id).push(id)) + GroupMember.non_request.where(source_id: ancestors.pluck(:id).push(id)) end def users_with_parents diff --git a/app/models/issue.rb b/app/models/issue.rb index 602eed86d9e..10a5d9d2a24 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -211,9 +211,8 @@ class Issue < ActiveRecord::Base due_date.try(:past?) || false end - # Only issues on public projects should be checked for spam def check_for_spam? - project.public? + project.public? && (title_changed? || description_changed?) end def as_json(options = {}) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cef8ad76b07..5ff83944d8c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -7,7 +7,6 @@ class MergeRequest < ActiveRecord::Base belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" - belongs_to :project, foreign_key: :target_project_id belongs_to :merge_user, class_name: "User" has_many :merge_request_diffs, dependent: :destroy @@ -155,8 +154,10 @@ class MergeRequest < ActiveRecord::Base # # Returns an ActiveRecord::Relation. def self.in_projects(relation) - source = where(source_project_id: relation).select(:id) - target = where(target_project_id: relation).select(:id) + # unscoping unnecessary conditions that'll be applied + # when executing `where("merge_requests.id IN (#{union.to_sql})")` + source = unscoped.where(source_project_id: relation).select(:id) + target = unscoped.where(target_project_id: relation).select(:id) union = Gitlab::SQL::Union.new([source, target]) where("merge_requests.id IN (#{union.to_sql})") @@ -541,6 +542,10 @@ class MergeRequest < ActiveRecord::Base target_project != source_project end + def project + target_project + end + # If the merge request closes any issues, save this information in the # `MergeRequestsClosingIssues` model. This is a performance optimization. # Calculating this information for a number of merge requests requires diff --git a/app/models/milestone.rb b/app/models/milestone.rb index c0deb59ec4c..e85d5709624 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -107,6 +107,21 @@ class Milestone < ActiveRecord::Base end end + def self.sort(method) + case method.to_s + when 'due_date_asc' + reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) + when 'due_date_desc' + reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC')) + when 'start_date_asc' + reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC')) + when 'start_date_desc' + reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC')) + else + order_by(method) + end + end + ## # Returns the String necessary to reference this Milestone in Markdown # diff --git a/app/models/namespace.rb b/app/models/namespace.rb index d350f1d6770..826ded22ae5 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -195,7 +195,7 @@ class Namespace < ActiveRecord::Base # Scopes the model on direct and indirect children of the record def descendants - self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").reorder('routes.path ASC') + self.class.joins(:route).merge(Route.inside_path(route.path)).reorder('routes.path ASC') end def user_ids_for_project_authorizations diff --git a/app/models/project.rb b/app/models/project.rb index 17cf8226bcc..f1bba56d32c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -196,6 +196,7 @@ class Project < ActiveRecord::Base validates :name, uniqueness: { scope: :namespace_id } validates :path, uniqueness: { scope: :namespace_id } validates :import_url, addressable_url: true, if: :external_import? + validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?] validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, @@ -237,7 +238,7 @@ class Project < ActiveRecord::Base # We need routes alias rs for JOIN so it does not conflict with # includes(:route) which we use in ProjectsFinder. joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'"). - where('rs.path LIKE ?', "#{path}/%") + where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%") end # "enabled" here means "not disabled". It includes private features! @@ -313,20 +314,15 @@ class Project < ActiveRecord::Base ntable = Namespace.arel_table pattern = "%#{query}%" - projects = select(:id).where( + # unscoping unnecessary conditions that'll be applied + # when executing `where("projects.id IN (#{union.to_sql})")` + projects = unscoped.select(:id).where( ptable[:path].matches(pattern). or(ptable[:name].matches(pattern)). or(ptable[:description].matches(pattern)) ) - # We explicitly remove any eager loading clauses as they're: - # - # 1. Not needed by this query - # 2. Combined with .joins(:namespace) lead to all columns from the - # projects & namespaces tables being selected, leading to a SQL error - # due to the columns of all UNION'd queries no longer being the same. - namespaces = select(:id). - except(:includes). + namespaces = unscoped.select(:id). joins(:namespace). where(ntable[:name].matches(pattern)) @@ -880,13 +876,9 @@ class Project < ActiveRecord::Base end def http_url_to_repo(user = nil) - url = web_url + credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user) - if user - url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" } - end - - "#{url}.git" + Gitlab::UrlSanitizer.new("#{web_url}.git", credentials: credentials).full_url end # Check if current branch name is marked as protected in the system diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index 200be99f36b..75834103db5 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -6,7 +6,7 @@ class ChatNotificationService < Service default_value_for :category, 'chat' prop_accessor :webhook, :username, :channel - boolean_accessor :notify_only_broken_pipelines + boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch validates :webhook, presence: true, url: true, if: :activated? @@ -17,6 +17,7 @@ class ChatNotificationService < Service if properties.nil? self.properties = {} self.notify_only_broken_pipelines = true + self.notify_only_default_branch = true end end @@ -29,6 +30,19 @@ class ChatNotificationService < Service pipeline wiki_page] end + def fields + default_fields + build_event_channels + end + + def default_fields + [ + { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" }, + { type: 'text', name: 'username', placeholder: 'e.g. GitLab' }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' }, + { type: 'checkbox', name: 'notify_only_default_branch' }, + ] + end + def execute(data) return unless supported_events.include?(data[:object_kind]) return unless webhook.present? @@ -123,6 +137,17 @@ class ChatNotificationService < Service end def should_pipeline_be_notified?(data) + notify_for_ref?(data) && notify_for_pipeline?(data) + end + + def notify_for_ref?(data) + return true if data[:object_attributes][:tag] + return true unless notify_only_default_branch + + data[:object_attributes][:ref] == project.default_branch + end + + def notify_for_pipeline?(data) case data[:object_attributes][:status] when 'success' !notify_only_broken_pipelines? diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb index 1156d050622..0362ed172c7 100644 --- a/app/models/project_services/mattermost_service.rb +++ b/app/models/project_services/mattermost_service.rb @@ -22,19 +22,11 @@ class MattermostService < ChatNotificationService </ol>' end - def fields - default_fields + build_event_channels - end - - def default_fields - [ - { type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' }, - { type: 'text', name: 'username', placeholder: 'e.g. GitLab' }, - { type: 'checkbox', name: 'notify_only_broken_pipelines' }, - ] - end - def default_channel_placeholder "Channel handle (e.g. town-square)" end + + def webhook_placeholder + 'http://mattermost.example.com/hooks/…' + end end diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 375966b9efc..5cff9a42484 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -30,7 +30,14 @@ class PrometheusService < MonitoringService end def help - 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. An `environment` label is required on each metric to identify the Environment.' + <<-MD.strip_heredoc + Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total` + and `container_memory_usage_bytes` from the configured Prometheus server. + + If you are not using [Auto-Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) + or have set up your own Prometheus server, an `environment` label is required on each metric to + [identify the Environment](https://docs.gitlab.com/ce/user/project/integrations/prometheus.html#metrics-and-labels). + MD end def self.to_param @@ -67,16 +74,16 @@ class PrometheusService < MonitoringService def calculate_reactive_cache(environment_slug) return unless active? && project && !project.pending_delete? - memory_query = %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024} - cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))} + memory_query = %{(sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})) /1024/1024} + cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}) * 100} { success: true, metrics: { - # Memory used in MB + # Average Memory used in MB memory_values: client.query_range(memory_query, start: 8.hours.ago), memory_current: client.query(memory_query), - # CPU Usage rate in cores. + # Average CPU Utilization cpu_values: client.query_range(cpu_query, start: 8.hours.ago), cpu_current: client.query(cpu_query) }, diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index b657db6f9ee..71da0af75f6 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -21,19 +21,11 @@ class SlackService < ChatNotificationService </ol>' end - def fields - default_fields + build_event_channels - end - - def default_fields - [ - { type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' }, - { type: 'text', name: 'username', placeholder: 'e.g. GitLab' }, - { type: 'checkbox', name: 'notify_only_broken_pipelines' }, - ] - end - def default_channel_placeholder "Channel name (e.g. general)" end + + def webhook_placeholder + 'https://hooks.slack.com/services/…' + end end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 539b31780b3..70eef359cdd 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -42,8 +42,11 @@ class ProjectWiki url_to_repo end - def http_url_to_repo - [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') + def http_url_to_repo(user = nil) + url = "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git" + credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user) + + Gitlab::UrlSanitizer.new(url, credentials: credentials).full_url end def wiki_base_path diff --git a/app/models/route.rb b/app/models/route.rb index 73574a6206b..4b3efab5c3c 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -10,9 +10,11 @@ class Route < ActiveRecord::Base after_update :rename_descendants + scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") } + def rename_descendants if path_changed? || name_changed? - descendants = Route.where('path LIKE ?', "#{path_was}/%") + descendants = self.class.inside_path(path_was) descendants.each do |route| attributes = {} @@ -21,7 +23,7 @@ class Route < ActiveRecord::Base attributes[:path] = route.path.sub(path_was, path) end - if name_changed? && route.name.present? + if name_changed? && name_was.present? && route.name.present? attributes[:name] = route.name.sub(name_was, name) end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index dbd564e5e7d..30aca62499c 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -132,7 +132,8 @@ class Snippet < ActiveRecord::Base end def check_for_spam? - public? + visibility_level_changed?(to: Snippet::PUBLIC) || + (public? && (title_changed? || content_changed?)) end def spammable_entity_type diff --git a/app/models/user.rb b/app/models/user.rb index 8c7ad5d5174..5d19d873f43 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -324,6 +324,8 @@ class User < ActiveRecord::Base end def find_by_personal_access_token(token_string) + return unless token_string + PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 83f51947bd4..cb6d30396ec 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -3,7 +3,7 @@ module Boards class ListService < BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute - issues = without_board_labels(issues) unless movable_list? + issues = without_board_labels(issues) unless list issues = with_list_label(issues) if movable_list? issues.order_by_position_and_priority end diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index b07338d500a..673ed02f952 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -25,12 +25,12 @@ class CreateBranchService < BaseService private def create_master_branch - project.repository.commit_file( + project.repository.create_file( current_user, '/README.md', '', message: 'Add README.md', - branch_name: 'master', - update: false) + branch_name: 'master' + ) end end diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 1c5a549feb9..d484a96f785 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -33,6 +33,7 @@ module Projects def import_repository begin + raise Error, "Blocked import URL." if Gitlab::UrlBlocker.blocked_url?(project.import_url) gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) rescue => e # Expire cache to prevent scenarios such as: @@ -40,7 +41,7 @@ module Projects # 2. Retried import, repo is broken or not imported but +exists?+ still returns true project.repository.before_import if project.repository_exists? - raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" + raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end end diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb index 023e0824e85..11030bee8f1 100644 --- a/app/services/spam_check_service.rb +++ b/app/services/spam_check_service.rb @@ -14,6 +14,9 @@ module SpamCheckService @spam_log_id = params.delete(:spam_log_id) end + # In order to be proceed to the spam check process, @spammable has to be + # a dirty instance, which means it should be already assigned with the new + # attribute values. def spam_check(spammable, user) spam_service = SpamService.new(spammable, @request) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 868fa7b3f21..af0ddbe5934 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -24,10 +24,9 @@ class SystemHooksService key: model.key, id: model.id ) + if model.user - data.merge!( - username: model.user.username - ) + data[:username] = model.user.username end when Project data.merge!(project_data(model)) @@ -35,8 +34,6 @@ class SystemHooksService if event == :rename || event == :transfer data[:old_path_with_namespace] = model.old_path_with_namespace end - - data when User data.merge!({ name: model.name, @@ -59,6 +56,8 @@ class SystemHooksService when GroupMember data.merge!(group_member_data(model)) end + + data end def build_event_name(model, event) diff --git a/app/validators/importable_url_validator.rb b/app/validators/importable_url_validator.rb new file mode 100644 index 00000000000..37a314adee6 --- /dev/null +++ b/app/validators/importable_url_validator.rb @@ -0,0 +1,11 @@ +# ImportableUrlValidator +# +# This validator blocks projects from using dangerous import_urls to help +# protect against Server-side Request Forgery (SSRF). +class ImportableUrlValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if Gitlab::UrlBlocker.blocked_url?(value) + record.errors.add(attribute, "imports are not allowed from that URL") + end + end +end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 9175b3d3f96..e403a9da616 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -48,7 +48,7 @@ .form-actions = f.submit 'Save', class: 'btn btn-save append-right-10' - if @appearance.persisted? - = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' + = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' - if @appearance.updated_at %span.pull-right diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 00366b0a8c9..3eab065bb9f 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -404,7 +404,7 @@ Enable Sentry .help-block Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: - %a{ href: 'https://getsentry.com', target: '_blank' } https://getsentry.com + %a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com .form-group = f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2' diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index e67ad663720..ebca9beb035 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -43,28 +43,34 @@ %h4 Features %hr - %p - Sign up + - sign_up = "Sign up" + %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") } + = sign_up %span.light.pull-right = boolean_to_icon signup_enabled? - %p - LDAP + - ldap = "LDAP" + %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") } + = ldap %span.light.pull-right = boolean_to_icon Gitlab.config.ldap.enabled - %p - Gravatar + - gravatar = "Gravatar" + %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") } + = gravatar %span.light.pull-right = boolean_to_icon gravatar_enabled? - %p - OmniAuth + - omniauth = "OmniAuth" + %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") } + = omniauth %span.light.pull-right = boolean_to_icon Gitlab.config.omniauth.enabled - %p - Reply by email + - reply_email = "Reply by email" + %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") } + = reply_email %span.light.pull-right = boolean_to_icon Gitlab::IncomingEmail.enabled? - %p - Container Registry + - container_reg = "Container Registry" + %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") } + = container_reg %span.light.pull-right = boolean_to_icon Gitlab.config.registry.enabled diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml index a1ef34dc588..5aae410a63f 100644 --- a/app/views/award_emoji/_awards_block.html.haml +++ b/app/views/award_emoji/_awards_block.html.haml @@ -10,8 +10,8 @@ - if current_user .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{ type: "button" } + %button.btn.award-control.has-tooltip.js-add-award{ type: 'button', + 'aria-label': 'Add emoji', + data: { title: 'Add emoji', placement: "bottom" } } = icon('smile-o', class: "award-control-icon award-control-icon-normal") = icon('spinner spin', class: "award-control-icon award-control-icon-loading") - %span.award-control-text - Add diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml index 0530d21a7e2..128b418090f 100644 --- a/app/views/ci/status/_graph_badge.html.haml +++ b/app/views/ci/status/_graph_badge.html.haml @@ -6,7 +6,7 @@ - tooltip = "#{subject.name} - #{status.label}" - if status.has_details? - = link_to status.details_path, class: 'build-content has-tooltip', data: { toggle: 'tooltip', title: tooltip } do + = link_to status.details_path, class: 'build-content has-tooltip', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do %span{ class: klass }= custom_icon(status.icon) .ci-status-text= subject.name - else @@ -15,6 +15,6 @@ .ci-status-text= subject.name - if status.has_action? - = link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do + = link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do %i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" } = custom_icon(status.action_icon) diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index d31ced004a0..e31fa5fbe95 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -19,12 +19,13 @@ .nav-controls - if @todos.any?(&:pending?) - = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do - Mark all as done - = icon('spinner spin') - = link_to bulk_restore_dashboard_todos_path, class: 'btn btn-loading js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do - Undo mark all as done - = icon('spinner spin') + .append-right-default + = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn btn-loading js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do + Mark all as done + = icon('spinner spin') + = link_to bulk_restore_dashboard_todos_path, class: 'btn btn-loading js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do + Undo mark all as done + = icon('spinner spin') .todos-filters .row-content-block.second-block diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 5d359538efe..21c751a23f8 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -8,7 +8,7 @@ - if devise_mapping.rememberable? .remember-me.checkbox %label{ for: "user_remember_me" } - = f.check_box :remember_me + = f.check_box :remember_me, class: 'remember-me-checkbox' %span Remember me .pull-right.forgot-password = link_to "Forgot your password?", new_password_path(resource_name) diff --git a/app/views/events/_event.atom.builder b/app/views/events/_event.atom.builder index 43a52cf3002..158061579f6 100644 --- a/app/views/events/_event.atom.builder +++ b/app/views/events/_event.atom.builder @@ -9,7 +9,7 @@ xml.entry do xml.author do xml.name event.author_name - xml.email event.author_email + xml.email event.author_public_email end xml.summary(type: "xhtml") do |summary| diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index f08c96df309..64b5a733b77 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -15,6 +15,6 @@ = link_to note.attachment.url, target: '_blank' do = image_tag note.attachment.url, class: 'note-image-attach' - else - = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do + = link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do %i.fa.fa-paperclip = note.attachment_identifier diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 2684f16c373..8e6da3fad90 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -118,6 +118,12 @@ .key m %td Go to merge requests + %tr + %td.shortcut + .key g + .key t + %td + Go to todos %tbody %tr %th diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 31631887317..f93b6b63426 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -17,7 +17,7 @@ %br Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br - Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. + Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}. - if current_application_settings.help_page_text.present? %hr = markdown_field(current_application_settings, :help_page_text) diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index e18bd47798b..e6058617ac9 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -33,7 +33,7 @@ - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td - = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank' + = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer' %td = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status @@ -50,7 +50,7 @@ - @repos.each do |repo| %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %td - = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: "_blank" + = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %fieldset.row .input-group @@ -70,7 +70,7 @@ - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %td - = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank' + = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status = label_tag 'Incompatible Project', nil, class: 'label label-danger' diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index d5b88709a34..7456799ca0e 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -43,7 +43,7 @@ - @repos.each do |repo| %tr{ id: "repo_#{repo["id"]}" } %td - = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" + = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer' %td.import-target = import_project_target(repo['namespace']['path'], repo['name']) %td.import-actions.job-status diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index 336becd229e..c5800a1cca0 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -13,7 +13,7 @@ %li %p Go to - #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: "_blank"}. + #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer'}. %li %p Make sure you're logged into the account that owns the projects you'd like to import. diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 5e01af008be..60de6bfe816 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -36,7 +36,7 @@ - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td - = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" + = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank", rel: 'noopener noreferrer' %td = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status @@ -53,7 +53,7 @@ - @repos.each do |repo| %tr{ id: "repo_#{repo.id}" } %td - = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" + = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target #{current_user.username}/#{repo.name} %td.import-actions.job-status @@ -63,7 +63,7 @@ - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.id}" } %td - = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" + = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status = label_tag "Incompatible Project", nil, class: "label label-danger" diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder index fcd30c8c765..23a88448055 100644 --- a/app/views/issues/_issue.atom.builder +++ b/app/views/issues/_issue.atom.builder @@ -7,7 +7,7 @@ xml.entry do xml.author do xml.name issue.author_name - xml.email issue.author_email + xml.email issue.author_public_email end xml.summary issue.title @@ -26,7 +26,7 @@ xml.entry do if issue.assignee xml.assignee do xml.name issue.assignee.name - xml.email issue.assignee.email + xml.email issue.assignee_public_email end end end diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml index 65887aacbaf..04e2d4b63e6 100644 --- a/app/views/koding/index.html.haml +++ b/app/views/koding/index.html.haml @@ -2,5 +2,5 @@ %p = icon('circle', class: 'cgreen') Integration is active for - = link_to koding_project_url, target: '_blank' do + = link_to koding_project_url, target: '_blank', rel: 'noopener noreferrer' do #{current_application_settings.koding_url} diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index a35a918d501..b7df11681d3 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -3,8 +3,9 @@ .layout-nav .container-fluid = render "layouts/nav/#{nav}" - .content-wrapper{ class: "#{layout_nav_class}" } + - if content_for?(:sub_nav) = yield :sub_nav + .content-wrapper{ class: layout_nav_class } .alert-wrapper = render "layouts/broadcast" = render "layouts/flash" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 5fde5c2613e..7ddee0e5244 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -32,7 +32,7 @@ = link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('wrench fw') %li - = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('bell fw') %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } = todos_count_format(todos_pending_count) diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index df0a0212f3d..99690e6b98a 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -6,7 +6,9 @@ %h4.prepend-top-0 Syntax highlighting theme %p - This setting allow you to customize the appearance of the syntax. + This setting allows you to customize the appearance of the syntax. + = succeed '.' do + = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank' .col-lg-9.syntax-theme - Gitlab::ColorSchemes.each do |scheme| = label_tag do @@ -20,6 +22,8 @@ Behavior %p This setting allows you to customize the behavior of the system layout and default views. + = succeed '.' do + = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank' .col-lg-9 .form-group = f.label :layout, class: 'label-light' do @@ -29,13 +33,11 @@ Choose between fixed (max. 1200px) and fluid (100%) application layout. .form-group = f.label :dashboard, class: 'label-light' do - Default Dashboard - = link_to('(?)', help_page_path('profile/preferences') + '#default-dashboard', target: '_blank') + Default dashboard = f.select :dashboard, dashboard_choices, {}, class: 'form-control' .form-group = f.label :project_view, class: 'label-light' do Project view - = link_to('(?)', help_page_path('profile/preferences') + '#default-project-view', target: '_blank') = f.select :project_view, project_view_choices, {}, class: 'form-control' .help-block Choose what content you want to see on a project's home page. diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index d551754a2e5..c74b3249a13 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -18,7 +18,7 @@ or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} .col-lg-9 .clearfix.avatar-image.append-bottom-default - = link_to avatar_icon(@user, 400), target: '_blank' do + = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' %h5.prepend-top-0 Upload new avatar diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index f864702d862..ea3cecb86a9 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -9,7 +9,7 @@ - else .nothing-here-block The SVG could not be displayed as it is too large, you can - #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')} + #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer')} instead. - else %img{ src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path)), alt: "#{blob.name}" } diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index b1e1be49de9..7b16d266982 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -3,7 +3,7 @@ .nothing-here-block File too large, you can = succeed '.' do - = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank' + = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer' - else - blob.load_all_data!(@repository) diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 4924c73cf8e..e14885f264b 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -5,7 +5,7 @@ %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title= title .modal-body - = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do + = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal', data: { method: method } do .dropzone .dropzone-previews.blob-upload-dropzone-previews %p.dz-message.light @@ -24,8 +24,5 @@ .inline.prepend-left-10 = commit_in_fork_help - -:javascript - gl.utils.disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file'); - new BlobFileDropzone($('.js-upload-blob-form'), '#{method}'); - new NewCommitForm($('.js-upload-blob-form')) +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('blob') diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 8853801016b..afe0b5dba45 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -2,14 +2,14 @@ - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') - = page_specific_javascript_bundle_tag('blob_edit') + = page_specific_javascript_bundle_tag('blob') = render "projects/commits/head" %div{ class: container_class } - if @conflict .alert.alert-danger Someone edited the file the same time you did. Please check out - = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" + = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs. .file-editor diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index e0ce8cc9601..4c449e040ee 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,7 +1,7 @@ - page_title "New File", @path.presence, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') - = page_specific_javascript_bundle_tag('blob_edit') + = page_specific_javascript_bundle_tag('blob') %h3.page-title New File diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 78720d88e4e..b597c7f7a12 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -1,6 +1,6 @@ - builds = @build.pipeline.builds.to_a -%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "151", "spy" => "affix" } } +%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "153", "spy" => "affix" } } .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default Job %strong ##{@build.id} @@ -137,3 +137,6 @@ = build.id - if build.retried? %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' } + +:javascript + new Sidebar(); diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 307010edb58..d5fe771613c 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,6 +1,6 @@ - @no_container = true - page_title "#{@build.name} (##{@build.id})", "Jobs" -= render "projects/pipelines/head", build_subnav: true += render "projects/pipelines/head" %div{ class: container_class } .build-page diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml index 5d9a776da89..a5a9e4d0621 100644 --- a/app/views/projects/buttons/_koding.html.haml +++ b/app/views/projects/buttons/_koding.html.haml @@ -1,3 +1,3 @@ - if koding_enabled? && current_user && @repository.koding_yml && can_push_branch?(@project, @project.default_branch) - = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do + = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank', rel: 'noopener noreferrer' do Run in IDE (Koding) diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index da5a676274f..09e3a775d1c 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -1,6 +1,7 @@ - disable_initialization = local_assigns.fetch(:disable_initialization, false) #commit-pipeline-table-view{ data: { disable_initialization: disable_initialization, endpoint: endpoint, + "help-page-path" => help_page_path('ci/quick_start/README'), } } - content_for :page_specific_javascripts do diff --git a/app/views/projects/cycle_analytics/_overview.html.haml b/app/views/projects/cycle_analytics/_overview.html.haml index c8f0b547f80..9007f2c24ba 100644 --- a/app/views/projects/cycle_analytics/_overview.html.haml +++ b/app/views/projects/cycle_analytics/_overview.html.haml @@ -9,7 +9,7 @@ Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project. To set up CA, you must first define a production environment by setting up your CI and then deploy to production. %p - %a.btn{ href: help_page_path('user/project/cycle_analytics'), target: "_blank" } Read more + %a.btn{ href: help_page_path('user/project/cycle_analytics'), target: '_blank' } Read more .col-md-6.overview-image %span.overview-icon = custom_icon ('icon_cycle_analytics_overview') diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 2802a4eca7b..82e0d0025ec 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -31,7 +31,7 @@ = f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'}) .form-group = f.label :tag_list, "Tags", class: 'label-light' - = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control" + = f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control" %p.help-block Separate tags with commas. %hr %fieldset diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml index 4c8fe1c271b..bf0f1819073 100644 --- a/app/views/projects/environments/_external_url.html.haml +++ b/app/views/projects/environments/_external_url.html.haml @@ -1,3 +1,3 @@ - if environment.external_url && can?(current_user, :read_environment, environment) - = link_to environment.external_url, target: '_blank', class: 'btn external-url' do + = link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url' do = icon('external-link') diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index b8c1782f050..3b45162df52 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -18,7 +18,11 @@ = render 'projects/deployments/actions', deployment: @environment.last_deployment .row .col-sm-12 + %h4 + CPU utilization %svg.prometheus-graph{ 'graph-type' => 'cpu_values' } .row .col-sm-12 + %h4 + Memory usage %svg.prometheus-graph{ 'graph-type' => 'memory_values' } diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index ef0dd0eda3c..c8363087d6a 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -16,7 +16,7 @@ .col-sm-6 .nav-controls - = link_to @environment.external_url, class: 'btn btn-default' do + = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do = icon('external-link') = render 'projects/deployments/actions', deployment: @environment.last_deployment diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index ad14b4e583e..8d134aaac67 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -21,7 +21,7 @@ selected: f.object.source_project_id .merge-request-select.dropdown = f.hidden_field :source_branch - = dropdown_toggle local_assigns.fetch(f.object.source_branch, "Select source branch"), { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } + = dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch = dropdown_title("Select source branch") = dropdown_filter("Search branches") diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index c8f097c69da..6682a85ffa6 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -16,7 +16,7 @@ .pull-right - if @merge_request.source_branch_exists? - if koding_enabled? && @repository.koding_yml - = link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank' do + = link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank', rel: 'noopener noreferrer' do Run in IDE (Koding) = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do Check out branch diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml index f0a23bec5e7..e632fc681cf 100644 --- a/app/views/projects/merge_requests/merge.js.haml +++ b/app/views/projects/merge_requests/merge.js.haml @@ -1,7 +1,8 @@ - case @status - when :success + - remove_source_branch = params[:should_remove_source_branch] == '1' || @merge_request.remove_source_branch? :plain - merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'}); + merge_request_widget.mergeInProgress(#{remove_source_branch}); - when :merge_when_pipeline_succeeds :plain $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_pipeline_succeeds'))}"); diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 93ed4b68e0e..cde0ce08e14 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -49,7 +49,7 @@ %strong Tip: = succeed '.' do You can also checkout merge requests locally by - = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank' + = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer' :javascript $(function(){ diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 918f5d161bb..b6340a00b29 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -7,6 +7,7 @@ = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) .nav-controls + = render 'shared/milestones_sort_dropdown' - if can?(current_user, :admin_milestone, @project) = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New Milestone' do New Milestone diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index d129da943f8..34a1214a350 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -23,7 +23,7 @@ - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1} - else .input-group-addon.static-namespace diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5552086bc50..6c0e6d48d6c 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -37,7 +37,7 @@ ":can-resolve" => can_resolve, ":author-name" => "'#{j(note.author.name)}'", "author-avatar" => note.author.avatar_url, - ":note-truncated" => "'#{truncate(note.note, length: 17)}'", + ":note-truncated" => "'#{j(truncate(note.note, length: 17))}'", ":resolved-by" => "'#{j(note.resolved_by.try(:name))}'", "v-show" => "#{can_resolve || note.resolved?}", "inline-template" => true, diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index a5acb7ac4a5..b02fef638ff 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,7 +1,7 @@ = content_for :sub_nav do .scrolling-tabs-container.sub-nav-scroll = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) } + .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } - if project_nav_tab? :pipelines = nav_link(path: 'pipelines#index', controller: :pipelines) do diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 0605af4fcd3..4be9a1371ec 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,10 +1,12 @@ .page-content-header .header-main-content = render 'ci/status/badge', status: @pipeline.detailed_status(current_user) - %strong Pipeline ##{@commit.pipelines.last.id} - triggered #{time_ago_with_tooltip(@commit.authored_date)} by - = author_avatar(@commit, size: 24) - = commit_author_link(@commit) + %strong Pipeline ##{@pipeline.id} + triggered #{time_ago_with_tooltip(@pipeline.created_at)} + - if @pipeline.user + by + = user_avatar(user: @pipeline.user, size: 24) + = user_link(@pipeline.user) .header-action-buttons - if can?(current_user, :update_pipeline, @pipeline.project) - if @pipeline.retryable? diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 5d59ce06612..3d73284699f 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -2,53 +2,19 @@ - page_title "Pipelines" = render "projects/pipelines/head" -%div{ class: container_class } - .top-area - %ul.nav-links - %li.js-pipelines-tab-all{ class: active_when(@scope.nil?) }> - = link_to project_pipelines_path(@project) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@pipelines_count) - - %li.js-pipelines-tab-pending{ class: active_when(@scope == 'pending') }> - = link_to project_pipelines_path(@project, scope: :pending) do - Pending - %span.badge - = number_with_delimiter(@pending_count) - - %li.js-pipelines-tab-running{ class: active_when(@scope == 'running') }> - = link_to project_pipelines_path(@project, scope: :running) do - Running - %span.badge.js-running-count - = number_with_delimiter(@running_count) - - %li.js-pipelines-tab-finished{ class: active_when(@scope == 'finished') }> - = link_to project_pipelines_path(@project, scope: :finished) do - Finished - %span.badge - = number_with_delimiter(@finished_count) - - %li.js-pipelines-tab-branches{ class: active_when(@scope == 'branches') }> - = link_to project_pipelines_path(@project, scope: :branches) do - Branches - - %li.js-pipelines-tab-tags{ class: active_when(@scope == 'tags') }> - = link_to project_pipelines_path(@project, scope: :tags) do - Tags - - .nav-controls - - if can? current_user, :create_pipeline, @project - = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do - Run pipeline - - - unless @repository.gitlab_ci_yml - = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' - - = link_to ci_lint_path, class: 'btn btn-default' do - %span CI Lint - .content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } } - .vue-pipelines-index +#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json), + "css-class" => container_class, + "help-page-path" => help_page_path('ci/quick_start/README'), + "new-pipeline-path" => new_namespace_project_pipeline_path(@project.namespace, @project), + "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, + "all-path" => project_pipelines_path(@project), + "pending-path" => project_pipelines_path(@project, scope: :pending), + "running-path" => project_pipelines_path(@project, scope: :running), + "finished-path" => project_pipelines_path(@project, scope: :finished), + "branches-path" => project_pipelines_path(@project, scope: :branches), + "tags-path" => project_pipelines_path(@project, scope: :tags), + "has-ci" => @repository.gitlab_ci_yml, + "ci-lint-path" => ci_lint_path } } = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('vue_pipelines') diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml index 964133504e6..86d5a0ec7b8 100644 --- a/app/views/projects/services/_index.html.haml +++ b/app/views/projects/services/_index.html.haml @@ -18,7 +18,7 @@ %th Last edit - @services.sort_by(&:title).each do |service| %tr - %td + %td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") } = boolean_to_icon service.activated? %td = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index 3a323d94cc2..2fb88297fb3 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -4,13 +4,13 @@ %ul.list-unstyled.indent-list %li 1. - = link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noreferrer noopener nofollow' do + = link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noopener noreferrer nofollow' do Enable custom slash commands = icon('external-link') on your Mattermost installation %li 2. - = link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noreferrer noopener nofollow' do + = link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noopener noreferrer nofollow' do Add a slash command = icon('external-link') in your Mattermost team with these options: diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml index a04fd5035a6..2a1b9d4c465 100644 --- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml @@ -4,7 +4,7 @@ %p This service allows users to perform common operations on this project by entering slash commands in Mattermost. - = link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank', ref: 'noreferrer nofollow noopener' do + = link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do View documentation = icon('external-link') %p.inline diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index 0d973a20d4c..078b7be6865 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -5,7 +5,7 @@ %p This service allows users to perform common operations on this project by entering slash commands in Slack. - = link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank', ref: 'noreferrer nofollow noopener' do + = link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do View documentation = icon('external-link') %p.inline @@ -57,7 +57,7 @@ = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.text-block = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36) - = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank') + = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer') .form-group = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' diff --git a/app/views/projects/stage/_in_stage_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml index 9c5eb501174..671a3ef481c 100644 --- a/app/views/projects/stage/_in_stage_group.html.haml +++ b/app/views/projects/stage/_in_stage_group.html.haml @@ -1,5 +1,5 @@ - group_status = CommitStatus.where(id: subject).status -%button.dropdown-menu-toggle.build-content.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}" } } +%button.dropdown-menu-toggle.build-content.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}", container: 'body' } } %span{ class: "ci-status-icon ci-status-icon-#{group_status}" } = ci_icon_for_status(group_status) %span.ci-status-text diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml index 8c582f747b3..713b758727e 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -1,4 +1,4 @@ -%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } } +%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "102", "spy" => "affix" } } .block.wiki-sidebar-header.append-bottom-default %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } = icon('angle-double-right') @@ -19,3 +19,6 @@ More Pages = render 'projects/wikis/new' + +:javascript + new Sidebar(); diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index 57a0eaa919e..db2ac1e1d12 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -4,10 +4,10 @@ Open %span.badge= counts[:opened] %li{ class: milestone_class_for_state(params[:state], 'closed') }> - = link_to milestones_filter_path(state: 'closed') do + = link_to milestones_filter_path(state: 'closed', sort: 'due_date_desc') do Closed %span.badge= counts[:closed] %li{ class: milestone_class_for_state(params[:state], 'all') }> - = link_to milestones_filter_path(state: 'all') do + = link_to milestones_filter_path(state: 'all', sort: 'due_date_desc') do All %span.badge= counts[:all] diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml new file mode 100644 index 00000000000..9b2f2fdcc93 --- /dev/null +++ b/app/views/shared/_milestones_sort_dropdown.html.haml @@ -0,0 +1,22 @@ +.dropdown.inline.prepend-left-10 + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %span.light + - if @sort.present? + = milestone_sort_options_hash[@sort] + - else + = sort_title_due_date_soon + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort + %li + = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do + = sort_title_due_date_soon + = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do + = sort_title_due_date_later + = link_to page_filter_path(sort: sort_value_start_date_soon, label: true) do + = sort_title_start_date_soon + = link_to page_filter_path(sort: sort_value_start_date_later, label: true) do + = sort_title_start_date_later + = link_to page_filter_path(sort: sort_value_name, label: true) do + = sort_title_name_asc + = link_to page_filter_path(sort: sort_value_name_desc, label: true) do + = sort_title_name_desc diff --git a/app/views/shared/empty_states/icons/_pipelines_empty.svg b/app/views/shared/empty_states/icons/_pipelines_empty.svg new file mode 100644 index 00000000000..8119d5bebe0 --- /dev/null +++ b/app/views/shared/empty_states/icons/_pipelines_empty.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 150"><g fill="none" fill-rule="evenodd" transform="translate(0-3)"><g transform="translate(0 105)"><g fill="#e5e5e5"><rect width="78" height="4" x="34" y="21" opacity=".5" rx="2"/><path d="m152 23c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4c-1.104 0-1.998-.888-1.998-2m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4c-1.104 0-1.998-.888-1.998-2m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4c-1.104 0-1.998-.888-1.998-2m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4c-1.104 0-1.998-.888-1.998-2m14 0c0-1.105.887-2 1.998-2h4c1.104 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4c-1.104 0-1.998-.888-1.998-2"/></g><g transform="translate(0 4)"><path fill="#98d7b2" fill-rule="nonzero" d="m19 38c-10.493 0-19-8.507-19-19 0-10.493 8.507-19 19-19 10.493 0 19 8.507 19 19 0 10.493-8.507 19-19 19m0-4c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15"/><path fill="#31af64" d="m17.07 21.02l-2.829-2.829c-.786-.786-2.047-.781-2.828 0-.786.786-.781 2.047 0 2.828l4.243 4.243c.392.392.902.587 1.412.588.512.002 1.021-.193 1.41-.582l7.79-7.79c.777-.777.775-2.042-.006-2.823-.786-.786-2.045-.784-2.823-.006l-6.37 6.37"/></g><g fill="#e52c5a" transform="translate(102)"><path fill-rule="nonzero" d="m24 47.5c-12.979 0-23.5-10.521-23.5-23.5 0-12.979 10.521-23.5 23.5-23.5 12.979 0 23.5 10.521 23.5 23.5 0 12.979-10.521 23.5-23.5 23.5m0-5c10.217 0 18.5-8.283 18.5-18.5 0-10.217-8.283-18.5-18.5-18.5-10.217 0-18.5 8.283-18.5 18.5 0 10.217 8.283 18.5 18.5 18.5"/><path d="m28.24 24l2.833-2.833c1.167-1.167 1.167-3.067-.004-4.239-1.169-1.169-3.069-1.173-4.239-.004l-2.833 2.833-2.833-2.833c-1.167-1.167-3.067-1.167-4.239.004-1.169 1.169-1.173 3.069-.004 4.239l2.833 2.833-2.833 2.833c-1.167 1.167-1.167 3.067.004 4.239 1.169 1.169 3.069 1.173 4.239.004l2.833-2.833 2.833 2.833c1.167 1.167 3.067 1.167 4.239-.004 1.169-1.169 1.173-3.069.004-4.239l-2.833-2.833"/></g><path fill="#e5e5e5" fill-rule="nonzero" d="m236 37c-7.732 0-14-6.268-14-14 0-7.732 6.268-14 14-14 7.732 0 14 6.268 14 14 0 7.732-6.268 14-14 14m0-4c5.523 0 10-4.477 10-10 0-5.523-4.477-10-10-10-5.523 0-10 4.477-10 10 0 5.523 4.477 10 10 10"/></g><g transform="translate(69 3)"><path fill="#e5e5e5" fill-rule="nonzero" d="m4 11.99v60.02c0 4.413 3.583 7.99 8 7.99h89.991c4.419 0 8-3.579 8-7.99v-60.02c0-4.413-3.583-7.99-8-7.99h-89.991c-4.419 0-8 3.579-8 7.99m-4 0c0-6.622 5.378-11.99 12-11.99h89.991c6.629 0 12 5.367 12 11.99v60.02c0 6.622-5.378 11.99-12 11.99h-89.991c-6.629 0-12-5.367-12-11.99v-60.02m52.874 80.3l-13.253-15.292h34.76l-13.253 15.292c-2.237 2.582-6.01 2.585-8.253 0m3.02-2.62c.644.743 1.564.743 2.207 0l7.516-8.673h-17.24l7.516 8.673"/><rect width="18" height="6" x="15" y="23" fill="#fc8a51" rx="3"/><rect width="18" height="6" x="39" y="39" fill="#e52c5a" rx="3"/><rect width="18" height="6" x="33" y="55" fill="#e5e5e5" rx="3"/><rect width="12" height="6" x="39" y="23" fill="#fde5d8" rx="3"/><rect width="12" height="6" x="57" y="55" fill="#e52c5a" rx="3"/><rect width="12" height="6" x="15" y="55" fill="#b5a7dd" rx="3"/><rect width="18" height="6" x="81" y="23" fill="#fc8a51" rx="3"/><rect width="18" height="6" x="15" y="39" fill="#fde5d8" rx="3"/><rect width="6" height="6" x="57" y="23" fill="#e52c5a" rx="3"/><g fill="#fde5d8"><rect width="6" height="6" x="69" y="23" rx="3"/><rect width="6" height="6" x="75" y="39" rx="3"/></g><rect width="6" height="6" x="63" y="39" fill="#e52c5a" rx="3"/></g><g transform="matrix(.70711-.70711.70711.70711 84.34 52.5)"><path fill="#6b4fbb" fill-rule="nonzero" d="m28.02 67.48c-15.927-2.825-28.02-16.738-28.02-33.476 0-18.778 15.222-34 34-34 18.778 0 34 15.222 34 34 0 16.738-12.1 30.652-28.02 33.476.015.173.023.347.023.524v21.999c0 3.314-2.693 6-6 6-3.314 0-6-2.682-6-6v-21.999c0-.177.008-.351.023-.524m5.977-7.476c14.359 0 26-11.641 26-26 0-14.359-11.641-26-26-26-14.359 0-26 11.641-26 26 0 14.359 11.641 26 26 26"/><path fill="#fff" fill-opacity=".3" stroke="#6b4fbb" stroke-width="8" d="m31 71c16.569 0 30-13.431 30-30 0-16.569-13.431-30-30-30" transform="matrix(.86603.5-.5.86603 26.663-17.507)"/></g></g></svg>
\ No newline at end of file diff --git a/app/views/shared/empty_states/icons/_pipelines_failed.svg b/app/views/shared/empty_states/icons/_pipelines_failed.svg new file mode 100644 index 00000000000..7dbabf7e4ef --- /dev/null +++ b/app/views/shared/empty_states/icons/_pipelines_failed.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446 249" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="0" d="m260.03 114h23.972v-.013c19.972-.53 36-16.887 36-36.987 0-20.435-16.565-37-37-37-.993 0-1.977.039-2.95.116-4.95-14.605-18.773-25.12-35.05-25.12-5.464 0-10.652 1.185-15.32 3.311-6.649-9.841-17.909-16.311-30.68-16.311-20.435 0-37 16.565-37 37 0 .701.019 1.397.058 2.088-16.11 3.999-28.06 18.561-28.06 35.912 0 20.435 16.565 37 37 37 .324 0 .646-.004.968-.012"/><ellipse id="2" cx="41" cy="41" rx="41" ry="41"/><mask id="1" width="186" height="112" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask><mask id="3" width="82" height="82" x="0" y="0" fill="#fff"><use xlink:href="#2"/></mask></defs><g fill="none" fill-rule="evenodd"><g transform="matrix(.86603.5-.5.86603 228.11 137.43)"><path stroke="#b5a7dd" stroke-width="4" d="m.445.161c15.89 10.636 34.998 16.839 55.55 16.839"/><g transform="translate(56 4)"><path fill="#fb722e" d="m16 8c0-1.105.902-2 2.01-2h7.983c1.109 0 2.01.888 2.01 2 0 1.105-.902 2-2.01 2h-7.983c-1.109 0-2.01-.888-2.01-2m0 10c0-1.105.902-2 2.01-2h7.983c1.109 0 2.01.888 2.01 2 0 1.105-.902 2-2.01 2h-7.983c-1.109 0-2.01-.888-2.01-2"/><path fill="#fde5d8" fill-rule="nonzero" d="m4 22h6c3.315 0 6-2.685 6-5.997v-6.01c0-3.315-2.684-5.997-6-5.997h-6v18m-4-18.992c0-1.661 1.343-3.01 2.994-3.01h7.01c5.523 0 10 4.47 10 9.997v6.01c0 5.521-4.476 9.997-10 9.997h-7.01c-1.654 0-2.994-1.343-2.994-3.01v-19.984"/></g></g><g fill-rule="nonzero" transform="translate(257)"><path fill="#e5e5e5" d="m3.597 18.747c5.611-9.09 15.519-14.747 26.403-14.747 17.12 0 31 13.879 31 31 0 7.02-2.34 13.685-6.58 19.1l3.149 2.466c4.786-6.111 7.431-13.639 7.431-21.565 0-19.33-15.67-35-35-35-12.286 0-23.476 6.384-29.808 16.647l3.404 2.1"/><g transform="matrix(.96593.25882-.25882.96593 15.98 9.578)"><path fill="#b5a7dd" d="m12.426 11.592l-2.142 1.768-3.664-2.116c-.186-.107-.43-.042-.543.154l-1.229 2.129c-.116.2-.052.438.138.547l3.658 2.112-.455 2.735c-.109.657-.165 1.327-.165 2.01 0 .678.055 1.348.165 2.01l.455 2.735-3.658 2.112c-.186.107-.251.351-.138.547l1.229 2.129c.116.2.353.264.543.154l3.664-2.116 2.142 1.768c1.036.855 2.205 1.533 3.462 2l2.6.972v4.225c0 .215.179.393.405.393h2.458c.231 0 .405-.174.405-.393v-4.225l2.6-.972c1.257-.47 2.426-1.147 3.462-2l2.142-1.768 3.664 2.116c.186.107.43.042.543-.154l1.229-2.129c.116-.2.052-.438-.138-.547l-3.658-2.112.455-2.735c.109-.657.165-1.327.165-2.01 0-.678-.055-1.348-.165-2.01l-.455-2.735 3.658-2.112c.186-.107.251-.351.138-.547l-1.229-2.129c-.116-.2-.353-.264-.543-.154l-3.664 2.116-2.142-1.768c-1.036-.855-2.205-1.533-3.462-2l-2.6-.972v-4.225c0-.215-.179-.393-.405-.393h-2.458c-.231 0-.405.174-.405.393v4.225l-2.6.972c-1.257.47-2.426 1.147-3.462 2m2.062-5.749v-1.45c0-2.426 1.963-4.393 4.405-4.393h2.458c2.433 0 4.405 1.967 4.405 4.393v1.45c1.689.631 3.243 1.538 4.608 2.665l1.259-.727c2.101-1.213 4.786-.497 6.01 1.618l1.229 2.129c1.216 2.107.499 4.798-1.602 6.01l-1.257.726c.144.866.219 1.755.219 2.662 0 .907-.075 1.796-.219 2.662l1.257.726c2.101 1.213 2.823 3.896 1.602 6.01l-1.229 2.129c-1.216 2.107-3.906 2.832-6.01 1.618l-1.259-.727c-1.365 1.127-2.92 2.034-4.608 2.665v1.45c0 2.426-1.963 4.393-4.405 4.393h-2.458c-2.433 0-4.405-1.967-4.405-4.393v-1.45c-1.689-.631-3.243-1.538-4.608-2.665l-1.259.727c-2.101 1.213-4.786.497-6.01-1.618l-1.229-2.129c-1.216-2.107-.499-4.798 1.602-6.01l1.257-.726c-.144-.866-.219-1.755-.219-2.662 0-.907.075-1.796.219-2.662l-1.257-.726c-2.101-1.213-2.823-3.896-1.602-6.01l1.229-2.129c1.216-2.107 3.906-2.832 6.01-1.618l1.259.727c1.365-1.127 2.92-2.034 4.608-2.665"/><path fill="#6b4fbb" d="m20.12 23.366c1.347 0 2.439-1.092 2.439-2.439 0-1.347-1.092-2.439-2.439-2.439-1.347 0-2.439 1.092-2.439 2.439 0 1.347 1.092 2.439 2.439 2.439m0 4c-3.556 0-6.439-2.883-6.439-6.439 0-3.556 2.883-6.439 6.439-6.439 3.556 0 6.439 2.883 6.439 6.439 0 3.556-2.883 6.439-6.439 6.439"/></g></g><use fill="#fff" stroke="#e5e5e5" stroke-width="8" mask="url(#1)" stroke-linejoin="round" xlink:href="#0"/><g transform="translate(175 58)"><use fill="#fff" stroke="#e5e5e5" stroke-width="8" mask="url(#3)" xlink:href="#2"/><g fill-rule="nonzero"><path fill="#e5e5e5" d="m41 78c20.435 0 37-16.565 37-37 0-20.435-16.565-37-37-37-20.435 0-37 16.565-37 37 0 20.435 16.565 37 37 37m0 4c-22.644 0-41-18.356-41-41 0-22.644 18.356-41 41-41 22.644 0 41 18.356 41 41 0 22.644-18.356 41-41 41"/><g transform="matrix(.96593.25882-.25882.96593 23.581 9.415)"><path fill="#b5a7dd" d="m14.821 13.655l-2.142 1.768-3.933-2.271c-.72-.416-1.634-.171-2.046.543l-1.507 2.61c-.409.708-.161 1.631.553 2.043l3.926 2.267-.455 2.735c-.145.869-.218 1.754-.218 2.65 0 .896.073 1.782.218 2.65l.455 2.735-3.926 2.267c-.72.416-.965 1.329-.553 2.043l1.507 2.61c.409.708 1.332.955 2.046.543l3.933-2.271 2.142 1.768c1.369 1.131 2.916 2.027 4.579 2.648l2.6.972v4.534c0 .831.669 1.5 1.493 1.5h3.01c.817 0 1.493-.676 1.493-1.5v-4.534l2.6-.972c1.663-.621 3.21-1.518 4.579-2.648l2.142-1.768 3.933 2.271c.72.416 1.634.171 2.046-.543l1.507-2.61c.409-.708.161-1.631-.553-2.043l-3.926-2.267.455-2.735c.145-.869.218-1.754.218-2.65 0-.896-.073-1.782-.218-2.65l-.455-2.735 3.926-2.267c.72-.416.965-1.329.553-2.043l-1.507-2.61c-.409-.708-1.332-.955-2.046-.543l-3.933 2.271-2.142-1.768c-1.369-1.131-2.916-2.027-4.579-2.648l-2.6-.972v-4.534c0-.831-.669-1.5-1.493-1.5h-3.01c-.817 0-1.493.676-1.493 1.5v4.534l-2.6.972c-1.663.621-3.21 1.518-4.579 2.648m3.179-6.395v-1.759c0-3.038 2.471-5.5 5.493-5.5h3.01c3.034 0 5.493 2.46 5.493 5.5v1.759c2.098.784 4.03 1.91 5.725 3.311l1.528-.882c2.631-1.519 5.999-.61 7.51 2.01l1.507 2.61c1.517 2.627.616 5.987-2.02 7.507l-1.525.881c.179 1.076.272 2.18.272 3.307 0 1.127-.093 2.231-.272 3.307l1.525.881c2.631 1.519 3.528 4.89 2.02 7.507l-1.507 2.61c-1.517 2.627-4.877 3.527-7.51 2.01l-1.528-.882c-1.696 1.401-3.627 2.527-5.725 3.311v1.759c0 3.038-2.471 5.5-5.493 5.5h-3.01c-3.034 0-5.493-2.46-5.493-5.5v-1.759c-2.098-.784-4.03-1.91-5.725-3.311l-1.528.882c-2.631 1.519-5.999.61-7.51-2.01l-1.507-2.61c-1.517-2.627-.616-5.987 2.02-7.507l1.525-.881c-.179-1.076-.272-2.18-.272-3.307 0-1.127.093-2.231.272-3.307l-1.525-.881c-2.631-1.519-3.528-4.89-2.02-7.507l1.507-2.61c1.517-2.627 4.877-3.527 7.51-2.01l1.528.882c1.696-1.401 3.627-2.527 5.725-3.311"/><path fill="#6b4fbb" d="m25 30c2.209 0 4-1.791 4-4 0-2.209-1.791-4-4-4-2.209 0-4 1.791-4 4 0 2.209 1.791 4 4 4m0 4c-4.418 0-8-3.582-8-8 0-4.418 3.582-8 8-8 4.418 0 8 3.582 8 8 0 4.418-3.582 8-8 8"/></g></g></g><g transform="translate(140 161)"><path fill="#e5e5e5" fill-rule="nonzero" d="m4 8.541v30.01c0 2.202 1.793 3.995 4 3.995h20c2.209 0 4-1.789 4-3.995v-30.01c0-2.202-1.793-3.995-4-3.995h-20c-2.209 0-4 1.789-4 3.995m-4 0c0-4.416 3.583-7.995 8-7.995h20c4.416 0 8 3.584 8 7.995v30.01c0 4.416-3.583 7.995-8 7.995h-20c-4.416 0-8-3.584-8-7.995v-30.01"/><g fill="#fb722e"><rect width="4" height="11" x="10" y="18.545" rx="2"/><rect width="4" height="11" x="21" y="18.545" rx="2"/></g></g><path fill="#e5e5e5" fill-rule="nonzero" d="m445.16 245.34c-16.874-11.778-110.62-20.336-222.14-20.336-111.61 0-205.4 8.571-222.18 20.364-.904.635-1.121 1.883-.486 2.786.635.904 1.883 1.121 2.786.486 15.756-11.07 109.46-19.636 219.88-19.636 110.34 0 203.99 8.55 219.85 19.617.906.632 2.153.41 2.785-.495.632-.906.41-2.153-.495-2.785"/></g></svg>
\ No newline at end of file diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index a95020a9be8..09f946f1d88 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -17,7 +17,7 @@ .stats %span = icon('bookmark') - = number_with_delimiter(group.projects.count) + = number_with_delimiter(group.projects.non_archived.count) %span = icon('users') diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 0b0f2c9cd1a..17107f55a2d 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -8,7 +8,7 @@ .alert.alert-danger Someone edited the #{issuable.class.model_name.human.downcase} the same time you did. Please check out - = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank" + = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs .form-group diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 25a4aec0a38..884bd3ca9ca 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('issuable') -%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } +%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "102", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } .issuable-sidebar - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.issuable-sidebar-header diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index c57282c5742..c0699b13719 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -10,7 +10,7 @@ .js-projects-list-holder - if projects.any? - %ul.projects-list.content-list + %ul.projects-list - projects.each_with_index do |project, i| - css_class = (i >= projects_limit) ? 'hide' : nil = render "shared/projects/project", project: project, skip_namespace: skip_namespace, diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index df21857e1ad..059aeebaf34 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -10,44 +10,44 @@ %li.project-row{ class: css_class } = cache(cache_key) do + - if avatar + .avatar-container.s40 + - if use_creator_avatar + = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' + - else + = project_icon(project, alt: '', class: 'avatar project-avatar s40') + .project-details + %h3.prepend-top-0.append-bottom-0 + = link_to project_path(project), class: dom_class(project) do + %span.project-full-name + %span.namespace-name + - if project.namespace && !skip_namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = project.name + + - if show_last_commit_as_description + .description.prepend-top-5 + = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit), + class: "commit-row-message" + - elsif project.description.present? + .description.prepend-top-5 + = markdown_field(project, :description) + .controls - if project.archived - %span.label.label-warning archived + %span.prepend-left-10.label.label-warning archived - if project.pipeline_status.has_status? - %span + %span.prepend-left-10 = render_project_pipeline_status(project.pipeline_status) - if forks - %span + %span.prepend-left-10 = icon('code-fork') = number_with_delimiter(project.forks_count) - if stars - %span + %span.prepend-left-10 = icon('star') = number_with_delimiter(project.star_count) - %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } + %span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } = visibility_level_icon(project.visibility_level, fw: true) - - .title - = link_to project_path(project), class: dom_class(project) do - - if avatar - .dash-project-avatar - .avatar-container.s40 - - if use_creator_avatar - = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' - - else - = project_icon(project, alt: '', class: 'avatar project-avatar s40') - %span.project-full-name - %span.namespace-name - - if project.namespace && !skip_namespace - = project.namespace.human_name - \/ - %span.project-name.filter-title - = project.name - - - if show_last_commit_as_description - .description - = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit), - class: "commit-row-message" - - elsif project.description.present? - .description - = markdown_field(project, :description) diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 76cd330e80a..601187455b3 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -13,7 +13,7 @@ .cover-block.user-cover-block .cover-controls - if @user == current_user - = link_to profile_path, class: 'btn btn-gray' do + = link_to profile_path, class: 'btn btn-gray has-tooltip', title: 'Edit profile', 'aria-label': 'Edit profile' do = icon('pencil') - elsif current_user - if @user.abuse_report @@ -24,7 +24,7 @@ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('exclamation-circle') - = link_to user_path(@user, rss_url_options), class: 'btn btn-gray' do + = link_to user_path(@user, rss_url_options), class: 'btn btn-gray has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do = icon('rss') - if current_user && current_user.admin? = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area', @@ -33,7 +33,7 @@ .profile-header .avatar-holder - = link_to avatar_icon(@user, 400), target: '_blank' do + = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' .user-info @@ -44,7 +44,7 @@ %span.middle-dot-divider @#{@user.username} %span.middle-dot-divider - Member since #{@user.created_at.to_s(:medium)} + Member since #{@user.created_at.to_date.to_s(:long)} .cover-desc - unless @user.public_email.blank? @@ -97,7 +97,8 @@ Snippets %div{ class: container_class } - .user-callout{ 'callout-svg' => custom_icon('icon_customization') } + - if @user == current_user + .user-callout{ 'callout-svg' => custom_icon('icon_customization') } .tab-content #activity.tab-pane .row-content-block.calender-block.white.second-block.hidden-xs diff --git a/bin/with_env b/bin/with_env new file mode 100755 index 00000000000..e678fa2f0cc --- /dev/null +++ b/bin/with_env @@ -0,0 +1,16 @@ +#!/bin/sh +# Usage: with_env ENV_FILE COMMAND [ARGS...] +# +# This script lets you modify the environment of an executable before +# launching it. It uses an 'env file' which must contain lines like +# 'MY_VARIABLE="my value"'. +# +env_file=$1 +shift + +# Use set -a to export all variables defined in env_file. +set -a +. "${env_file}" +set +a + +exec "$@" diff --git a/changelogs/unreleased/1051-api-create-users-without-password.yml b/changelogs/unreleased/1051-api-create-users-without-password.yml deleted file mode 100644 index 24b5a73b45c..00000000000 --- a/changelogs/unreleased/1051-api-create-users-without-password.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Optionally make users created via the API set their password -merge_request: 8957 -author: Joost Rijneveld diff --git a/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml b/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml deleted file mode 100644 index 4a1a199673c..00000000000 --- a/changelogs/unreleased/12726-preserve-issues-after-deleting-users.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Deleting a user doesn't delete issues they've created/are assigned to -merge_request: 7393 -author: diff --git a/changelogs/unreleased/1363-redo-mailroom-support.yml b/changelogs/unreleased/1363-redo-mailroom-support.yml deleted file mode 100644 index 8ed206f4fdb..00000000000 --- a/changelogs/unreleased/1363-redo-mailroom-support.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Redo internals of Incoming Mail Support -merge_request: 9385 -author: diff --git a/changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml b/changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml deleted file mode 100644 index 1b7e294bd67..00000000000 --- a/changelogs/unreleased/1381-present-commits-pagination-headers-correctly.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "GET 'projects/:id/repository/commits' endpoint improvements" -merge_request: 9679 -author: George Andrinopoulos, Jordan Ryan Reuter diff --git a/changelogs/unreleased/14492-change-fork-endpoint.yml b/changelogs/unreleased/14492-change-fork-endpoint.yml deleted file mode 100644 index 39024b51b54..00000000000 --- a/changelogs/unreleased/14492-change-fork-endpoint.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Move /projects/fork/:id to /projects/:id/fork -merge_request: 8940 -author: diff --git a/changelogs/unreleased/14748-runner-version-in-admin-views.yml b/changelogs/unreleased/14748-runner-version-in-admin-views.yml deleted file mode 100644 index 2478a81c824..00000000000 --- a/changelogs/unreleased/14748-runner-version-in-admin-views.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add runner version to /admin/runners view -merge_request: 8733 -author: Jonathon Reinhart diff --git a/changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml b/changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml deleted file mode 100644 index f247fe35439..00000000000 --- a/changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove remnants of git annex support. -merge_request: -author: diff --git a/changelogs/unreleased/18962-update-issues-button-jumps.yml b/changelogs/unreleased/18962-update-issues-button-jumps.yml deleted file mode 100644 index 7be136ac4ff..00000000000 --- a/changelogs/unreleased/18962-update-issues-button-jumps.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Align bulk update issues button to the right -merge_request: -author: diff --git a/changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml b/changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml deleted file mode 100644 index d74057dca8a..00000000000 --- a/changelogs/unreleased/19302-wiki-page-delete-does-not-trigger-the-webhook.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Execute web hooks for WikiPage delete operation -merge_request: 8198 -author: diff --git a/changelogs/unreleased/1937-https-clone-url-username.yml b/changelogs/unreleased/1937-https-clone-url-username.yml deleted file mode 100644 index fa89d94e0f3..00000000000 --- a/changelogs/unreleased/1937-https-clone-url-username.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add the Username to the HTTP(S) clone URL of a Repository -merge_request: 9347 -author: Jan Christophersen diff --git a/changelogs/unreleased/19497-hide-relevant-info-when-project-issues-are-disabled.yml b/changelogs/unreleased/19497-hide-relevant-info-when-project-issues-are-disabled.yml deleted file mode 100644 index eceb2b9fac6..00000000000 --- a/changelogs/unreleased/19497-hide-relevant-info-when-project-issues-are-disabled.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Hide issue info when project issues are disabled -merge_request: -author: George Andrinopoulos diff --git a/changelogs/unreleased/20495-plus-icon-button.yml b/changelogs/unreleased/20495-plus-icon-button.yml deleted file mode 100644 index 0f8650eb7b6..00000000000 --- a/changelogs/unreleased/20495-plus-icon-button.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove plus icon from MR button on compare view -merge_request: -author: diff --git a/changelogs/unreleased/20732_member_exists_409.yml b/changelogs/unreleased/20732_member_exists_409.yml deleted file mode 100644 index 135647c7ac3..00000000000 --- a/changelogs/unreleased/20732_member_exists_409.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Add member: Always return 409 when a member exists' -merge_request: -author: diff --git a/changelogs/unreleased/21240_snippets_line_ending.yml b/changelogs/unreleased/21240_snippets_line_ending.yml deleted file mode 100644 index 880fdd2c9ed..00000000000 --- a/changelogs/unreleased/21240_snippets_line_ending.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Download snippets with LF line-endings by default -merge_request: 8999 -author: diff --git a/changelogs/unreleased/21605-allow-html5-details.yml b/changelogs/unreleased/21605-allow-html5-details.yml deleted file mode 100644 index b0c654783d9..00000000000 --- a/changelogs/unreleased/21605-allow-html5-details.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: SanitizationFilter allows html5 details and summary tags -merge_request: 6568 -author: diff --git a/changelogs/unreleased/22018-api-milestone-merge-requests.yml b/changelogs/unreleased/22018-api-milestone-merge-requests.yml deleted file mode 100644 index ccad2ec838c..00000000000 --- a/changelogs/unreleased/22018-api-milestone-merge-requests.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Adds API endpoint to fetch all merge request for a single milestone -merge_request: -author: Joren De Groof diff --git a/changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml b/changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml deleted file mode 100644 index 028923b83cf..00000000000 --- a/changelogs/unreleased/22132-rename-branch-name-params-to-branch.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Standardize branch name params as branch on V4 API -merge_request: 8936 -author: diff --git a/changelogs/unreleased/22466-task-list-alignment.yml b/changelogs/unreleased/22466-task-list-alignment.yml deleted file mode 100644 index 6e6ccb873ec..00000000000 --- a/changelogs/unreleased/22466-task-list-alignment.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Align task list checkboxes -merge_request: 6487 -author: Jared Deckard <jared.deckard@gmail.com> diff --git a/changelogs/unreleased/22562-todos-filters.yml b/changelogs/unreleased/22562-todos-filters.yml deleted file mode 100644 index 9cca138744a..00000000000 --- a/changelogs/unreleased/22562-todos-filters.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix Sort dropdown reflow issue -merge_request: 9533 -author: Jarkko Tuunanen diff --git a/changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml b/changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml deleted file mode 100644 index 9b3c2bd9278..00000000000 --- a/changelogs/unreleased/22645-add-discussion-contribs-to-calendar.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add discussion events to contributions calendar -merge_request: 8821 -author: diff --git a/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml b/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml deleted file mode 100644 index 05d5993ddf3..00000000000 --- a/changelogs/unreleased/22818-licence-gitignore-and-yml-endpoints-removal.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: V3 deprecated templates endpoints removal -merge_request: 8853 -author: diff --git a/changelogs/unreleased/22850-404-when-requesting-build-trace.yml b/changelogs/unreleased/22850-404-when-requesting-build-trace.yml new file mode 100644 index 00000000000..6b442130d9b --- /dev/null +++ b/changelogs/unreleased/22850-404-when-requesting-build-trace.yml @@ -0,0 +1,4 @@ +--- +title: Resolve "404 when requesting build trace" +merge_request: 9759 +author: dosuken123 diff --git a/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml b/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml deleted file mode 100644 index a53e7d77c16..00000000000 --- a/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add spec for todo with target_type Commit -merge_request: 9351 -author: George Andrinopoulos diff --git a/changelogs/unreleased/23061-consolidate-project-lists.yml b/changelogs/unreleased/23061-consolidate-project-lists.yml deleted file mode 100644 index dbb8fed55c0..00000000000 --- a/changelogs/unreleased/23061-consolidate-project-lists.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Consolidate /projects endpoint' -merge_request: 8962 -author: diff --git a/changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml b/changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml deleted file mode 100644 index f7c856040e0..00000000000 --- a/changelogs/unreleased/23062-allow-git-log-to-accept-follow-and-skip.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make Git history follow renames again by performing the --skip in Ruby -merge_request: -author: diff --git a/changelogs/unreleased/23104-remove-public-param-for-projects.yml b/changelogs/unreleased/23104-remove-public-param-for-projects.yml deleted file mode 100644 index 78eb785279f..00000000000 --- a/changelogs/unreleased/23104-remove-public-param-for-projects.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: remove `public` param for projects' -merge_request: 8736 -author: diff --git a/changelogs/unreleased/23535-folders-in-wiki-repository.yml b/changelogs/unreleased/23535-folders-in-wiki-repository.yml deleted file mode 100644 index 05212b608d4..00000000000 --- a/changelogs/unreleased/23535-folders-in-wiki-repository.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Show directory hierarchy when listing wiki pages -merge_request: 8133 -author: Alex Braha Stoll diff --git a/changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml b/changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml deleted file mode 100644 index 628db8a5419..00000000000 --- a/changelogs/unreleased/23819-fix-milestone-counters-to-top-right-of-panel-headings.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix position of counter in milestone panels -merge_request: 7842 -author: Andrew Smith (EspadaV8) diff --git a/changelogs/unreleased/23862-fix-group-project-count.yml b/changelogs/unreleased/23862-fix-group-project-count.yml new file mode 100644 index 00000000000..7b2e9f9bfa6 --- /dev/null +++ b/changelogs/unreleased/23862-fix-group-project-count.yml @@ -0,0 +1,4 @@ +--- +title: Adding non_archived scope for counting projects +merge_request: 8305 +author: Naveen Kumar diff --git a/changelogs/unreleased/23948-assign-to-me.yml b/changelogs/unreleased/23948-assign-to-me.yml deleted file mode 100644 index d73aa92b0e9..00000000000 --- a/changelogs/unreleased/23948-assign-to-me.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Re-add Assign to me link to Merge Request and Issues -merge_request: -author: diff --git a/changelogs/unreleased/23993-drop-ci_projects.yml b/changelogs/unreleased/23993-drop-ci_projects.yml deleted file mode 100644 index ee9cf774e37..00000000000 --- a/changelogs/unreleased/23993-drop-ci_projects.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Drop unused ci_projects table and some unused project_id columns, - then rename gl_project_id to project_id. Stop exporting job trace when - exporting projects. -merge_request: 9378 -author: David Wagner diff --git a/changelogs/unreleased/24215-closed-issues-board.yml b/changelogs/unreleased/24215-closed-issues-board.yml new file mode 100644 index 00000000000..678ec34b274 --- /dev/null +++ b/changelogs/unreleased/24215-closed-issues-board.yml @@ -0,0 +1,4 @@ +--- +title: Display all closed issues in “done” board list +merge_request: +author: diff --git a/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml b/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml deleted file mode 100644 index fa137a29cb4..00000000000 --- a/changelogs/unreleased/24333-close-issues-with-merge-request-title-ui.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show Issues mentioned / being closed from a Merge Requests title below the - 'Accept Merge Request' button -merge_request: 9194 -author: Jan Christophersen diff --git a/changelogs/unreleased/24683-sidebar-spinners.yml b/changelogs/unreleased/24683-sidebar-spinners.yml deleted file mode 100644 index 3fec273152f..00000000000 --- a/changelogs/unreleased/24683-sidebar-spinners.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: hide loading spinners for server-rendered sidebar fields -merge_request: -author: diff --git a/changelogs/unreleased/24976-start-of-line-mention.yml b/changelogs/unreleased/24976-start-of-line-mention.yml deleted file mode 100644 index 99208aac87c..00000000000 --- a/changelogs/unreleased/24976-start-of-line-mention.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added a feature to create a 'directly addressed' Todo when mentioned in the beginning of a line. -merge_request: 7926 -author: Ershad Kunnakkadan diff --git a/changelogs/unreleased/24998-fix-typo-gitlab-config-file.yml b/changelogs/unreleased/24998-fix-typo-gitlab-config-file.yml deleted file mode 100644 index 3b90466e3af..00000000000 --- a/changelogs/unreleased/24998-fix-typo-gitlab-config-file.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix typo in Gitlab config file -merge_request: 9702 -author: medied diff --git a/changelogs/unreleased/25188-polyfill-es-symbol.yml b/changelogs/unreleased/25188-polyfill-es-symbol.yml new file mode 100644 index 00000000000..d0cf36b9ec6 --- /dev/null +++ b/changelogs/unreleased/25188-polyfill-es-symbol.yml @@ -0,0 +1,4 @@ +--- +title: Add ECMAScript polyfills for Symbol and Array.find +merge_request: 10120 +author: diff --git a/changelogs/unreleased/25367-add-impersonation-token.yml b/changelogs/unreleased/25367-add-impersonation-token.yml deleted file mode 100644 index 4a30f960036..00000000000 --- a/changelogs/unreleased/25367-add-impersonation-token.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Manage user personal access tokens through api and add impersonation tokens -merge_request: 9099 -author: Simon Vocella diff --git a/changelogs/unreleased/25437-just-emoji.yml b/changelogs/unreleased/25437-just-emoji.yml deleted file mode 100644 index ceb81a47f2d..00000000000 --- a/changelogs/unreleased/25437-just-emoji.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Introduce /award slash command; Allow posting of just an emoji in comment -merge_request: 9382 -author: mhasbini diff --git a/changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml b/changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml deleted file mode 100644 index e9d46f6b122..00000000000 --- a/changelogs/unreleased/25465-todo-done-clicking-is-kind-of-unsafe.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Todo done clicking is kind of unusable -merge_request: 8691 -author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/25503_issues_finder_performance.yml b/changelogs/unreleased/25503_issues_finder_performance.yml deleted file mode 100644 index 87964269c6d..00000000000 --- a/changelogs/unreleased/25503_issues_finder_performance.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Filter by projects in the end of search -merge_request: 9030 -author: diff --git a/changelogs/unreleased/25709-diff-file-overflow.yml b/changelogs/unreleased/25709-diff-file-overflow.yml deleted file mode 100644 index 7d1b2b36ab8..00000000000 --- a/changelogs/unreleased/25709-diff-file-overflow.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Responsive title in diffs inline, side by side, with and without sidebar -merge_request: 8475 -author: diff --git a/changelogs/unreleased/25920-create-issue-from-failing-build.yml b/changelogs/unreleased/25920-create-issue-from-failing-build.yml deleted file mode 100644 index 580d1074aa7..00000000000 --- a/changelogs/unreleased/25920-create-issue-from-failing-build.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add button to create issue for failing build -merge_request: 9391 -author: Alex Sanford diff --git a/changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml b/changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml deleted file mode 100644 index 799c5277207..00000000000 --- a/changelogs/unreleased/26087-asciidoc-cicd-badges-snippet.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added AsciiDoc Snippet to CI/CD Badges -merge_request: 9164 -author: Jan Christophersen diff --git a/changelogs/unreleased/26136-list-repository-tree-api-doc.yml b/changelogs/unreleased/26136-list-repository-tree-api-doc.yml deleted file mode 100644 index 85d8bc6ca8a..00000000000 --- a/changelogs/unreleased/26136-list-repository-tree-api-doc.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make documentation of list repository tree API call more detailed -merge_request: 9532 -author: Marius Kleiner diff --git a/changelogs/unreleased/26206-fix-download-dropdown.yml b/changelogs/unreleased/26206-fix-download-dropdown.yml deleted file mode 100644 index a6c101375bb..00000000000 --- a/changelogs/unreleased/26206-fix-download-dropdown.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Set dropdown height fixed to 250px and make it scrollable -merge_request: 9063 -author: diff --git a/changelogs/unreleased/26286-most-recent-activity-profile-header.yml b/changelogs/unreleased/26286-most-recent-activity-profile-header.yml deleted file mode 100644 index 74d5a43a804..00000000000 --- a/changelogs/unreleased/26286-most-recent-activity-profile-header.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added 'Most Recent Activity' header to the User Profile page -merge_request: 9189 -author: Jan Christophersen diff --git a/changelogs/unreleased/26287-link-branch-in-calendar-activity.yml b/changelogs/unreleased/26287-link-branch-in-calendar-activity.yml deleted file mode 100644 index 35855578d21..00000000000 --- a/changelogs/unreleased/26287-link-branch-in-calendar-activity.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add Links to Branches in Calendar Activity -merge_request: 9224 -author: Jan Christophersen diff --git a/changelogs/unreleased/2629-show-public-rss-feeds-to-anonymous-users.yml b/changelogs/unreleased/2629-show-public-rss-feeds-to-anonymous-users.yml deleted file mode 100644 index 6ee8e5724bc..00000000000 --- a/changelogs/unreleased/2629-show-public-rss-feeds-to-anonymous-users.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Show public RSS feeds to anonymous users -merge_request: 9596 -author: Michael Kozono diff --git a/changelogs/unreleased/26315-unify-labels-filter-behavior.yml b/changelogs/unreleased/26315-unify-labels-filter-behavior.yml deleted file mode 100644 index cd2f40c94fe..00000000000 --- a/changelogs/unreleased/26315-unify-labels-filter-behavior.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Unify issues search behavior by always filtering when ALL labels matches -merge_request: 8849 -author: diff --git a/changelogs/unreleased/26348-cleanup-navigation-order-groups.yml b/changelogs/unreleased/26348-cleanup-navigation-order-groups.yml deleted file mode 100644 index ce888baa32f..00000000000 --- a/changelogs/unreleased/26348-cleanup-navigation-order-groups.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Clean-up Groups navigation order -merge_request: 9309 -author: diff --git a/changelogs/unreleased/26348-cleanup-navigation-order.yml b/changelogs/unreleased/26348-cleanup-navigation-order.yml deleted file mode 100644 index d5324f9e025..00000000000 --- a/changelogs/unreleased/26348-cleanup-navigation-order.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Clean-up Project navigation order -merge_request: 9272 -author: diff --git a/changelogs/unreleased/26371-native-emojis-v3-code.yml b/changelogs/unreleased/26371-native-emojis-v3-code.yml deleted file mode 100644 index 88346711490..00000000000 --- a/changelogs/unreleased/26371-native-emojis-v3-code.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use native unicode emojis -merge_request: -author: diff --git a/changelogs/unreleased/26379-iid-param.yml b/changelogs/unreleased/26379-iid-param.yml deleted file mode 100644 index ac743e68d6f..00000000000 --- a/changelogs/unreleased/26379-iid-param.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: add :iids param to IssuableFinder (resolve technical dept) -merge_request: 9222 -author: mhasbini diff --git a/changelogs/unreleased/26500-informative-slack-notifications.yml b/changelogs/unreleased/26500-informative-slack-notifications.yml deleted file mode 100644 index 342235424f4..00000000000 --- a/changelogs/unreleased/26500-informative-slack-notifications.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add user & build links in Slack Notifications -merge_request: 8641 -author: Poornima M diff --git a/changelogs/unreleased/26651-cannot-move-project-into-group.yml b/changelogs/unreleased/26651-cannot-move-project-into-group.yml deleted file mode 100644 index 244a19a627d..00000000000 --- a/changelogs/unreleased/26651-cannot-move-project-into-group.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Specify in the documentation that only projects owners can transfer projects -merge_request: -author: diff --git a/changelogs/unreleased/26703-todos-count.yml b/changelogs/unreleased/26703-todos-count.yml deleted file mode 100644 index 24fd0c406e2..00000000000 --- a/changelogs/unreleased/26703-todos-count.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: show 99+ for large count in todos notification bell -merge_request: 9171 -author: mhasbini diff --git a/changelogs/unreleased/26705-filter-todos-by-manual-add.yml b/changelogs/unreleased/26705-filter-todos-by-manual-add.yml deleted file mode 100644 index 3521496a20e..00000000000 --- a/changelogs/unreleased/26705-filter-todos-by-manual-add.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Filter todos by manual add -merge_request: 8691 -author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml b/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml deleted file mode 100644 index 6fc4615dab8..00000000000 --- a/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Combined deploy keys, push rules, protect branches and mirror repository settings options into a single one called - Repository -merge_request: -author: diff --git a/changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml b/changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml deleted file mode 100644 index 15da43b8091..00000000000 --- a/changelogs/unreleased/26744-add-omniauth-oauth2-generic-strategy.yml +++ /dev/null @@ -1,3 +0,0 @@ -title: Add the oauth2_generic OmniAuth strategy -merge_request: 9048 -author: Joe Marty
\ No newline at end of file diff --git a/changelogs/unreleased/26790-label-color-todos.yml b/changelogs/unreleased/26790-label-color-todos.yml deleted file mode 100644 index 74084473d81..00000000000 --- a/changelogs/unreleased/26790-label-color-todos.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: fix background color for labels mention in todo -merge_request: 9155 -author: mhasbini diff --git a/changelogs/unreleased/26847-api-pipelines-use-basic.yml b/changelogs/unreleased/26847-api-pipelines-use-basic.yml deleted file mode 100644 index 2034a4ba080..00000000000 --- a/changelogs/unreleased/26847-api-pipelines-use-basic.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Expose pipelines as PipelineBasic `api/v3/projects/:id/pipelines` -merge_request: 8875 -author: diff --git a/changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml b/changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml deleted file mode 100644 index 3d6400cba76..00000000000 --- a/changelogs/unreleased/26875-builds-api-endpoint-skipped-scope.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add all available statuses to scope filter for project builds endpoint -merge_request: -author: George Andrinopoulos diff --git a/changelogs/unreleased/26900-pipelines-tabs.yml b/changelogs/unreleased/26900-pipelines-tabs.yml deleted file mode 100644 index f08514c621f..00000000000 --- a/changelogs/unreleased/26900-pipelines-tabs.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Adds Pending and Finished tabs to pipelines page -merge_request: -author: diff --git a/changelogs/unreleased/26957-tanuki-anim-hang.yml b/changelogs/unreleased/26957-tanuki-anim-hang.yml deleted file mode 100644 index c7b4b9ebdfd..00000000000 --- a/changelogs/unreleased/26957-tanuki-anim-hang.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: don't animate logo when downloading files -merge_request: -author: diff --git a/changelogs/unreleased/27032-add-a-house-keeping-api-call.yml b/changelogs/unreleased/27032-add-a-house-keeping-api-call.yml deleted file mode 100644 index a9f70e339c0..00000000000 --- a/changelogs/unreleased/27032-add-a-house-keeping-api-call.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add housekeeping endpoint for Projects API -merge_request: 9421 -author: diff --git a/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml b/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml deleted file mode 100644 index ee236310a71..00000000000 --- a/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: API: Add environment stop action -merge_request: 8808 -author: diff --git a/changelogs/unreleased/27287-label-dropdown-error-messages.yml b/changelogs/unreleased/27287-label-dropdown-error-messages.yml deleted file mode 100644 index dfd4102c324..00000000000 --- a/changelogs/unreleased/27287-label-dropdown-error-messages.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix displaying error messages for create label dropdown -merge_request: 9058 -author: Tom Koole diff --git a/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml b/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml deleted file mode 100644 index dd4907166c4..00000000000 --- a/changelogs/unreleased/27336-add-environment-url-link-to-terminal-page.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added external environment link to web terminal view -merge_request: 8303 -author: diff --git a/changelogs/unreleased/27354-navigation-new-button.yml b/changelogs/unreleased/27354-navigation-new-button.yml deleted file mode 100644 index 62cac9bbbd3..00000000000 --- a/changelogs/unreleased/27354-navigation-new-button.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Re-add the New Project button in nav bar -merge_request: -author: diff --git a/changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml b/changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml deleted file mode 100644 index a116c68ad87..00000000000 --- a/changelogs/unreleased/27376-cache-default-branch-pipeline-on-project.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Speed up project dashboard by caching pipeline status and eager loading routes -merge_request: 9903 -author: diff --git a/changelogs/unreleased/27452-update-issue-count.yml b/changelogs/unreleased/27452-update-issue-count.yml deleted file mode 100644 index a7417eba63c..00000000000 --- a/changelogs/unreleased/27452-update-issue-count.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: update issue count when closing/reopening an issue -merge_request: -author: diff --git a/changelogs/unreleased/27501-api-use-visibility-everywhere.yml b/changelogs/unreleased/27501-api-use-visibility-everywhere.yml deleted file mode 100644 index f1b70687878..00000000000 --- a/changelogs/unreleased/27501-api-use-visibility-everywhere.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "API: Use `visibility` as string parameter everywhere" -merge_request: 9337 -author: diff --git a/changelogs/unreleased/27503-feature-status-aria-labels.yml b/changelogs/unreleased/27503-feature-status-aria-labels.yml new file mode 100644 index 00000000000..f514fd5b631 --- /dev/null +++ b/changelogs/unreleased/27503-feature-status-aria-labels.yml @@ -0,0 +1,4 @@ +--- +title: Add `aria-label` for feature status accessibility +merge_request: 9830 +author: diff --git a/changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml b/changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml deleted file mode 100644 index 3050b072863..00000000000 --- a/changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Option to prevent signing in from multiple ips -merge_request: 8998 -author: diff --git a/changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml b/changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml deleted file mode 100644 index a4ef2b23aaa..00000000000 --- a/changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make stuck builds detection more performant -merge_request: 9025 -author: diff --git a/changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml b/changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml deleted file mode 100644 index 4436b4bee68..00000000000 --- a/changelogs/unreleased/27530-fix-job-dropdown-pipeline-console-error.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes job dropdown action throws error in js console -merge_request: 9182 -author: diff --git a/changelogs/unreleased/27532_api_changes.yml b/changelogs/unreleased/27532_api_changes.yml deleted file mode 100644 index 778469d5a86..00000000000 --- a/changelogs/unreleased/27532_api_changes.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use iids as filter parameter -merge_request: 9096 -author: diff --git a/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml b/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml deleted file mode 100644 index 5c738af7704..00000000000 --- a/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Refactor dropdown_assignee_spec -merge_request: 9711 -author: George Andrinopoulos diff --git a/changelogs/unreleased/27574-pipelines-empty-state.yml b/changelogs/unreleased/27574-pipelines-empty-state.yml new file mode 100644 index 00000000000..c26ea97205f --- /dev/null +++ b/changelogs/unreleased/27574-pipelines-empty-state.yml @@ -0,0 +1,4 @@ +--- +title: Adds empty and error state to pipelines +merge_request: +author: diff --git a/changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml b/changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml deleted file mode 100644 index 8f297620e23..00000000000 --- a/changelogs/unreleased/27608-fixes-markdown-in-activity-feed-is-light-gray.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes markdown in activity-feed is gray -merge_request: 9179 -author: diff --git a/changelogs/unreleased/27610-issue-number-alignment.yml b/changelogs/unreleased/27610-issue-number-alignment.yml deleted file mode 100644 index 19ab8872c62..00000000000 --- a/changelogs/unreleased/27610-issue-number-alignment.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: fixes issue number alignment problem in MR and issue list -merge_request: 9020 -author: diff --git a/changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml b/changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml deleted file mode 100644 index 59da28964f7..00000000000 --- a/changelogs/unreleased/27631-fix-small-height-of-activity-header-page.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Fix small height of activity header page" -merge_request: 8952 -author: Pavel Sorokin diff --git a/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml b/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml deleted file mode 100644 index 6c98b46d8cb..00000000000 --- a/changelogs/unreleased/27726-fix-dropdown-width-in-admin-project-page.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes dropdown width in admin project page -merge_request: 9002 -author: diff --git a/changelogs/unreleased/27762-add-default-artifacts-expiration.yml b/changelogs/unreleased/27762-add-default-artifacts-expiration.yml deleted file mode 100644 index 27fa77ed04d..00000000000 --- a/changelogs/unreleased/27762-add-default-artifacts-expiration.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add admin setting for default artifacts expiration -merge_request: 9219 -author: diff --git a/changelogs/unreleased/27778-a11y-sidebar.yml b/changelogs/unreleased/27778-a11y-sidebar.yml deleted file mode 100644 index fb37d7fdb35..00000000000 --- a/changelogs/unreleased/27778-a11y-sidebar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improves a11y in sidebar by adding aria-hidden attributes in i tags and by - fixing two broken aria-hidden attributes -merge_request: -author: diff --git a/changelogs/unreleased/27783-fix-fe-doc-broken-link.yml b/changelogs/unreleased/27783-fix-fe-doc-broken-link.yml deleted file mode 100644 index 429110e9178..00000000000 --- a/changelogs/unreleased/27783-fix-fe-doc-broken-link.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes FE Doc broken link -merge_request: 9120 -author: diff --git a/changelogs/unreleased/27840-improve-search-bar-experience.yml b/changelogs/unreleased/27840-improve-search-bar-experience.yml deleted file mode 100644 index 87b1f0c5572..00000000000 --- a/changelogs/unreleased/27840-improve-search-bar-experience.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Enhanced filter issues layout for better mobile experiance -merge_request: 9280 -author: Pratik Borsadiya diff --git a/changelogs/unreleased/27910-admin-can-create-project-in-all-groups.yml b/changelogs/unreleased/27910-admin-can-create-project-in-all-groups.yml new file mode 100644 index 00000000000..40fd8dacc82 --- /dev/null +++ b/changelogs/unreleased/27910-admin-can-create-project-in-all-groups.yml @@ -0,0 +1,4 @@ +--- +title: Allow admin to view all namespaces +merge_request: +author: George Andrinopoulos diff --git a/changelogs/unreleased/27920-both-wip-messages-showing.yml b/changelogs/unreleased/27920-both-wip-messages-showing.yml deleted file mode 100644 index 497fda8c8ba..00000000000 --- a/changelogs/unreleased/27920-both-wip-messages-showing.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Dispatch needed JS when creating a new MR in diff view -merge_request: -author: diff --git a/changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml b/changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml deleted file mode 100644 index 53077eedc11..00000000000 --- a/changelogs/unreleased/27924-set-max-width-mini-pipeline-text.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Set maximum width for mini pipeline graph text so it is not truncated to early -merge_request: 9188 -author: diff --git a/changelogs/unreleased/27934-left-align-logo.yml b/changelogs/unreleased/27934-left-align-logo.yml deleted file mode 100644 index d4e5e169465..00000000000 --- a/changelogs/unreleased/27934-left-align-logo.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Left align logo -merge_request: -author: diff --git a/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml b/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml deleted file mode 100644 index adc129d8dca..00000000000 --- a/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Uploaded files which content can change now require revalidation on each page load -merge_request: 9453 -author: diff --git a/changelogs/unreleased/27978-improve-task-list-ux.yml b/changelogs/unreleased/27978-improve-task-list-ux.yml deleted file mode 100644 index a6bd99da82e..00000000000 --- a/changelogs/unreleased/27978-improve-task-list-ux.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Only add a newline in the Markdown Editor if the current line is not empty -merge_request: 9455 -author: Jan Christophersen diff --git a/changelogs/unreleased/27994-fix-mr-widget-jump.yml b/changelogs/unreleased/27994-fix-mr-widget-jump.yml deleted file mode 100644 index 77783e54a3a..00000000000 --- a/changelogs/unreleased/27994-fix-mr-widget-jump.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix MR widget jump -merge_request: 9146 -author: diff --git a/changelogs/unreleased/28010-mr-merge-button-default-to-danger.yml b/changelogs/unreleased/28010-mr-merge-button-default-to-danger.yml deleted file mode 100644 index 06bb669ceac..00000000000 --- a/changelogs/unreleased/28010-mr-merge-button-default-to-danger.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Default to subtle MR mege button until CI status is available -merge_request: -author: diff --git a/changelogs/unreleased/28019-make-builds-show-faster.yml b/changelogs/unreleased/28019-make-builds-show-faster.yml deleted file mode 100644 index bbfea0e4c88..00000000000 --- a/changelogs/unreleased/28019-make-builds-show-faster.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Avoid calling Build#trace_with_state for performance -merge_request: 9149 -author: Takuya Noguchi diff --git a/changelogs/unreleased/28082-deleted-branch-event-404.yml b/changelogs/unreleased/28082-deleted-branch-event-404.yml deleted file mode 100644 index e989ca34784..00000000000 --- a/changelogs/unreleased/28082-deleted-branch-event-404.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Stop linking to deleted Branches in Activity tabs -merge_request: 9203 -author: Jan Christophersen diff --git a/changelogs/unreleased/28142-overlap-bugs.yml b/changelogs/unreleased/28142-overlap-bugs.yml deleted file mode 100644 index 9fdabdf204a..00000000000 --- a/changelogs/unreleased/28142-overlap-bugs.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix z index issues with sidebar -merge_request: -author: diff --git a/changelogs/unreleased/28176_merge_widget_fix.yml b/changelogs/unreleased/28176_merge_widget_fix.yml deleted file mode 100644 index 8e4e75fc237..00000000000 --- a/changelogs/unreleased/28176_merge_widget_fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix error in MR widget after /merge slash command -merge_request: 9259 -author: diff --git a/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml deleted file mode 100644 index 3bcf0e06d08..00000000000 --- a/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Truncate long Todo titles for non-mobile screens -merge_request: 9311 -author: diff --git a/changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml b/changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml deleted file mode 100644 index df2478a3f28..00000000000 --- a/changelogs/unreleased/28204-option-to-disable-webpack-dev-server-livereload.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Pick up option from GDK to disable webpack dev server livereload -merge_request: -author: diff --git a/changelogs/unreleased/28229-pipelines-loading-icon.yml b/changelogs/unreleased/28229-pipelines-loading-icon.yml deleted file mode 100644 index d8f82f658c2..00000000000 --- a/changelogs/unreleased/28229-pipelines-loading-icon.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Centers loading icon vertically and horizontally in pipelines table in commit - view -merge_request: -author: diff --git a/changelogs/unreleased/28236-browse-button-dropping.yml b/changelogs/unreleased/28236-browse-button-dropping.yml deleted file mode 100644 index 3a3d755f40c..00000000000 --- a/changelogs/unreleased/28236-browse-button-dropping.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Increase right side of file header to button stays on same line -merge_request: -author: diff --git a/changelogs/unreleased/28247-timeloops-bug.yml b/changelogs/unreleased/28247-timeloops-bug.yml deleted file mode 100644 index 12ab523b7c7..00000000000 --- a/changelogs/unreleased/28247-timeloops-bug.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Only run timeago loops after rendering timeago components -merge_request: -author: diff --git a/changelogs/unreleased/28253-fix-buid-scroll-button-position.yml b/changelogs/unreleased/28253-fix-buid-scroll-button-position.yml deleted file mode 100644 index b13d115dab9..00000000000 --- a/changelogs/unreleased/28253-fix-buid-scroll-button-position.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix positioning of `Scroll to top` button -merge_request: -author: diff --git a/changelogs/unreleased/28257-issues-iids.yml b/changelogs/unreleased/28257-issues-iids.yml deleted file mode 100644 index 0a85504a8de..00000000000 --- a/changelogs/unreleased/28257-issues-iids.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: API issues - support filtering by iids -merge_request: -author: diff --git a/changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml b/changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml deleted file mode 100644 index fa1674453de..00000000000 --- a/changelogs/unreleased/28262-horizontal-scrolling-issue-on-long-project-names.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Wrap long Project and Group titles -merge_request: 9301 -author: diff --git a/changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml b/changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml deleted file mode 100644 index 6e3cd8a60d8..00000000000 --- a/changelogs/unreleased/28277-document-u2f-limitations-with-multiple-urls.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Document U2F limitations with multiple URLs -merge_request: 9300 -author: diff --git a/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml b/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml deleted file mode 100644 index b97e9a59b2a..00000000000 --- a/changelogs/unreleased/28303-change-development-tanuki-favicon-colors-to-match-logo.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Change development tanuki favicon colors to match logo color order -merge_request: -author: diff --git a/changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml b/changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml deleted file mode 100644 index fed02139a5c..00000000000 --- a/changelogs/unreleased/28329-allow-slash-in-slash-command-args.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Allow slashes in slash command arguments -merge_request: -author: diff --git a/changelogs/unreleased/28353-little-grammar-issue.yml b/changelogs/unreleased/28353-little-grammar-issue.yml deleted file mode 100644 index 10bdb17b266..00000000000 --- a/changelogs/unreleased/28353-little-grammar-issue.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix grammer issue in admin/runners -merge_request: -author: diff --git a/changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml b/changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml deleted file mode 100644 index faf1e89ed94..00000000000 --- a/changelogs/unreleased/28366-renamed-file-tooltip-contains-html.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove markup that was showing in tooltip for renamed files -merge_request: 9374 -author: diff --git a/changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml b/changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml deleted file mode 100644 index 6fc89fd91dd..00000000000 --- a/changelogs/unreleased/28367-fix-unfold-diff-line-number-copy-paste.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes includes line number during unfold copy n paste in parallel diff view -merge_request: 9365 -author: diff --git a/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml deleted file mode 100644 index ed357d86fe3..00000000000 --- a/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Changed coverage reg expression placeholder text to be more like a placeholder -merge_request: -author: diff --git a/changelogs/unreleased/28410-dropdown-styling.yml b/changelogs/unreleased/28410-dropdown-styling.yml deleted file mode 100644 index 2a7af1dd6e8..00000000000 --- a/changelogs/unreleased/28410-dropdown-styling.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add badges to global dropdown -merge_request: -author: diff --git a/changelogs/unreleased/28447-hybrid-repository-storages.yml b/changelogs/unreleased/28447-hybrid-repository-storages.yml deleted file mode 100644 index 00dfc5781b9..00000000000 --- a/changelogs/unreleased/28447-hybrid-repository-storages.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update storage settings to allow extra values per repository storage -merge_request: 9597 -author: diff --git a/changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml b/changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml deleted file mode 100644 index 196a9b788ea..00000000000 --- a/changelogs/unreleased/28450-test-compiling-frontend-assets-for-production-in-ci.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: test compiling production assets and generate webpack bundle report in CI -merge_request: 9396 -author: diff --git a/changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml b/changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml deleted file mode 100644 index dbbe8a19204..00000000000 --- a/changelogs/unreleased/28458-present-gitlab-version-for-v4-changes-on-docs.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Present GitLab version for each V3 to V4 API change on v3_to_v4.md -merge_request: -author: diff --git a/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml b/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml deleted file mode 100644 index 80995d75c23..00000000000 --- a/changelogs/unreleased/28462-fix-delimiter-removes-issue-in-todo-counter.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes delimiter removes when todo marked as done -merge_request: 9435 -author: diff --git a/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml b/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml deleted file mode 100644 index 660a881e094..00000000000 --- a/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes large file name tooltip cutoff in diff header -merge_request: 9529 -author: diff --git a/changelogs/unreleased/28516-default-kubernetes-namespace.yml b/changelogs/unreleased/28516-default-kubernetes-namespace.yml deleted file mode 100644 index 9fa5c681a53..00000000000 --- a/changelogs/unreleased/28516-default-kubernetes-namespace.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make a default namespace of Kubernetes service to contain project ID -merge_request: -author: diff --git a/changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml b/changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml deleted file mode 100644 index eda5764c13e..00000000000 --- a/changelogs/unreleased/28524-gitlab-ci-yml-coverage-key-is-unknown.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Document when current coverage configuration option was introduced -merge_request: 9443 -author: diff --git a/changelogs/unreleased/28538-restore-nav-shortcuts.yml b/changelogs/unreleased/28538-restore-nav-shortcuts.yml deleted file mode 100644 index 07b39cd50d1..00000000000 --- a/changelogs/unreleased/28538-restore-nav-shortcuts.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Restore keyboard shortcuts for "Activity" and "Charts" -merge_request: 9680 -author: diff --git a/changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml b/changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml deleted file mode 100644 index ada726c9048..00000000000 --- a/changelogs/unreleased/28598-narrow-environment-payload-by-using-basic-project.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Narrow environment payload by using basic project details resource -merge_request: -author: diff --git a/changelogs/unreleased/28614-harmonious-color-palette.yml b/changelogs/unreleased/28614-harmonious-color-palette.yml new file mode 100644 index 00000000000..b436e7129a4 --- /dev/null +++ b/changelogs/unreleased/28614-harmonious-color-palette.yml @@ -0,0 +1,4 @@ +--- +title: Update color palette to a more harmonious and consistent one. +merge_request: 10154 +author: diff --git a/changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml b/changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml deleted file mode 100644 index bff996172f3..00000000000 --- a/changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update account view to display new username -merge_request: -author: diff --git a/changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml b/changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml deleted file mode 100644 index e38e5d0db5b..00000000000 --- a/changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improve grammar in GitLab flow documentation -merge_request: 9552 -author: infogrind diff --git a/changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml b/changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml deleted file mode 100644 index b8dba0b5993..00000000000 --- a/changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Set max height to screen height for Zen mode -merge_request: 9667 -author: diff --git a/changelogs/unreleased/28713-fe-style-guide.yml b/changelogs/unreleased/28713-fe-style-guide.yml new file mode 100644 index 00000000000..57edb43e27f --- /dev/null +++ b/changelogs/unreleased/28713-fe-style-guide.yml @@ -0,0 +1,4 @@ +--- +title: Adds Frontend Styleguide to documentation +merge_request: 9961 +author: diff --git a/changelogs/unreleased/28723-consistent-handling-indexof.yml b/changelogs/unreleased/28723-consistent-handling-indexof.yml deleted file mode 100644 index 95d6181d5fa..00000000000 --- a/changelogs/unreleased/28723-consistent-handling-indexof.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Keep consistent in handling indexOf results -merge_request: 9531 -author: Takuya Noguchi diff --git a/changelogs/unreleased/28805-download-archive-with-branch-like-feature-xxxx-add-extra-directory-level.yml b/changelogs/unreleased/28805-download-archive-with-branch-like-feature-xxxx-add-extra-directory-level.yml deleted file mode 100644 index 38ff6b97b2b..00000000000 --- a/changelogs/unreleased/28805-download-archive-with-branch-like-feature-xxxx-add-extra-directory-level.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Ensure archive download is only one directory deep -merge_request: 9616 -author: diff --git a/changelogs/unreleased/28807-search-for-milestone-by-title-in-rest-api.yml b/changelogs/unreleased/28807-search-for-milestone-by-title-in-rest-api.yml deleted file mode 100644 index 0016253e32e..00000000000 --- a/changelogs/unreleased/28807-search-for-milestone-by-title-in-rest-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Enable filtering milestones by search criteria in the API -merge_request: 9606 -author: diff --git a/changelogs/unreleased/28835-jobs-head.yml b/changelogs/unreleased/28835-jobs-head.yml deleted file mode 100644 index 1580cfb19ba..00000000000 --- a/changelogs/unreleased/28835-jobs-head.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix jobs table header height -merge_request: -author: diff --git a/changelogs/unreleased/28837-remove-help-duplicate.yml b/changelogs/unreleased/28837-remove-help-duplicate.yml deleted file mode 100644 index b1001245663..00000000000 --- a/changelogs/unreleased/28837-remove-help-duplicate.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove help link from right dropdown -merge_request: -author: diff --git a/changelogs/unreleased/28865-filter-by-authorized-projects-in-v4.yml b/changelogs/unreleased/28865-filter-by-authorized-projects-in-v4.yml deleted file mode 100644 index 7c64783cbd0..00000000000 --- a/changelogs/unreleased/28865-filter-by-authorized-projects-in-v4.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add filter param for project membership for current_user in API v4 -merge_request: -author: diff --git a/changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml b/changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml deleted file mode 100644 index 114a14ec2df..00000000000 --- a/changelogs/unreleased/28890-allow-creating-mr-without-target-branch-in-url.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow creating merge request even if target branch is not specified in query - params -merge_request: 9968 -author: diff --git a/changelogs/unreleased/28893-highlighted-diff-doesn-t-stay-highlighted-on-refresh.yml b/changelogs/unreleased/28893-highlighted-diff-doesn-t-stay-highlighted-on-refresh.yml deleted file mode 100644 index 9ba33af010c..00000000000 --- a/changelogs/unreleased/28893-highlighted-diff-doesn-t-stay-highlighted-on-refresh.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Highlight line number if specified on diff pages when page loads -merge_request: 9664 -author: diff --git a/changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml b/changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml deleted file mode 100644 index 48e62f8f70d..00000000000 --- a/changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix json response in branches controller -merge_request: 9710 -author: George Andrinopoulos diff --git a/changelogs/unreleased/28935-make-logo-smaller.yml b/changelogs/unreleased/28935-make-logo-smaller.yml deleted file mode 100644 index ef79fc7d212..00000000000 --- a/changelogs/unreleased/28935-make-logo-smaller.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Decrease tanuki logo size -merge_request: -author: diff --git a/changelogs/unreleased/29043-upgrade-vue-and-remove-warnings.yml b/changelogs/unreleased/29043-upgrade-vue-and-remove-warnings.yml new file mode 100644 index 00000000000..9055b23a13f --- /dev/null +++ b/changelogs/unreleased/29043-upgrade-vue-and-remove-warnings.yml @@ -0,0 +1,4 @@ +--- +title: Upgrade VueJS to v2.2.4 and disable dev mode warnings +merge_request: 9981 +author: diff --git a/changelogs/unreleased/29263-merge-button-color.yml b/changelogs/unreleased/29263-merge-button-color.yml deleted file mode 100644 index 2d0625483a4..00000000000 --- a/changelogs/unreleased/29263-merge-button-color.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: ensure MR widget dropdown is same color as button -merge_request: -author: diff --git a/changelogs/unreleased/29428-new-directory-from-existing-branch.yml b/changelogs/unreleased/29428-new-directory-from-existing-branch.yml new file mode 100644 index 00000000000..b3f7cd1f8f8 --- /dev/null +++ b/changelogs/unreleased/29428-new-directory-from-existing-branch.yml @@ -0,0 +1,4 @@ +--- +title: New directory from interface on existing branch +merge_request: 9921 +author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/29483-spam-check-only-title-and-description.yml b/changelogs/unreleased/29483-spam-check-only-title-and-description.yml new file mode 100644 index 00000000000..de8cacb250d --- /dev/null +++ b/changelogs/unreleased/29483-spam-check-only-title-and-description.yml @@ -0,0 +1,4 @@ +--- +title: Spam check only when spammable attributes have changed +merge_request: +author: diff --git a/changelogs/unreleased/29550-fix-quick-submit-on-preview.yml b/changelogs/unreleased/29550-fix-quick-submit-on-preview.yml new file mode 100644 index 00000000000..71214971ffd --- /dev/null +++ b/changelogs/unreleased/29550-fix-quick-submit-on-preview.yml @@ -0,0 +1,4 @@ +--- +title: Fix quick submit short-cut on preview tab for comments +merge_request: 10002 +author: diff --git a/changelogs/unreleased/29555-align-all-todo.yml b/changelogs/unreleased/29555-align-all-todo.yml new file mode 100644 index 00000000000..c1555a96a92 --- /dev/null +++ b/changelogs/unreleased/29555-align-all-todo.yml @@ -0,0 +1,4 @@ +--- +title: align Mark all as done with other Done buttons on Todos page +merge_request: +author: diff --git a/changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml b/changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml deleted file mode 100644 index d0a04b0a130..00000000000 --- a/changelogs/unreleased/29565-name-of-the-uncompressed-folder-of-a-tag-archive-changed.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix archive prefix bug for refs containing dots -merge_request: -author: diff --git a/changelogs/unreleased/29575-polling.yml b/changelogs/unreleased/29575-polling.yml new file mode 100644 index 00000000000..75016afd455 --- /dev/null +++ b/changelogs/unreleased/29575-polling.yml @@ -0,0 +1,4 @@ +--- +title: Adds polling utility function for vue resource +merge_request: +author: diff --git a/changelogs/unreleased/29604-v3-fix-branch-creation.yml b/changelogs/unreleased/29604-v3-fix-branch-creation.yml deleted file mode 100644 index 25687e8be97..00000000000 --- a/changelogs/unreleased/29604-v3-fix-branch-creation.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use "branch_name" instead "branch" on V3 branch creation API -merge_request: -author: diff --git a/changelogs/unreleased/29897-remove-force-scroll-for-mr-changes-diff.yml b/changelogs/unreleased/29897-remove-force-scroll-for-mr-changes-diff.yml new file mode 100644 index 00000000000..d1da96096f8 --- /dev/null +++ b/changelogs/unreleased/29897-remove-force-scroll-for-mr-changes-diff.yml @@ -0,0 +1,4 @@ +--- +title: Remove forced scroll into view when switching to Changes MR tab +merge_request: +author: diff --git a/changelogs/unreleased/29930-fix-profile-cover-button-a11y.yml b/changelogs/unreleased/29930-fix-profile-cover-button-a11y.yml new file mode 100644 index 00000000000..754d471c7d7 --- /dev/null +++ b/changelogs/unreleased/29930-fix-profile-cover-button-a11y.yml @@ -0,0 +1,4 @@ +--- +title: Add tooltip and accessibility for profile cover buttons +merge_request: 10182 +author: diff --git a/changelogs/unreleased/3440-remove-hsts-header.yml b/changelogs/unreleased/3440-remove-hsts-header.yml deleted file mode 100644 index 0310e733f4e..00000000000 --- a/changelogs/unreleased/3440-remove-hsts-header.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Stop setting Strict-Transport-Securty header from within the app -merge_request: -author: diff --git a/changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml b/changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml deleted file mode 100644 index 4a4932288b4..00000000000 --- a/changelogs/unreleased/3874-correctly-return-json-on-delete-responses.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Return 202 with JSON body on async removals on V4 API -merge_request: -author: diff --git a/changelogs/unreleased/4195-add-sorting-to-project-milestones.yml b/changelogs/unreleased/4195-add-sorting-to-project-milestones.yml new file mode 100644 index 00000000000..d4104dfa772 --- /dev/null +++ b/changelogs/unreleased/4195-add-sorting-to-project-milestones.yml @@ -0,0 +1,4 @@ +--- +title: Add dropdown sort to project milestones +merge_request: +author: George Andrinopoulos diff --git a/changelogs/unreleased/6073_project_api.yml b/changelogs/unreleased/6073_project_api.yml deleted file mode 100644 index fd6792a406e..00000000000 --- a/changelogs/unreleased/6073_project_api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API project create: Make name or path required' -merge_request: 9416 -author: diff --git a/changelogs/unreleased/9381-authentiq-backchannel-logout.yml b/changelogs/unreleased/9381-authentiq-backchannel-logout.yml deleted file mode 100644 index 4dbf36cd096..00000000000 --- a/changelogs/unreleased/9381-authentiq-backchannel-logout.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Adds remote logout functionality to the Authentiq OAuth provider -merge_request: 9381 -author: Alexandros Keramidas diff --git a/changelogs/unreleased/add-auto-submited-header.yml b/changelogs/unreleased/add-auto-submited-header.yml deleted file mode 100644 index 93481613b39..00000000000 --- a/changelogs/unreleased/add-auto-submited-header.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Set Auto-Submitted header to mails -merge_request: -author: Semyon Pupkov diff --git a/changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml b/changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml deleted file mode 100644 index d10e4cb7c87..00000000000 --- a/changelogs/unreleased/add-changelog-filtered-search-visual-tokens.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add filtered search visual tokens -merge_request: 8969 -author: diff --git a/changelogs/unreleased/add-filtered-search-to-mr.yml b/changelogs/unreleased/add-filtered-search-to-mr.yml deleted file mode 100644 index e3577e2aec7..00000000000 --- a/changelogs/unreleased/add-filtered-search-to-mr.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add filtered search to MR page -merge_request: -author: diff --git a/changelogs/unreleased/add-frequently-used-emojis-back-to-menu.yml b/changelogs/unreleased/add-frequently-used-emojis-back-to-menu.yml deleted file mode 100644 index 66d5bb63734..00000000000 --- a/changelogs/unreleased/add-frequently-used-emojis-back-to-menu.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add frequently used emojis back to awards menu -merge_request: -author: diff --git a/changelogs/unreleased/add-git-version-to-system-info.yml b/changelogs/unreleased/add-git-version-to-system-info.yml deleted file mode 100644 index 2827fcec28d..00000000000 --- a/changelogs/unreleased/add-git-version-to-system-info.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add git version to gitlab:env:info -merge_request: 9128 -author: Semyon Pupkov diff --git a/changelogs/unreleased/add-issue-modal-loading-indicator.yml b/changelogs/unreleased/add-issue-modal-loading-indicator.yml new file mode 100644 index 00000000000..5398399c018 --- /dev/null +++ b/changelogs/unreleased/add-issue-modal-loading-indicator.yml @@ -0,0 +1,4 @@ +--- +title: Shows loading icon in issue boards modal when changing filters +merge_request: +author: diff --git a/changelogs/unreleased/add-kube-ca-pem-file-deprecate-kube-ca-pem.yml b/changelogs/unreleased/add-kube-ca-pem-file-deprecate-kube-ca-pem.yml deleted file mode 100644 index 1ae1e3c7a7a..00000000000 --- a/changelogs/unreleased/add-kube-ca-pem-file-deprecate-kube-ca-pem.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add KUBE_CA_PEM_FILE, deprecate KUBE_CA_PEM -merge_request: 9398 -author: diff --git a/changelogs/unreleased/add-pipeline-triggers.yml b/changelogs/unreleased/add-pipeline-triggers.yml deleted file mode 100644 index 81b11da0bb2..00000000000 --- a/changelogs/unreleased/add-pipeline-triggers.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add pipeline trigger API with user permissions -merge_request: 9277 -author: diff --git a/changelogs/unreleased/add-test-backoff-util.yml b/changelogs/unreleased/add-test-backoff-util.yml new file mode 100644 index 00000000000..f3f3b99caec --- /dev/null +++ b/changelogs/unreleased/add-test-backoff-util.yml @@ -0,0 +1,4 @@ +--- +title: Added tests for the w.gl.utils.backOff promise +merge_request: +author: diff --git a/changelogs/unreleased/add-todos-shortcut.yml b/changelogs/unreleased/add-todos-shortcut.yml new file mode 100644 index 00000000000..41d42775937 --- /dev/null +++ b/changelogs/unreleased/add-todos-shortcut.yml @@ -0,0 +1,4 @@ +--- +title: Add `g t` global shortcut to go to todos +merge_request: +author: diff --git a/changelogs/unreleased/add-yarn-documentation.yml b/changelogs/unreleased/add-yarn-documentation.yml deleted file mode 100644 index 5bcc01ac177..00000000000 --- a/changelogs/unreleased/add-yarn-documentation.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: add rake tasks to handle yarn dependencies and update documentation -merge_request: 9316 -author: diff --git a/changelogs/unreleased/add_mr_info_to_issues_list.yml b/changelogs/unreleased/add_mr_info_to_issues_list.yml deleted file mode 100644 index 8087aa6296c..00000000000 --- a/changelogs/unreleased/add_mr_info_to_issues_list.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add merge request count to each issue on issues list -merge_request: 9252 -author: blackst0ne diff --git a/changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml b/changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml deleted file mode 100644 index ffcf197a596..00000000000 --- a/changelogs/unreleased/alphabetically_sort_tags_on_runner_list.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Alphabetically sort tags on runner list -merge_request: 8922 -author: blackst0ne diff --git a/changelogs/unreleased/api-drop-subscribed.yml b/changelogs/unreleased/api-drop-subscribed.yml deleted file mode 100644 index 2a39026b519..00000000000 --- a/changelogs/unreleased/api-drop-subscribed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove "subscribed" field from API responses returning list of issues or merge - requests -merge_request: 9661 -author: diff --git a/changelogs/unreleased/api-empty-return.yml b/changelogs/unreleased/api-empty-return.yml deleted file mode 100644 index 7810e83eb0e..00000000000 --- a/changelogs/unreleased/api-empty-return.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Return 204 for all delete endpoints' -merge_request: 9397 -author: Robert Schilling diff --git a/changelogs/unreleased/api-entities.yml b/changelogs/unreleased/api-entities.yml deleted file mode 100644 index 2003d00fd52..00000000000 --- a/changelogs/unreleased/api-entities.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "Use an entity for RepoBranch commits and enhance RepoCommit" -merge_request: 7138 -author: Ben Boeckel diff --git a/changelogs/unreleased/api-notes-entity-fields.yml b/changelogs/unreleased/api-notes-entity-fields.yml deleted file mode 100644 index f7631df31e2..00000000000 --- a/changelogs/unreleased/api-notes-entity-fields.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Remove deprecated fields Notes#upvotes and Notes#downvotes' -merge_request: 9384 -author: Robert Schilling diff --git a/changelogs/unreleased/api-post-block.yml b/changelogs/unreleased/api-post-block.yml deleted file mode 100644 index dfc61ffa9e3..00000000000 --- a/changelogs/unreleased/api-post-block.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Use POST to (un)block a user' -merge_request: 9371 -author: Robert Schilling diff --git a/changelogs/unreleased/api-remove-deploy-key-disable.yml b/changelogs/unreleased/api-remove-deploy-key-disable.yml deleted file mode 100644 index f471ad2aa20..00000000000 --- a/changelogs/unreleased/api-remove-deploy-key-disable.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Remove `DELETE projects/:id/deploy_keys/:key_id/disable`' -merge_request: 9365 -author: Robert Schilling diff --git a/changelogs/unreleased/api-remove-owned-groups.yml b/changelogs/unreleased/api-remove-owned-groups.yml deleted file mode 100644 index cf0301b7fe0..00000000000 --- a/changelogs/unreleased/api-remove-owned-groups.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Remove /groups/owned endpoint' -merge_request: 9505 -author: Robert Schilling diff --git a/changelogs/unreleased/api-star-restful.yml b/changelogs/unreleased/api-star-restful.yml deleted file mode 100644 index 3e7de8cd822..00000000000 --- a/changelogs/unreleased/api-star-restful.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar`' -merge_request: 9328 -author: Robert Schilling diff --git a/changelogs/unreleased/api-subscription-restful.yml b/changelogs/unreleased/api-subscription-restful.yml deleted file mode 100644 index 95db470e6c9..00000000000 --- a/changelogs/unreleased/api-subscription-restful.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: - Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource.' -merge_request: 9325 -author: Robert Schilling diff --git a/changelogs/unreleased/api-todos-restful.yml b/changelogs/unreleased/api-todos-restful.yml deleted file mode 100644 index dba1350a495..00000000000 --- a/changelogs/unreleased/api-todos-restful.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Use POST requests to mark todos as done' -merge_request: 9410 -author: Robert Schilling diff --git a/changelogs/unreleased/artifactsdoc.yml b/changelogs/unreleased/artifactsdoc.yml deleted file mode 100644 index 4ef32d5256f..00000000000 --- a/changelogs/unreleased/artifactsdoc.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added documentation for permalinks to most recent build artifacts. -merge_request: 8934 -author: Christian Godenschwager diff --git a/changelogs/unreleased/backup_storage_class.yml b/changelogs/unreleased/backup_storage_class.yml deleted file mode 100644 index fc9989fc251..00000000000 --- a/changelogs/unreleased/backup_storage_class.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add storage class configuration option for Amazon S3 remote backups -merge_request: -author: Jon Keys diff --git a/changelogs/unreleased/beautiful-karma-output.yml b/changelogs/unreleased/beautiful-karma-output.yml deleted file mode 100644 index 6ccddebab68..00000000000 --- a/changelogs/unreleased/beautiful-karma-output.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make Karma output look nicer for CI -merge_request: 9165 -author: winniehell diff --git a/changelogs/unreleased/branch_deletion.yml b/changelogs/unreleased/branch_deletion.yml deleted file mode 100644 index dbc9265a1fb..00000000000 --- a/changelogs/unreleased/branch_deletion.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: on branch deletion show loading icon and disabled the button -merge_request: 6761 -author: wendy0402 diff --git a/changelogs/unreleased/bugfix-systemhook.yml b/changelogs/unreleased/bugfix-systemhook.yml new file mode 100644 index 00000000000..4c4d0dcc7a2 --- /dev/null +++ b/changelogs/unreleased/bugfix-systemhook.yml @@ -0,0 +1,4 @@ +--- +title: Fix bug when system hook for deploy key +merge_request: 9796 +author: billy.lb diff --git a/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml b/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml deleted file mode 100644 index f335ae27fda..00000000000 --- a/changelogs/unreleased/bypass-email-domain-validation-when-created-by-admin.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Bypass email domain validation when a user is created by an admin. -merge_request: 8575 -author: Reza Mohammadi @remohammadi diff --git a/changelogs/unreleased/cleaner-additional-award-emoji-button.yml b/changelogs/unreleased/cleaner-additional-award-emoji-button.yml new file mode 100644 index 00000000000..84685f4bd45 --- /dev/null +++ b/changelogs/unreleased/cleaner-additional-award-emoji-button.yml @@ -0,0 +1,4 @@ +--- +title: Removed unnecessary 'add' text in additional award emoji button +merge_request: +author: diff --git a/changelogs/unreleased/clear-connections-before-starting-sidekiq.yml b/changelogs/unreleased/clear-connections-before-starting-sidekiq.yml deleted file mode 100644 index 8778fac6e9d..00000000000 --- a/changelogs/unreleased/clear-connections-before-starting-sidekiq.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Clear ActiveRecord connections before starting Sidekiq -merge_request: -author: diff --git a/changelogs/unreleased/commons-chunk-plugin.yml b/changelogs/unreleased/commons-chunk-plugin.yml deleted file mode 100644 index 5c11ea3bbb2..00000000000 --- a/changelogs/unreleased/commons-chunk-plugin.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use webpack CommonsChunkPlugin to place common javascript libraries in their - own bundles -merge_request: 9647 -author: diff --git a/changelogs/unreleased/copy-branch-to-clipboard.yml b/changelogs/unreleased/copy-branch-to-clipboard.yml deleted file mode 100644 index c12e324ed3c..00000000000 --- a/changelogs/unreleased/copy-branch-to-clipboard.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added the ability to copy a branch name to the clipboard -merge_request: 9103 -author: Glenn Sayers diff --git a/changelogs/unreleased/cover-my-karma.yml b/changelogs/unreleased/cover-my-karma.yml deleted file mode 100644 index 4a823dc5ca4..00000000000 --- a/changelogs/unreleased/cover-my-karma.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Reintroduce coverage report for JavaScript -merge_request: 9133 -author: winniehell diff --git a/changelogs/unreleased/create_branch_repo_less.yml b/changelogs/unreleased/create_branch_repo_less.yml deleted file mode 100644 index e8b14fa3b67..00000000000 --- a/changelogs/unreleased/create_branch_repo_less.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Creating a new branch from an issue will automatically initialize a repository if one doesn't already exist. -merge_request: -author: diff --git a/changelogs/unreleased/dashboard-filter-search-keep-params.yml b/changelogs/unreleased/dashboard-filter-search-keep-params.yml deleted file mode 100644 index a140715b7a2..00000000000 --- a/changelogs/unreleased/dashboard-filter-search-keep-params.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Dashboard project search keeps selected sort & filters -merge_request: -author: diff --git a/changelogs/unreleased/delete-artifacts-for-pages.yml b/changelogs/unreleased/delete-artifacts-for-pages.yml deleted file mode 100644 index 50b3dd81d60..00000000000 --- a/changelogs/unreleased/delete-artifacts-for-pages.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Delete artifacts for pages unless expiry date is specified -merge_request: 9716 -author: diff --git a/changelogs/unreleased/diff-make-obvious-cant-comment.yml b/changelogs/unreleased/diff-make-obvious-cant-comment.yml deleted file mode 100644 index 2cb95947939..00000000000 --- a/changelogs/unreleased/diff-make-obvious-cant-comment.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Visually show expanded diff lines cant have comments -merge_request: -author: diff --git a/changelogs/unreleased/dm-group-reference-full-name.yml b/changelogs/unreleased/dm-group-reference-full-name.yml deleted file mode 100644 index f445d955529..00000000000 --- a/changelogs/unreleased/dm-group-reference-full-name.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use full group name in GFM group reference title -merge_request: -author: diff --git a/changelogs/unreleased/dynamic-header-fixture.yml b/changelogs/unreleased/dynamic-header-fixture.yml deleted file mode 100644 index 9789a1999c8..00000000000 --- a/changelogs/unreleased/dynamic-header-fixture.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replace static fixture for header_spec.js -merge_request: 9174 -author: winniehell diff --git a/changelogs/unreleased/dynamic-project-title-fixture.yml b/changelogs/unreleased/dynamic-project-title-fixture.yml deleted file mode 100644 index 2404cbb891c..00000000000 --- a/changelogs/unreleased/dynamic-project-title-fixture.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replace static fixture for project_title_spec.js -merge_request: 9175 -author: winniehell diff --git a/changelogs/unreleased/dz-blacklist--names.yml b/changelogs/unreleased/dz-blacklist--names.yml deleted file mode 100644 index 2941965002d..00000000000 --- a/changelogs/unreleased/dz-blacklist--names.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Reserve few project and nested group paths that have wildcard routes associated -merge_request: 9898 -author: diff --git a/changelogs/unreleased/dz-change-project-view.yml b/changelogs/unreleased/dz-change-project-view.yml deleted file mode 100644 index 47e007a80a8..00000000000 --- a/changelogs/unreleased/dz-change-project-view.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Change default project view for user from readme to files view -merge_request: 9584 -author: diff --git a/changelogs/unreleased/dz-create-nested-groups-via-ui.yml b/changelogs/unreleased/dz-create-nested-groups-via-ui.yml deleted file mode 100644 index f9529a5941a..00000000000 --- a/changelogs/unreleased/dz-create-nested-groups-via-ui.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Allow creating nested groups via UI -merge_request: 8786 -author: diff --git a/changelogs/unreleased/dz-dashboard-groups-search.yml b/changelogs/unreleased/dz-dashboard-groups-search.yml deleted file mode 100644 index c473cba774d..00000000000 --- a/changelogs/unreleased/dz-dashboard-groups-search.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add filter and sorting to dashboard groups page -merge_request: 9619 -author: diff --git a/changelogs/unreleased/dz-nested-groups-api.yml b/changelogs/unreleased/dz-nested-groups-api.yml deleted file mode 100644 index d33ff42700f..00000000000 --- a/changelogs/unreleased/dz-nested-groups-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add nested groups to the API -merge_request: 9034 -author: diff --git a/changelogs/unreleased/dz-nested-groups-members.yml b/changelogs/unreleased/dz-nested-groups-members.yml deleted file mode 100644 index bab0c8465c2..00000000000 --- a/changelogs/unreleased/dz-nested-groups-members.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Show members of parent groups on project members page -merge_request: -author: diff --git a/changelogs/unreleased/dz-nested-groups-restrictions.yml b/changelogs/unreleased/dz-nested-groups-restrictions.yml deleted file mode 100644 index 2ffb6032525..00000000000 --- a/changelogs/unreleased/dz-nested-groups-restrictions.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Restrict nested group names to prevent ambiguous routes -merge_request: 9738 -author: diff --git a/changelogs/unreleased/dz-refactor-full-path.yml b/changelogs/unreleased/dz-refactor-full-path.yml deleted file mode 100644 index da8568fd220..00000000000 --- a/changelogs/unreleased/dz-refactor-full-path.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Store group and project full name and full path in routes table -merge_request: 8979 -author: diff --git a/changelogs/unreleased/etag-notes-polling.yml b/changelogs/unreleased/etag-notes-polling.yml deleted file mode 100644 index 53990821d25..00000000000 --- a/changelogs/unreleased/etag-notes-polling.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use ETag to improve performance of issue notes polling -merge_request: 9036 -author: diff --git a/changelogs/unreleased/expose-pagination-headers.yml b/changelogs/unreleased/expose-pagination-headers.yml deleted file mode 100644 index 1b4cd43fa06..00000000000 --- a/changelogs/unreleased/expose-pagination-headers.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'CORS: Whitelist pagination headers' -merge_request: 9651 -author: Robert Schilling diff --git a/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml b/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml deleted file mode 100644 index 7e626982de6..00000000000 --- a/changelogs/unreleased/fe-paginated-environments-api-add-subview.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Adds paginationd and folders view to environments table -merge_request: -author: diff --git a/changelogs/unreleased/feature-brand-logo-in-emails.yml b/changelogs/unreleased/feature-brand-logo-in-emails.yml deleted file mode 100644 index a7674b9b25e..00000000000 --- a/changelogs/unreleased/feature-brand-logo-in-emails.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Brand header logo for pipeline emails -merge_request: 9049 -author: Alexis Reigel diff --git a/changelogs/unreleased/feature-github-find-users-by-email.yml b/changelogs/unreleased/feature-github-find-users-by-email.yml deleted file mode 100644 index 1503cf2b9f7..00000000000 --- a/changelogs/unreleased/feature-github-find-users-by-email.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: GitHub Importer - Find users based on GitHub email address -merge_request: 8958 -author: diff --git a/changelogs/unreleased/feature-openid-connect.yml b/changelogs/unreleased/feature-openid-connect.yml deleted file mode 100644 index e84eb7aff86..00000000000 --- a/changelogs/unreleased/feature-openid-connect.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Implement OpenID Connect identity provider -merge_request: 8018 -author: Markus Koller diff --git a/changelogs/unreleased/feature-runner-jobs-v4-api.yml b/changelogs/unreleased/feature-runner-jobs-v4-api.yml deleted file mode 100644 index b24ea65266d..00000000000 --- a/changelogs/unreleased/feature-runner-jobs-v4-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add Runner's jobs v4 API -merge_request: 9273 -author: diff --git a/changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml b/changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml deleted file mode 100644 index e646a6a17b7..00000000000 --- a/changelogs/unreleased/feature-runners-registration-deletion-v4-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add Runner's registration/deletion v4 API -merge_request: 9246 -author: diff --git a/changelogs/unreleased/feature-syshook_commits.yml b/changelogs/unreleased/feature-syshook_commits.yml deleted file mode 100644 index 1305f5cd414..00000000000 --- a/changelogs/unreleased/feature-syshook_commits.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added commit array to Syshook json -merge_request: 9685 -author: Gabriele Pongelli diff --git a/changelogs/unreleased/feature-tokens-rake-task.yml b/changelogs/unreleased/feature-tokens-rake-task.yml new file mode 100644 index 00000000000..6c3845757db --- /dev/null +++ b/changelogs/unreleased/feature-tokens-rake-task.yml @@ -0,0 +1,4 @@ +--- +title: New rake task to reset all email and private tokens +merge_request: +author: diff --git a/changelogs/unreleased/filter-bar-fix-ie.yml b/changelogs/unreleased/filter-bar-fix-ie.yml new file mode 100644 index 00000000000..f1fa7d9b177 --- /dev/null +++ b/changelogs/unreleased/filter-bar-fix-ie.yml @@ -0,0 +1,4 @@ +--- +title: Fixed filtered search not working in IE +merge_request: +author: diff --git a/changelogs/unreleased/fix-ci-api-regression-for-after-script.yml b/changelogs/unreleased/fix-ci-api-regression-for-after-script.yml new file mode 100644 index 00000000000..cdd7d1e6945 --- /dev/null +++ b/changelogs/unreleased/fix-ci-api-regression-for-after-script.yml @@ -0,0 +1,4 @@ +--- +title: Fix after_script processing for Runners APIv4 +merge_request: 10185 +author: diff --git a/changelogs/unreleased/fix-cycle-analytics-events-limit.yml b/changelogs/unreleased/fix-cycle-analytics-events-limit.yml deleted file mode 100644 index 152b37ca430..00000000000 --- a/changelogs/unreleased/fix-cycle-analytics-events-limit.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add limit to the number of events showed in cycle analytics -merge_request: -author: diff --git a/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml b/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml deleted file mode 100644 index 605b5f01d0e..00000000000 --- a/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Deprecate usage of `types` configuration entry to describe CI/CD stages -merge_request: 9766 -author: diff --git a/changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml b/changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml deleted file mode 100644 index 17fd1336b8e..00000000000 --- a/changelogs/unreleased/fix-gb-notification-settings-when-no-repository.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Show notifications settings dropdown even if repository feature is disabled -merge_request: 9180 -author: diff --git a/changelogs/unreleased/fix-gb-passed-with-warnings-status-on-mysql.yml b/changelogs/unreleased/fix-gb-passed-with-warnings-status-on-mysql.yml deleted file mode 100644 index 6365b1a1910..00000000000 --- a/changelogs/unreleased/fix-gb-passed-with-warnings-status-on-mysql.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix "passed with warnings" stage status on MySQL installations -merge_request: 9802 -author: diff --git a/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml b/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml deleted file mode 100644 index 49e243ca6bb..00000000000 --- a/changelogs/unreleased/fix-gb-pipeline-retry-builds-started.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix CI/CD pipeline retry and take stages order into account -merge_request: 9021 -author: diff --git a/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml b/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml deleted file mode 100644 index d747e0e63a3..00000000000 --- a/changelogs/unreleased/fix-gb-pipeline-retry-cancel-buttons-consistency.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix pipeline retry and cancel buttons on pipeline details page -merge_request: 9225 -author: diff --git a/changelogs/unreleased/fix-gb-remove-deprecated-ci-build-status-badge.yml b/changelogs/unreleased/fix-gb-remove-deprecated-ci-build-status-badge.yml deleted file mode 100644 index 71ff768a190..00000000000 --- a/changelogs/unreleased/fix-gb-remove-deprecated-ci-build-status-badge.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove deprecated build status badge and related services -merge_request: 9620 -author: diff --git a/changelogs/unreleased/fix-gb-update-commit-status-api.yml b/changelogs/unreleased/fix-gb-update-commit-status-api.yml deleted file mode 100644 index aa4fcba4e89..00000000000 --- a/changelogs/unreleased/fix-gb-update-commit-status-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix updaing commit status when using optional attributes -merge_request: 9618 -author: diff --git a/changelogs/unreleased/fix-issue-23237.yml b/changelogs/unreleased/fix-issue-23237.yml new file mode 100644 index 00000000000..ed0ffc0684d --- /dev/null +++ b/changelogs/unreleased/fix-issue-23237.yml @@ -0,0 +1,4 @@ +--- +title: "Fixes an issue in the new merge request form, where a tag would be selected instead of a branch when they have the same names" +merge_request: 9535 +author: Weiqing Chu diff --git a/changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml b/changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml deleted file mode 100644 index ee827b7c939..00000000000 --- a/changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix issues mentioned but not closed for external issue trackers -merge_request: -author: diff --git a/changelogs/unreleased/fix-prometheus-including-d3-main-bundle.yml b/changelogs/unreleased/fix-prometheus-including-d3-main-bundle.yml deleted file mode 100644 index a42b0db3cfc..00000000000 --- a/changelogs/unreleased/fix-prometheus-including-d3-main-bundle.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Removed d3 from the main application.js bundle -merge_request: 10062 -author: diff --git a/changelogs/unreleased/fix-slow-queries-for-branches-index.yml b/changelogs/unreleased/fix-slow-queries-for-branches-index.yml deleted file mode 100644 index f5bd7003615..00000000000 --- a/changelogs/unreleased/fix-slow-queries-for-branches-index.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes n+1 query for tags and branches index page -merge_request: 9905 -author: diff --git a/changelogs/unreleased/fix_issue_from_milestone.yml b/changelogs/unreleased/fix_issue_from_milestone.yml deleted file mode 100644 index 02581e3ea09..00000000000 --- a/changelogs/unreleased/fix_issue_from_milestone.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: fix milestone does not automatically assign when create issue from milestone -merge_request: -author: diff --git a/changelogs/unreleased/fixes-namespace-api-documentation.yml b/changelogs/unreleased/fixes-namespace-api-documentation.yml deleted file mode 100644 index 6b578bb1602..00000000000 --- a/changelogs/unreleased/fixes-namespace-api-documentation.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update API docs for new namespace format -merge_request: 9073 -author: Markus Koller diff --git a/changelogs/unreleased/format-timeago-date.yml b/changelogs/unreleased/format-timeago-date.yml deleted file mode 100644 index f331c34abbc..00000000000 --- a/changelogs/unreleased/format-timeago-date.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Format timeago date to short format -merge_request: -author: diff --git a/changelogs/unreleased/gfm-autocomplete-fixes.yml b/changelogs/unreleased/gfm-autocomplete-fixes.yml deleted file mode 100644 index 737e2ad5234..00000000000 --- a/changelogs/unreleased/gfm-autocomplete-fixes.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix errors in slash commands matcher, add simple test coverage -merge_request: -author: YarNayar diff --git a/changelogs/unreleased/gitaly-post-receive.yml b/changelogs/unreleased/gitaly-post-receive.yml deleted file mode 100644 index cf206e39084..00000000000 --- a/changelogs/unreleased/gitaly-post-receive.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add internal API to notify Gitaly of post receive -merge_request: 8983 -author: diff --git a/changelogs/unreleased/group-memebrs-owner-level.yml b/changelogs/unreleased/group-memebrs-owner-level.yml deleted file mode 100644 index ba77f38eb6d..00000000000 --- a/changelogs/unreleased/group-memebrs-owner-level.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added option to update to owner for group members -merge_request: -author: diff --git a/changelogs/unreleased/instrument-in-karma.yml b/changelogs/unreleased/instrument-in-karma.yml deleted file mode 100644 index cfabf2569fe..00000000000 --- a/changelogs/unreleased/instrument-in-karma.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Move babel config for instanbul to karma config -merge_request: 9286 -author: winniehell diff --git a/changelogs/unreleased/introduce-pipeline-triggers.yml b/changelogs/unreleased/introduce-pipeline-triggers.yml deleted file mode 100644 index ce5a230d48f..00000000000 --- a/changelogs/unreleased/introduce-pipeline-triggers.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Introduce Pipeline Triggers that are user-aware -merge_request: -author: diff --git a/changelogs/unreleased/issue-boards-cant-drag-fix.yml b/changelogs/unreleased/issue-boards-cant-drag-fix.yml new file mode 100644 index 00000000000..ac92573abe8 --- /dev/null +++ b/changelogs/unreleased/issue-boards-cant-drag-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fixed bug in issue boards which stopped cards being able to be dragged +merge_request: +author: diff --git a/changelogs/unreleased/issue-descrpiption-spinner-off.yml b/changelogs/unreleased/issue-descrpiption-spinner-off.yml deleted file mode 100644 index 87104d09804..00000000000 --- a/changelogs/unreleased/issue-descrpiption-spinner-off.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed loading spinner position on issue template toggle -merge_request: -author: diff --git a/changelogs/unreleased/issue-newproj-layout.yml b/changelogs/unreleased/issue-newproj-layout.yml deleted file mode 100644 index d15e8b7d1e5..00000000000 --- a/changelogs/unreleased/issue-newproj-layout.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Removed duplicate "Visibility Level" label on New Project page -merge_request: -author: Robert Marcano diff --git a/changelogs/unreleased/issue-tags-layout.yml b/changelogs/unreleased/issue-tags-layout.yml deleted file mode 100644 index abf4a609932..00000000000 --- a/changelogs/unreleased/issue-tags-layout.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix 'New Tag' layout on Tags page -merge_request: -author: Robert Marcano diff --git a/changelogs/unreleased/issue_16834.yml b/changelogs/unreleased/issue_16834.yml deleted file mode 100644 index 06175579ac3..00000000000 --- a/changelogs/unreleased/issue_16834.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update API endpoints for raw files -merge_request: -author: diff --git a/changelogs/unreleased/issue_24815.yml b/changelogs/unreleased/issue_24815.yml deleted file mode 100644 index 916e47d36a9..00000000000 --- a/changelogs/unreleased/issue_24815.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix issuable stale object error handler for js when updating tasklists -merge_request: -author: diff --git a/changelogs/unreleased/issue_25900.yml b/changelogs/unreleased/issue_25900.yml deleted file mode 100644 index b4b72b8a20c..00000000000 --- a/changelogs/unreleased/issue_25900.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Gather issuable metadata to avoid n+1 queries on index view -merge_request: -author: diff --git a/changelogs/unreleased/issue_26701.yml b/changelogs/unreleased/issue_26701.yml deleted file mode 100644 index 6834351bf43..00000000000 --- a/changelogs/unreleased/issue_26701.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove JIRA closed status icon -merge_request: -author: diff --git a/changelogs/unreleased/list_issues_with_no_labels.yml b/changelogs/unreleased/list_issues_with_no_labels.yml deleted file mode 100644 index ab44841631b..00000000000 --- a/changelogs/unreleased/list_issues_with_no_labels.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Document ability to list issues with no labels using API -merge_request: 9697 -author: Vignesh Ravichandran diff --git a/changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml b/changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml deleted file mode 100644 index bd5db5ac7af..00000000000 --- a/changelogs/unreleased/lnovy-gitlab-ce-empty-variables.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'UI: Allow a project variable to be set to an empty value' -merge_request: 6044 -author: Lukáš Nový diff --git a/changelogs/unreleased/long-file-name-overflow.yml b/changelogs/unreleased/long-file-name-overflow.yml deleted file mode 100644 index 7ccf05491e1..00000000000 --- a/changelogs/unreleased/long-file-name-overflow.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed long file names overflowing under action buttons -merge_request: -author: diff --git a/changelogs/unreleased/migrate-pipeline-events-and-email-service.yml b/changelogs/unreleased/migrate-pipeline-events-and-email-service.yml deleted file mode 100644 index ce4d5092c17..00000000000 --- a/changelogs/unreleased/migrate-pipeline-events-and-email-service.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Migrate SlackService and MattermostService from build_events to pipeline_events, and migrate BuildsEmailService to PipelinesEmailService. Update Hipchat to use pipeline events rather than build events. -merge_request: 8196 -author: diff --git a/changelogs/unreleased/mock-ci-service.yml b/changelogs/unreleased/mock-ci-service.yml deleted file mode 100644 index 24c6366177f..00000000000 --- a/changelogs/unreleased/mock-ci-service.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add Mock CI service/integration for development -merge_request: -author: diff --git a/changelogs/unreleased/move_tags_service_to_namespace.yml b/changelogs/unreleased/move_tags_service_to_namespace.yml deleted file mode 100644 index ba76f291162..00000000000 --- a/changelogs/unreleased/move_tags_service_to_namespace.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Move tag services to Tags namespace -merge_request: -author: dixpac diff --git a/changelogs/unreleased/moving-issue-with-two-list-labels.yml b/changelogs/unreleased/moving-issue-with-two-list-labels.yml deleted file mode 100644 index d5ea81e3810..00000000000 --- a/changelogs/unreleased/moving-issue-with-two-list-labels.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Removes label when moving issue to another list that it is currently in -merge_request: -author: diff --git a/changelogs/unreleased/mr-diff-comment-button.yml b/changelogs/unreleased/mr-diff-comment-button.yml deleted file mode 100644 index 1dc6ed1c495..00000000000 --- a/changelogs/unreleased/mr-diff-comment-button.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Improved diff comment button UX -merge_request: -author: diff --git a/changelogs/unreleased/new-branch-fixture.yml b/changelogs/unreleased/new-branch-fixture.yml deleted file mode 100644 index ce5ed816102..00000000000 --- a/changelogs/unreleased/new-branch-fixture.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replace static fixture for new_branch_spec.js -merge_request: 9131 -author: winniehell diff --git a/changelogs/unreleased/only-create-unmergeable-todo-once.yml b/changelogs/unreleased/only-create-unmergeable-todo-once.yml deleted file mode 100644 index e675ed945ad..00000000000 --- a/changelogs/unreleased/only-create-unmergeable-todo-once.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Only create unmergeable todos once when MR fails to merge -merge_request: -author: diff --git a/changelogs/unreleased/only-yield-valid-reference-matches.yml b/changelogs/unreleased/only-yield-valid-reference-matches.yml deleted file mode 100644 index 95da3cc56fd..00000000000 --- a/changelogs/unreleased/only-yield-valid-reference-matches.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Only yield valid references in ReferenceFilter.references_in -merge_request: -author: diff --git a/changelogs/unreleased/pages-0-4-0.yml b/changelogs/unreleased/pages-0-4-0.yml deleted file mode 100644 index 7286b25125e..00000000000 --- a/changelogs/unreleased/pages-0-4-0.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use GitLab Pages v0.4.0 -merge_request: 9896 -author: diff --git a/changelogs/unreleased/paginate-all-the-things.yml b/changelogs/unreleased/paginate-all-the-things.yml deleted file mode 100644 index 52f23ba52a9..00000000000 --- a/changelogs/unreleased/paginate-all-the-things.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Paginate all endpoints that return an array' -merge_request: 8606 -author: Robert Schilling diff --git a/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml b/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml deleted file mode 100644 index 0751047c3c0..00000000000 --- a/changelogs/unreleased/pass in current_user in MergeRequest and MergeRequestsHelper.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: pass in current_user in MergeRequest and MergeRequestsHelper -merge_request: 8624 -author: Dongqing Hu diff --git a/changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml b/changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml deleted file mode 100644 index 74e0c18fa67..00000000000 --- a/changelogs/unreleased/pass_coverage_value_to_commit_status_api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make it possible to pass coverage value to commit status API -merge_request: 9214 -author: wendy0402 diff --git a/changelogs/unreleased/pipeline-blocking-actions.yml b/changelogs/unreleased/pipeline-blocking-actions.yml deleted file mode 100644 index 6bde501de18..00000000000 --- a/changelogs/unreleased/pipeline-blocking-actions.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Make it possible to configure blocking manual actions -merge_request: 9585 -author: diff --git a/changelogs/unreleased/pipelines-build-tooltip.yml b/changelogs/unreleased/pipelines-build-tooltip.yml new file mode 100644 index 00000000000..000276e1de3 --- /dev/null +++ b/changelogs/unreleased/pipelines-build-tooltip.yml @@ -0,0 +1,4 @@ +--- +title: Fixed job tooltip being cut-off +merge_request: +author: diff --git a/changelogs/unreleased/priority-to-label-priority.yml b/changelogs/unreleased/priority-to-label-priority.yml deleted file mode 100644 index 2d9c58bfd9b..00000000000 --- a/changelogs/unreleased/priority-to-label-priority.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rename priority sorting option to label priority -merge_request: -author: diff --git a/changelogs/unreleased/projects-list-line-breaks.yml b/changelogs/unreleased/projects-list-line-breaks.yml new file mode 100644 index 00000000000..179d7081293 --- /dev/null +++ b/changelogs/unreleased/projects-list-line-breaks.yml @@ -0,0 +1,4 @@ +--- +title: Fixed projects list lines breaking +merge_request: +author: diff --git a/changelogs/unreleased/protected-branch-dropdown-titles.yml b/changelogs/unreleased/protected-branch-dropdown-titles.yml deleted file mode 100644 index df82cc00fc9..00000000000 --- a/changelogs/unreleased/protected-branch-dropdown-titles.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added headers to protected branch access dropdowns -merge_request: -author: diff --git a/changelogs/unreleased/quick-submit-fixture.yml b/changelogs/unreleased/quick-submit-fixture.yml deleted file mode 100644 index a2cf05dabec..00000000000 --- a/changelogs/unreleased/quick-submit-fixture.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replace static fixture for behaviors/quick_submit_spec.js -merge_request: 9086 -author: winniehell diff --git a/changelogs/unreleased/remember-me-missasligned-mobile.yml b/changelogs/unreleased/remember-me-missasligned-mobile.yml new file mode 100644 index 00000000000..7071d32727f --- /dev/null +++ b/changelogs/unreleased/remember-me-missasligned-mobile.yml @@ -0,0 +1,4 @@ +--- +title: Corrected alignment for the remember-me checkbox in the login view +merge_request: +author: diff --git a/changelogs/unreleased/removal_of_unused_parameter.yml b/changelogs/unreleased/removal_of_unused_parameter.yml deleted file mode 100644 index 26bffafd9d9..00000000000 --- a/changelogs/unreleased/removal_of_unused_parameter.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'removed unused parameter ''status_only: true''' -merge_request: -author: diff --git a/changelogs/unreleased/remove-es6-extension.yml b/changelogs/unreleased/remove-es6-extension.yml deleted file mode 100644 index 65f4a7a7867..00000000000 --- a/changelogs/unreleased/remove-es6-extension.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove es6 file extension from JavaScript files -merge_request: 9241 -author: winniehell diff --git a/changelogs/unreleased/remove-inactive-default-email-services.yml b/changelogs/unreleased/remove-inactive-default-email-services.yml deleted file mode 100644 index c32c1390e4e..00000000000 --- a/changelogs/unreleased/remove-inactive-default-email-services.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove inactive default email services -merge_request: 8987 -author: diff --git a/changelogs/unreleased/remove-jquery-ui-datepicker.yml b/changelogs/unreleased/remove-jquery-ui-datepicker.yml deleted file mode 100644 index cd00690d774..00000000000 --- a/changelogs/unreleased/remove-jquery-ui-datepicker.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replaced jQuery UI datepicker -merge_request: -author: diff --git a/changelogs/unreleased/remove-jquery-ui-plugins.yml b/changelogs/unreleased/remove-jquery-ui-plugins.yml deleted file mode 100644 index c768f702ba2..00000000000 --- a/changelogs/unreleased/remove-jquery-ui-plugins.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Removed jQuery UI highlight & autocomplete -merge_request: -author: diff --git a/changelogs/unreleased/remove-jquery-ui-sortable.yml b/changelogs/unreleased/remove-jquery-ui-sortable.yml deleted file mode 100644 index 35f47822738..00000000000 --- a/changelogs/unreleased/remove-jquery-ui-sortable.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replaced jQuery UI sortable -merge_request: -author: diff --git a/changelogs/unreleased/remove-new-relic-gem.yml b/changelogs/unreleased/remove-new-relic-gem.yml deleted file mode 100644 index b15ecd3e4e7..00000000000 --- a/changelogs/unreleased/remove-new-relic-gem.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove the newrelic gem -merge_request: 9622 -author: Robert Schilling diff --git a/changelogs/unreleased/remove-readme-option.yml b/changelogs/unreleased/remove-readme-option.yml deleted file mode 100644 index 1d4c862c00e..00000000000 --- a/changelogs/unreleased/remove-readme-option.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove readme-only project view preference -merge_request: -author: diff --git a/changelogs/unreleased/remove-subscribe-label-tooltip.yml b/changelogs/unreleased/remove-subscribe-label-tooltip.yml deleted file mode 100644 index 90b71d3be51..00000000000 --- a/changelogs/unreleased/remove-subscribe-label-tooltip.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove tooltips from label subscription buttons -merge_request: -author: diff --git a/changelogs/unreleased/remove-unused-ci-tables.yml b/changelogs/unreleased/remove-unused-ci-tables.yml deleted file mode 100644 index fccfb882bd9..00000000000 --- a/changelogs/unreleased/remove-unused-ci-tables.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove various unused CI tables and columns -merge_request: 9639 -author: diff --git a/changelogs/unreleased/rename-ci_commits-to-ci_pipeline.yml b/changelogs/unreleased/rename-ci_commits-to-ci_pipeline.yml deleted file mode 100644 index 4067b3de00c..00000000000 --- a/changelogs/unreleased/rename-ci_commits-to-ci_pipeline.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rename table ci_commits to ci_pipelines -merge_request: 9638 -author: diff --git a/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml deleted file mode 100644 index b813127b1e6..00000000000 --- a/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rename retry failed button on pipeline page to just retry -merge_request: -author: diff --git a/changelogs/unreleased/rename_delete_services.yml b/changelogs/unreleased/rename_delete_services.yml deleted file mode 100644 index 686a1ef3d55..00000000000 --- a/changelogs/unreleased/rename_delete_services.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix inconsistent naming for services that delete things -merge_request: 5803 -author: dixpac diff --git a/changelogs/unreleased/rename_files_delete_service.yml b/changelogs/unreleased/rename_files_delete_service.yml deleted file mode 100644 index 4de1c5b0d63..00000000000 --- a/changelogs/unreleased/rename_files_delete_service.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rename Files::DeleteService to Files::DestroyService -merge_request: 9110 -author: dixpac diff --git a/changelogs/unreleased/replace-npm-with-yarn.yml b/changelogs/unreleased/replace-npm-with-yarn.yml deleted file mode 100644 index 5e795eb0c8d..00000000000 --- a/changelogs/unreleased/replace-npm-with-yarn.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: replace npm with yarn and add yarn.lock -merge_request: 9055 -author: diff --git a/changelogs/unreleased/replace_closing_mr_icon.yml b/changelogs/unreleased/replace_closing_mr_icon.yml new file mode 100644 index 00000000000..4d7b5fa67a7 --- /dev/null +++ b/changelogs/unreleased/replace_closing_mr_icon.yml @@ -0,0 +1,4 @@ +--- +title: Replace closing MR icon +merge_request: 10103 +author: blackst0ne diff --git a/changelogs/unreleased/requires-input-fixture.yml b/changelogs/unreleased/requires-input-fixture.yml deleted file mode 100644 index be674499429..00000000000 --- a/changelogs/unreleased/requires-input-fixture.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Replace static fixture for behaviors/requires_input_spec.js -merge_request: 9162 -author: winniehell diff --git a/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml b/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml deleted file mode 100644 index e799dd3b48d..00000000000 --- a/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Change project count limit from 10 to 100000 -merge_request: -author: diff --git a/changelogs/unreleased/routes-lower-case.yml b/changelogs/unreleased/routes-lower-case.yml deleted file mode 100644 index 2110956680c..00000000000 --- a/changelogs/unreleased/routes-lower-case.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove repeated routes.path check for postgresql database -merge_request: -author: mhasbini diff --git a/changelogs/unreleased/rss-btn-alignment-fix.yml b/changelogs/unreleased/rss-btn-alignment-fix.yml deleted file mode 100644 index c8f57ec0b7c..00000000000 --- a/changelogs/unreleased/rss-btn-alignment-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed RSS button alignment on activity pages -merge_request: -author: diff --git a/changelogs/unreleased/seed-abuse-reports.yml b/changelogs/unreleased/seed-abuse-reports.yml deleted file mode 100644 index 6fbcb81ae3f..00000000000 --- a/changelogs/unreleased/seed-abuse-reports.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Seed abuse reports for development -merge_request: -author: diff --git a/changelogs/unreleased/set-default-cache-key-for-jobs.yml b/changelogs/unreleased/set-default-cache-key-for-jobs.yml deleted file mode 100644 index b69348d2ece..00000000000 --- a/changelogs/unreleased/set-default-cache-key-for-jobs.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Set default cache key to "default" for jobs -merge_request: 9666 -author: diff --git a/changelogs/unreleased/settings-tab.yml b/changelogs/unreleased/settings-tab.yml deleted file mode 100644 index 69990c9a917..00000000000 --- a/changelogs/unreleased/settings-tab.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Moved project settings from the gear drop-down menu to a tab -merge_request: 9786 -author: diff --git a/changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml b/changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml deleted file mode 100644 index 57f1474093a..00000000000 --- a/changelogs/unreleased/sh-bump-hashie-to-3-5-5.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Bump Hashie to 3.5.5 and omniauth to 1.4.2 to eliminate warning noise -merge_request: -author: diff --git a/changelogs/unreleased/sh-delete-user-permission-check.yml b/changelogs/unreleased/sh-delete-user-permission-check.yml deleted file mode 100644 index c0e79aae2a8..00000000000 --- a/changelogs/unreleased/sh-delete-user-permission-check.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add user deletion permission check in `Users::DestroyService` -merge_request: -author: diff --git a/changelogs/unreleased/slow-search-changelog.yml b/changelogs/unreleased/slow-search-changelog.yml new file mode 100644 index 00000000000..d50cf1f94cd --- /dev/null +++ b/changelogs/unreleased/slow-search-changelog.yml @@ -0,0 +1,4 @@ +--- +title: Simplify search queries for projects and merge requests +merge_request: 10053 +author: mhasbini diff --git a/changelogs/unreleased/snippets-search.yml b/changelogs/unreleased/snippets-search.yml deleted file mode 100644 index 00cf34f4a48..00000000000 --- a/changelogs/unreleased/snippets-search.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix snippets search result spacing -merge_request: -author: diff --git a/changelogs/unreleased/sort-builds-in-stage-dropdown.yml b/changelogs/unreleased/sort-builds-in-stage-dropdown.yml deleted file mode 100644 index 646f25125b1..00000000000 --- a/changelogs/unreleased/sort-builds-in-stage-dropdown.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Sort builds in stage dropdown -merge_request: -author: diff --git a/changelogs/unreleased/ssh-key-paste.yml b/changelogs/unreleased/ssh-key-paste.yml deleted file mode 100644 index 1e34ef60f6e..00000000000 --- a/changelogs/unreleased/ssh-key-paste.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: SSH key field updates title after pasting key -merge_request: -author: diff --git a/changelogs/unreleased/static-navbar.yml b/changelogs/unreleased/static-navbar.yml deleted file mode 100644 index eaf478a48d0..00000000000 --- a/changelogs/unreleased/static-navbar.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove fixed positioning from top nav -merge_request: !7547 -author: diff --git a/changelogs/unreleased/task_list_refactor.yml b/changelogs/unreleased/task_list_refactor.yml deleted file mode 100644 index 68942dadaa8..00000000000 --- a/changelogs/unreleased/task_list_refactor.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Deduplicate markdown task lists -merge_request: -author: diff --git a/changelogs/unreleased/tc-api-pipeline-jobs.yml b/changelogs/unreleased/tc-api-pipeline-jobs.yml deleted file mode 100644 index 993c1b6526a..00000000000 --- a/changelogs/unreleased/tc-api-pipeline-jobs.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint -merge_request: 9727 -author: diff --git a/changelogs/unreleased/tc-fix-project-create-500.yml b/changelogs/unreleased/tc-fix-project-create-500.yml deleted file mode 100644 index 1b746a41eab..00000000000 --- a/changelogs/unreleased/tc-fix-project-create-500.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix for creating a project through API when import_url is nil -merge_request: 9841 -author: diff --git a/changelogs/unreleased/tc-pipeline-show-trigger-date.yml b/changelogs/unreleased/tc-pipeline-show-trigger-date.yml new file mode 100644 index 00000000000..4de784d98f3 --- /dev/null +++ b/changelogs/unreleased/tc-pipeline-show-trigger-date.yml @@ -0,0 +1,4 @@ +--- +title: Show correct user & creation time in heading of the pipeline page +merge_request: 9936 +author: diff --git a/changelogs/unreleased/time-tracking-color-not-consistent.yml b/changelogs/unreleased/time-tracking-color-not-consistent.yml new file mode 100644 index 00000000000..50ec9efb1ff --- /dev/null +++ b/changelogs/unreleased/time-tracking-color-not-consistent.yml @@ -0,0 +1,4 @@ +--- +title: Corrected time tracking icon color in the issuable side bar +merge_request: +author: diff --git a/changelogs/unreleased/unified-member-api-response.yml b/changelogs/unreleased/unified-member-api-response.yml deleted file mode 100644 index 0a60b4d46a3..00000000000 --- a/changelogs/unreleased/unified-member-api-response.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'API: Return 400 for all validation erros in the mebers API' -merge_request: 9523 -author: Robert Schilling diff --git a/changelogs/unreleased/update-ace.yml b/changelogs/unreleased/update-ace.yml deleted file mode 100644 index dbe476e3ae0..00000000000 --- a/changelogs/unreleased/update-ace.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update code editor (ACE) to 1.2.6, to fix input problems with compose key -merge_request: -author: diff --git a/changelogs/unreleased/update-vue-2-1.yml b/changelogs/unreleased/update-vue-2-1.yml deleted file mode 100644 index acc42bf00b1..00000000000 --- a/changelogs/unreleased/update-vue-2-1.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: update Vue to v2.1.10 -merge_request: 9386 -author: diff --git a/changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml b/changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml deleted file mode 100644 index ff5a58f6232..00000000000 --- a/changelogs/unreleased/use-redis-channel-to-post-runner-notifcations.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use redis channel to post notifications -merge_request: -author: diff --git a/changelogs/unreleased/user-calendar-border.yml b/changelogs/unreleased/user-calendar-border.yml deleted file mode 100644 index 8ebcca83256..00000000000 --- a/changelogs/unreleased/user-calendar-border.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Removed top border from user contribution calendar -merge_request: -author: diff --git a/changelogs/unreleased/user-callout-showing-on-all-profiles.yml b/changelogs/unreleased/user-callout-showing-on-all-profiles.yml new file mode 100644 index 00000000000..b8eb5a149b7 --- /dev/null +++ b/changelogs/unreleased/user-callout-showing-on-all-profiles.yml @@ -0,0 +1,4 @@ +--- +title: User callout only shows on current users profile +merge_request: +author: diff --git a/changelogs/unreleased/user-callouts.yml b/changelogs/unreleased/user-callouts.yml deleted file mode 100644 index f6ce06a3d8f..00000000000 --- a/changelogs/unreleased/user-callouts.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Added user callouts to the projects dashboard and user profile -merge_request: -author: diff --git a/changelogs/unreleased/user-profile-join-date.yml b/changelogs/unreleased/user-profile-join-date.yml new file mode 100644 index 00000000000..f9d78b0dc3e --- /dev/null +++ b/changelogs/unreleased/user-profile-join-date.yml @@ -0,0 +1,4 @@ +--- +title: Removed the hours & minutes from the users start date on their profile +merge_request: +author: diff --git a/changelogs/unreleased/workhorse-1-4-0.yml b/changelogs/unreleased/workhorse-1-4-0.yml deleted file mode 100644 index b55fabddb0f..00000000000 --- a/changelogs/unreleased/workhorse-1-4-0.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use gitlab-workhorse 1.4.0 -merge_request: 9724 -author: diff --git a/changelogs/unreleased/zj-builds-to-jobs-api.yml b/changelogs/unreleased/zj-builds-to-jobs-api.yml deleted file mode 100644 index 473dd9bc8ed..00000000000 --- a/changelogs/unreleased/zj-builds-to-jobs-api.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rename builds to job for the v4 API -merge_request: 9463 -author: diff --git a/changelogs/unreleased/zj-chat-notification-default-branch.yml b/changelogs/unreleased/zj-chat-notification-default-branch.yml new file mode 100644 index 00000000000..fa0052d5034 --- /dev/null +++ b/changelogs/unreleased/zj-chat-notification-default-branch.yml @@ -0,0 +1,4 @@ +--- +title: Only send chat notifications for the default branch +merge_request: +author: diff --git a/changelogs/unreleased/zj-variables-build-job.yml b/changelogs/unreleased/zj-variables-build-job.yml deleted file mode 100644 index 1cb0919f824..00000000000 --- a/changelogs/unreleased/zj-variables-build-job.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rename job environment variables to new terminology -merge_request: 9756 -author: diff --git a/config/environments/test.rb b/config/environments/test.rb index fb25d3a8b14..a25c5016a3b 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,7 @@ Rails.application.configure do + # Make sure the middleware is inserted first in middleware chain + config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestBlockerMiddleware') + # Settings specified here will take precedence over those in config/application.rb # The test environment is used exclusively to run your application's diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index ba7f6773985..3747baf4c3b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -450,7 +450,7 @@ production: &base # This setting is obsolete because we expect it to be moved under # repositories/storages in GitLab 9.1. # - # socket_path: tmp/sockets/gitaly.socket + # socket_path: tmp/sockets/private/gitaly.socket # # 4. Advanced settings diff --git a/config/webpack.config.js b/config/webpack.config.js index c6794d6b944..0859c8416c8 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -18,10 +18,10 @@ var config = { context: path.join(ROOT_PATH, 'app/assets/javascripts'), entry: { common: './commons/index.js', - common_vue: ['vue', 'vue-resource'], + common_vue: ['vue', './vue_shared/common_vue.js'], common_d3: ['d3'], main: './main.js', - blob_edit: './blob_edit/blob_edit_bundle.js', + blob: './blob_edit/blob_bundle.js', boards: './boards/boards_bundle.js', simulate_drag: './test_utils/simulate_drag.js', cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js', @@ -132,7 +132,7 @@ var config = { 'empty_states': path.join(ROOT_PATH, 'app/views/shared/empty_states'), 'icons': path.join(ROOT_PATH, 'app/views/shared/icons'), 'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'), - 'vue$': 'vue/dist/vue.common.js', + 'vue$': 'vue/dist/vue.esm.js', } } } diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index aea0a72b633..4bc735916c1 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -155,7 +155,7 @@ class Gitlab::Seeder::CycleAnalytics issue.project.repository.add_branch(@user, branch_name, 'master') - commit_sha = issue.project.repository.create_file(@user, filename, "content", options, message: "Commit for ##{issue.iid}", branch_name: branch_name) + commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for ##{issue.iid}", branch_name: branch_name) issue.project.repository.commit(commit_sha) GitPushService.new(issue.project, diff --git a/db/migrate/20170313213916_add_index_to_user_ghost.rb b/db/migrate/20170313213916_add_index_to_user_ghost.rb new file mode 100644 index 00000000000..c429039c275 --- /dev/null +++ b/db/migrate/20170313213916_add_index_to_user_ghost.rb @@ -0,0 +1,24 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIndexToUserGhost < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + disable_ddl_transaction! + + def up + add_concurrent_index :users, :ghost + end + + def down + remove_index :users, :ghost + end +end diff --git a/db/migrate/20170317203554_index_routes_path_for_like.rb b/db/migrate/20170317203554_index_routes_path_for_like.rb new file mode 100644 index 00000000000..7ac09b2abe5 --- /dev/null +++ b/db/migrate/20170317203554_index_routes_path_for_like.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class IndexRoutesPathForLike < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + INDEX_NAME = 'index_routes_on_path_text_pattern_ops' + + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + unless index_exists?(:routes, :path, name: INDEX_NAME) + execute("CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON routes (path varchar_pattern_ops);") + end + end + + def down + return unless Gitlab::Database.postgresql? + + if index_exists?(:routes, :path, name: INDEX_NAME) + execute("DROP INDEX CONCURRENTLY #{INDEX_NAME};") + end + 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 9dfe77bedb7..44c688fa134 100644 --- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb +++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb @@ -6,41 +6,12 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration DOWNTIME = false - THREAD_COUNT = 8 - KNOWN_PATHS = %w(artifacts graphs refs badges).freeze def up - queues = Array.new(THREAD_COUNT) { Queue.new } - start = false - - threads = Array.new(THREAD_COUNT) do |index| - Thread.new do - queue = queues[index] - - # Wait until we have input to process. - until start; end - - rename_projects(queue.pop) until queue.empty? - end - end - - enum = queues.each - reserved_projects.each_slice(100) do |slice| - begin - queue = enum.next - rescue StopIteration - enum.rewind - retry - end - - queue << slice + rename_projects(slice) end - - start = true - - threads.each(&:join) end def down diff --git a/db/schema.rb b/db/schema.rb index bddcab8d75e..47833b7e518 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170317131326) do +ActiveRecord::Schema.define(version: 20170317203554) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -985,6 +985,7 @@ ActiveRecord::Schema.define(version: 20170317131326) do end add_index "routes", ["path"], name: "index_routes_on_path", unique: true, using: :btree + add_index "routes", ["path"], name: "index_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"} add_index "routes", ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true, using: :btree create_table "sent_notifications", force: :cascade do |t| @@ -1245,6 +1246,7 @@ ActiveRecord::Schema.define(version: 20170317131326) do add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} + add_index "users", ["ghost"], name: "index_users_on_ghost", using: :btree add_index "users", ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md new file mode 100644 index 00000000000..30a4c08508d --- /dev/null +++ b/doc/administration/gitaly/index.md @@ -0,0 +1,86 @@ +# Gitaly + +[Gitaly](https://gitlab.com/gitlab-org/gitlay) (introduced in GitLab +9.0) is a service that provides high-level RPC access to Git +repositories. As of GitLab 9.0 it is still an optional component with +limited scope. + +GitLab components that access Git repositories (gitlab-rails, +gitlab-shell, gitlab-workhorse) act as clients to Gitaly. End users do +not have direct access to Gitaly. + +## Configuring Gitaly + +The Gitaly service itself is configured via environment variables. +These variables are documented [in the gitaly +repository](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md). + +To change a Gitaly environment variable in Omnibus you can use +`gitaly['env']` in `/etc/gitlab/gitlab.rb`. Changes will be applied +when you run `gitlab-ctl reconfigure`. + +```ruby +gitaly['env'] = { + 'GITALY_MY_VARIABLE' => 'value' +} +``` + +To change a Gitaly environment variable in installations from source +you can edit `/home/git/gitaly/env`. + +```shell +GITALY_MY_VARIABLE='value' +``` + +Changes to `/home/git/gitaly/env` are applied when you run `service +gitlab restart`. + +## Configuring GitLab to not use Gitaly + +Gitaly is still an optional component in GitLab 9.0. This means you +can choose to not use it. + +In Omnibus you can make the following change in +`/etc/gitlab/gitlab.rb` and reconfigure. This will both disable the +Gitaly service and configure the rest of GitLab not to use it. + +```ruby +gitaly['enable'] = false +``` + +In source installations, edit `/home/git/gitlab/config/gitlab.yml` and +make sure `socket_path` in the `gitaly` section is commented out. This +does not disable the Gitaly service; it only prevents it from being +used. + +Apply the change with `service gitlab restart`. + +```yaml + gitaly: + # socket_path: tmp/sockets/private/gitlay.socket +``` + +## Disabling or enabling the Gitaly service + +Be careful: if you disable Gitaly without instructing the rest of your +GitLab installation not to use Gitaly, you may end up with errors +because GitLab tries to access a service that is not running. + +To disable the Gitaly service in your Omnibus installation, add the +following line to `/etc/gitlab/gitlab.rb`: + +```ruby +gitaly['enable'] = false +``` + +When you run `gitlab-ctl reconfigure` the Gitaly service will be +disabled. + +To disable the Gitaly service in an installation from source, add the +following to `/etc/default/gitlab`: + +```shell +gitaly_enabled=false +``` + +When you run `service gitlab restart` Gitaly will be disabled.
\ No newline at end of file diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md index 0a08591c3ce..cf3aca106e9 100644 --- a/doc/administration/high_availability/database.md +++ b/doc/administration/high_availability/database.md @@ -13,6 +13,8 @@ Database Service (RDS) that runs PostgreSQL. If you use a cloud-managed service, or provide your own PostgreSQL: +1. Setup PostgreSQL according to the + [database requirements document](doc/install/requirements.md#database). 1. Set up a `gitlab` username with a password of your choice. The `gitlab` user needs privileges to create the `gitlabhq_production` database. 1. Configure the GitLab application servers with the appropriate details. diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index 3893d837006..bf1aa6b9ac5 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -26,7 +26,7 @@ options: circumstances it could lead to data loss if a failure occurs before data has synced. -## Client mount options +## NFS Client mount options Below is an example of an NFS mount point we use on GitLab.com: diff --git a/doc/api/README.md b/doc/api/README.md index 58d090b8f5e..e627b6f2ee8 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -74,6 +74,12 @@ returned with status code `401`: } ``` +### Session Cookie + +When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is +set. The API will use this cookie for authentication if it is present, but using +the API to generate a new session cookie is currently not supported. + ### Private Tokens You need to pass a `private_token` parameter via query string or header. If passed as a @@ -113,65 +119,25 @@ moment – `read_user` and `api` – the groundwork has been laid to add more sc At any time you can revoke any personal access token by just clicking **Revoke**. -### Session Cookie +### Impersonation tokens -When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is -set. The API will use this cookie for authentication if it is present, but using -the API to generate a new session cookie is currently not supported. +> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions. -## Basic Usage +Impersonation tokens are a type of [Personal Access Token](#personal-access-tokens) +that can only be created by an admin for a specific user. -API requests should be prefixed with `api` and the API version. The API version -is defined in [`lib/api.rb`][lib-api-url]. +They are a better alternative to using the user's password/private token +or using the [Sudo](#sudo) feature which also requires the admin's password +or private token, since the password/token can change over time. Impersonation +tokens are a great fit if you want to build applications or tools which +authenticate with the API as a specific user. -Example of a valid API request: +For more information about the usage please refer to the +[users API](users.md#retrieve-user-impersonation-tokens) docs. -```shell -GET https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK -``` - -Example of a valid API request using cURL and authentication via header: - -```shell -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" -``` - -The API uses JSON to serialize data. You don't need to specify `.json` at the -end of an API URL. - -## Status codes - -The API is designed to return different status codes according to context and -action. This way, if a request results in an error, the caller is able to get -insight into what went wrong. - -The following table gives an overview of how the API functions generally behave. - -| Request type | Description | -| ------------ | ----------- | -| `GET` | Access one or more resources and return the result as JSON. | -| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. | -| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. | -| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. | - -The following table shows the possible return codes for API requests. - -| Return values | Description | -| ------------- | ----------- | -| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | -| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | -| `201 Created` | The `POST` request was successful and the resource is returned as JSON. | -| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | -| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | -| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. | -| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. | -| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. | -| `405 Method Not Allowed` | The request is not supported. | -| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. | -| `422 Unprocessable` | The entity could not be processed. | -| `500 Server Error` | While handling the request something went wrong server-side. | +### Sudo -## Sudo +> Needs admin permissions. All API requests support performing an API call as if you were another user, provided your private token is from an administrator account. You need to pass @@ -202,7 +168,7 @@ returned with status code `404`: Example of a valid API call and a request using cURL with sudo request, providing a username: -```shell +``` GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username ``` @@ -213,7 +179,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "h Example of a valid API call and a request using cURL with sudo request, providing an ID: -```shell +``` GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 ``` @@ -221,13 +187,57 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" ``` -## Impersonation Tokens +## Basic Usage + +API requests should be prefixed with `api` and the API version. The API version +is defined in [`lib/api.rb`][lib-api-url]. + +Example of a valid API request: + +``` +GET /projects?private_token=9koXpg98eAheJpvBs5tK +``` + +Example of a valid API request using cURL and authentication via header: + +```shell +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" +``` + +The API uses JSON to serialize data. You don't need to specify `.json` at the +end of an API URL. + +## Status codes + +The API is designed to return different status codes according to context and +action. This way, if a request results in an error, the caller is able to get +insight into what went wrong. + +The following table gives an overview of how the API functions generally behave. + +| Request type | Description | +| ------------ | ----------- | +| `GET` | Access one or more resources and return the result as JSON. | +| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. | +| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. | +| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. | -Impersonation Tokens are a type of Personal Access Token that can only be created by an admin for a specific user. These can be used by automated tools -to authenticate with the API as a specific user, as a better alternative to using the user's password or private token directly, which may change over time, -and to using the [Sudo](#sudo) feature, which requires the tool to know an admin's password or private token, which can change over time as well and are extremely powerful. +The following table shows the possible return codes for API requests. -For more information about the usage please refer to the [Users](users.md) page +| Return values | Description | +| ------------- | ----------- | +| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. | +| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. | +| `201 Created` | The `POST` request was successful and the resource is returned as JSON. | +| `304 Not Modified` | Indicates that the resource has not been modified since the last request. | +| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. | +| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. | +| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. | +| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. | +| `405 Method Not Allowed` | The request is not supported. | +| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. | +| `422 Unprocessable` | The entity could not be processed. | +| `500 Server Error` | While handling the request something went wrong server-side. | ## Pagination @@ -307,14 +317,14 @@ For example, an issue might have `id: 46` and `iid: 5`. That means that if you want to get an issue via the API you should use the `id`: -```bash +``` GET /projects/42/issues/:id ``` On the other hand, if you want to create a link to a web page you should use the `iid`: -```bash +``` GET /projects/42/issues/:iid ``` @@ -398,3 +408,4 @@ programming languages. Visit the [GitLab website] for a complete list. [lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb [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-9099]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9099 diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md index fdb41a1d615..50fc19f0e08 100644 --- a/doc/api/pipeline_triggers.md +++ b/doc/api/pipeline_triggers.md @@ -41,10 +41,10 @@ Get details of project's build trigger. GET /projects/:id/triggers/:trigger_id ``` -| Attribute | Type | required | Description | -|-----------|---------|----------|--------------------------| -| `id` | integer | yes | The ID of a project | -| `token` | string | yes | The `token` of a trigger | +| Attribute | Type | required | Description | +|--------------|---------|----------|--------------------------| +| `id` | integer | yes | The ID of a project | +| `trigger_id` | integer | yes | The trigger id | ``` curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5" @@ -103,6 +103,7 @@ PUT /projects/:id/triggers/:trigger_id | Attribute | Type | required | Description | |---------------|---------|----------|--------------------------| +| `id` | integer | yes | The ID of a project | | `trigger_id` | integer | yes | The trigger id | | `description` | string | no | The trigger name | @@ -133,6 +134,7 @@ POST /projects/:id/triggers/:trigger_id/take_ownership | Attribute | Type | required | Description | |---------------|---------|----------|--------------------------| +| `id` | integer | yes | The ID of a project | | `trigger_id` | integer | yes | The trigger id | ``` diff --git a/doc/api/users.md b/doc/api/users.md index 14b5c6c713e..2ada4d09c84 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -828,10 +828,12 @@ Example response: ] ``` -## Retrieve user impersonation tokens +## Get all impersonation tokens of a user -It retrieves every impersonation token of the user. Note that only administrators can do this. -This function takes pagination parameters `page` and `per_page` to restrict the list of impersonation tokens. +> Requires admin permissions. + +It retrieves every impersonation token of the user. Use the pagination +parameters `page` and `per_page` to restrict the list of impersonation tokens. ``` GET /users/:user_id/impersonation_tokens @@ -842,27 +844,50 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `user_id` | integer | yes | The ID of the user | -| `state` | string | no | filter tokens based on state (all, active, inactive) | +| `state` | string | no | filter tokens based on state (`all`, `active`, `inactive`) | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens +``` Example response: + ```json [ - { - "id": 1, - "name": "mytoken", - "revoked": false, - "expires_at": "2017-01-04", - "scopes": ['api'], - "active": true, - "impersonation": true, - "token": "9koXpg98eAheJpvBs5tK" - } + { + "active" : true, + "token" : "EsMo-vhKfXGwX9RKrwiy", + "scopes" : [ + "api" + ], + "revoked" : false, + "name" : "mytoken", + "id" : 2, + "created_at" : "2017-03-17T17:18:09.283Z", + "impersonation" : true, + "expires_at" : "2017-04-04" + }, + { + "active" : false, + "scopes" : [ + "read_user" + ], + "revoked" : true, + "token" : "ZcZRpLeEuQRprkRjYydY", + "name" : "mytoken2", + "created_at" : "2017-03-17T17:19:28.697Z", + "id" : 3, + "impersonation" : true, + "expires_at" : "2017-04-14" + } ] ``` -## Show a user's impersonation token +## Get an impersonation token of a user -It shows a user's impersonation token. Note that only administrators can do this. +> Requires admin permissions. + +It shows a user's impersonation token. ``` GET /users/:user_id/impersonation_tokens/:impersonation_token_id @@ -875,7 +900,31 @@ Parameters: | `user_id` | integer | yes | The ID of the user | | `impersonation_token_id` | integer | yes | The ID of the impersonation token | -## Create a impersonation token +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/2 +``` + +Example response: + +```json +{ + "active" : true, + "token" : "EsMo-vhKfXGwX9RKrwiy", + "scopes" : [ + "api" + ], + "revoked" : false, + "name" : "mytoken", + "id" : 2, + "created_at" : "2017-03-17T17:18:09.283Z", + "impersonation" : true, + "expires_at" : "2017-04-04" +} +``` + +## Create an impersonation token + +> Requires admin permissions. It creates a new impersonation token. Note that only administrators can do this. You are only able to create impersonation tokens to impersonate the user and perform @@ -891,32 +940,46 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `user_id` | integer | yes | The ID of the user | -| `name` | string | yes | The name of the impersonation token | -| `expires_at` | date | no | The expiration date of the impersonation token | -| `scopes` | array | no | The array of scopes of the impersonation token (api, read_user) | +| `name` | string | yes | The name of the impersonation token | +| `expires_at` | date | no | The expiration date of the impersonation token in ISO format (`YYYY-MM-DD`)| +| `scopes` | array | yes | The array of scopes of the impersonation token (`api`, `read_user`) | + +``` +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=mytoken" --data "expires_at=2017-04-04" --data "scopes[]=api" https://gitlab.example.com/api/v4/users/42/impersonation_tokens +``` Example response: + ```json { - "id": 1, - "name": "mytoken", - "revoked": false, - "expires_at": "2017-01-04", - "scopes": ['api'], - "active": true, - "impersonation": true, - "token": "9koXpg98eAheJpvBs5tK" + "id" : 2, + "revoked" : false, + "scopes" : [ + "api" + ], + "token" : "EsMo-vhKfXGwX9RKrwiy", + "active" : true, + "impersonation" : true, + "name" : "mytoken", + "created_at" : "2017-03-17T17:18:09.283Z", + "expires_at" : "2017-04-04" } ``` ## Revoke an impersonation token -It revokes an impersonation token. Note that only administrators can revoke impersonation tokens. +> Requires admin permissions. + +It revokes an impersonation token. ``` DELETE /users/:user_id/impersonation_tokens/:impersonation_token_id ``` +``` +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/1 +``` + Parameters: | Attribute | Type | Required | Description | diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index 0794156bc39..7f4426ee85d 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -8,16 +8,16 @@ Below are the changes made between V3 and V4. ### 8.17 -- Removed `/projects/:search` (use: `/projects?search=x`) [!8877](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8877) -- `iid` filter has been removed from `projects/:id/issues` [!8967](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8967) -- `projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) -- Endpoints under `projects/merge_request/:id` have been removed (use: `projects/merge_requests/:id`) [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) +- Removed `GET /projects/:search` (use: `GET /projects?search=x`) [!8877](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8877) +- `iid` filter has been removed from `GET /projects/:id/issues` [!8967](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8967) +- `GET /projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) +- Endpoints under `GET /projects/merge_request/:id` have been removed (use: `GET /projects/merge_requests/:id`) [!8793](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8793) - Project snippets do not return deprecated field `expires_at` [!8723](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8723) -- Endpoints under `projects/:id/keys` have been removed (use `projects/:id/deploy_keys`) [!8716](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8716) +- Endpoints under `GET /projects/:id/keys` have been removed (use `GET /projects/:id/deploy_keys`) [!8716](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8716) ### 9.0 -- Status 409 returned for POST `project/:id/members` when a member already exists [!9093](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9093) +- Status 409 returned for `POST /projects/:id/members` when a member already exists [!9093](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9093) - Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar` [!9328](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9328) - Removed the following deprecated Templates endpoints (these are still accessible with `/templates` prefix) [!8853](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8853) - `/licences` @@ -28,31 +28,31 @@ Below are the changes made between V3 and V4. - `/gitignores/:key` - `/gitlab_ci_ymls/:key` - `/dockerfiles/:key` -- Moved `/projects/fork/:id` to `/projects/:id/fork` [!8940](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8940) +- Moved `POST /projects/fork/:id` to `POST /projects/:id/fork` [!8940](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8940) - Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` [!9410](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9410) - Project filters are no longer available as `GET /projects/foo`, but as `GET /projects?foo=true` instead [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962) - `GET /projects/visible` & `GET /projects/all` are consolidated into `GET /projects` and can be used with or without authorization - `GET /projects/owned` moved to `GET /projects?owned=true` - `GET /projects/starred` moved to `GET /projects?starred=true` - `GET /projects` returns all projects visible to current user, even if the user is not a member [!9674](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9674) - - To get projects the user is a member of, use `/projects?membership=true` + - To get projects the user is a member of, use `GET /projects?membership=true` - Return pagination headers for all endpoints that return an array [!8606](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8606) - Added `POST /environments/:environment_id/stop` to stop an environment [!8808](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8808) -- Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366) +- Removed `DELETE /projects/:id/deploy_keys/:key_id/disable`. Use `DELETE /projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366) - Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` [!9371](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9371) -- Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325) -- Labels filter on `projects/:id/issues` and `/issues` now matches only issues containing all labels (i.e.: Logical AND, not OR) [!8849](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8849) +- Make subscription API more RESTful. Use `POST /projects/:id/:subscribable_type/:subscribable_id/subscribe` to subscribe and `POST /projects/:id/:subscribable_type/:subscribable_id/unsubscribe` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325) +- Labels filter on `GET /projects/:id/issues` and `GET /issues` now matches only issues containing all labels (i.e.: Logical AND, not OR) [!8849](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8849) - Renamed param `branch_name` to `branch` on the following endpoints [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936) - - POST `:id/repository/branches` - - POST `:id/repository/commits` - - POST/PUT/DELETE `:id/repository/files` -- Renamed `merge when build succeeds` to merge `when pipeline succeeds parameters` on the following endpoints: [!9335](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/) - - PUT `projects/:id/merge_requests/:merge_request_id/merge` - - POST `projects/:id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds` - - POST `projects` - - POST `projects/user/:user_id` - - PUT `projects/:id` -- Renamed `branch_name` to `branch` on DELETE `id/repository/branches/:branch` response [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936) + - `POST /projects/:id/repository/branches` + - `POST /projects/:id/repository/commits` + - `POST/PUT/DELETE :id/repository/files` +- Renamed the `merge_when_build_succeeds` parameter to `merge_when_pipeline_succeeds` on the following endpoints: [!9335](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/) + - `PUT /projects/:id/merge_requests/:merge_request_id/merge` + - `POST /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds` + - `POST /projects` + - `POST /projects/user/:user_id` + - `PUT /projects/:id` +- Renamed `branch_name` to `branch` on `DELETE /projects/:id/repository/branches/:branch` response [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936) - Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736) - Remove `subscribed` field from responses returning list of issues or merge requests. Fetch individual issues or merge requests to obtain the value @@ -62,21 +62,21 @@ Below are the changes made between V3 and V4. - Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384) - Return HTTP status code `400` for all validation errors when creating or updating a member instead of sometimes `422` error. [!9523](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9523) - Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505) -- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) -- `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096) +- Return 202 with JSON body on async removals on V4 API (`DELETE /projects/:id/repository/merged_branches` and `DELETE /projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) +- `GET /projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096) - Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875) - Renamed all `build` references to `job` [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463) -- Drop GET '/projects/:id/repository/commits/:sha/jobs' [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463) +- Drop `GET /projects/:id/repository/commits/:sha/jobs` [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463) - Rename Build Triggers to be Pipeline Triggers API [!9713](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9713) - `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline` - Require description when creating a new trigger `POST /projects/:id/triggers` - Simplify project payload exposed on Environment endpoints [!9675](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9675) - API uses merge request `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the merge requests, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530) - API uses issue `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the issues, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530) -- Change initial page from `0` to `1` on `GET projects/:id/repository/commits` (like on the rest of the API) [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) -- Return correct `Link` header data for `GET projects/:id/repository/commits` [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) +- Change initial page from `0` to `1` on `GET /projects/:id/repository/commits` (like on the rest of the API) [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) +- Return correct `Link` header data for `GET /projects/:id/repository/commits` [!9679] (https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9679) - Update endpoints for repository files [!9637](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9637) - - Moved `/projects/:id/repository/files?file_path=:file_path` to `/projects/:id/repository/files/:file_path` (`:file_path` should be URL-encoded) - - `/projects/:id/repository/blobs/:sha` now returns JSON attributes for the blob identified by `:sha`, instead of finding the commit identified by `:sha` and returning the raw content of the blob in that commit identified by the required `?filepath=:filepath` - - Moved `/projects/:id/repository/commits/:sha/blob?file_path=:file_path` and `/projects/:id/repository/blobs/:sha?file_path=:file_path` to `/projects/:id/repository/files/:file_path/raw?ref=:sha` - - `/projects/:id/repository/tree` parameter `ref_name` has been renamed to `ref` for consistency + - Moved `GET /projects/:id/repository/files?file_path=:file_path` to `GET /projects/:id/repository/files/:file_path` (`:file_path` should be URL-encoded) + - `GET /projects/:id/repository/blobs/:sha` now returns JSON attributes for the blob identified by `:sha`, instead of finding the commit identified by `:sha` and returning the raw content of the blob in that commit identified by the required `?filepath=:filepath` + - Moved `GET /projects/:id/repository/commits/:sha/blob?file_path=:file_path` and `GET /projects/:id/repository/blobs/:sha?file_path=:file_path` to `GET /projects/:id/repository/files/:file_path/raw?ref=:sha` + - `GET /projects/:id/repository/tree` parameter `ref_name` has been renamed to `ref` for consistency diff --git a/doc/development/README.md b/doc/development/README.md index 265df98fb87..e27e7fc7d19 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -15,7 +15,7 @@ - [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations - [Testing standards and style guidelines](testing.md) - [UX guide](ux_guide/index.md) for building GitLab with existing CSS styles and elements -- [Frontend guidelines](frontend.md) +- [Frontend guidelines](fe_guide/index.md) - [SQL guidelines](sql.md) for working with SQL queries - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers - [`Gemfile` guidelines](gemfile.md) diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md index b03216fec95..0810b32efd7 100644 --- a/doc/development/ci_setup.md +++ b/doc/development/ci_setup.md @@ -7,7 +7,7 @@ We currently use four CI services to test GitLab: 1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce) 2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org 3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) -4. [Mock CI Service](user/project/integrations/mock_ci.md) for local development +4. [Mock CI Service](../user/project/integrations/mock_ci.md) for local development | Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore | |---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------| diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md new file mode 100644 index 00000000000..366b220cbb2 --- /dev/null +++ b/doc/development/fe_guide/accessibility.md @@ -0,0 +1,13 @@ +# Accessibility + +## Resources + +[Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] +are useful for testing for potential accessibility problems in GitLab. + +Accessibility best-practices and more in-depth information is available on +[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools. + + +[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools +[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules diff --git a/doc/development/fe_guide/architecture.md b/doc/development/fe_guide/architecture.md new file mode 100644 index 00000000000..aebb22caa15 --- /dev/null +++ b/doc/development/fe_guide/architecture.md @@ -0,0 +1,22 @@ +# Architecture + +When you are developing a new feature that requires architectural design, or if +you are changing the fundamental design of an existing feature, make sure it is +discussed with one of the Frontend Architecture Experts. + +A Frontend Architect is an expert who makes high-level Frontend design decisions +and decides on technical standards, including coding standards and frameworks. + +Architectural decisions should be accessible to everyone, so please document +them in the relevant Merge Request discussion or by updating our documentation +when appropriate. + +You can find the Frontend Architecture experts on the [team page][team-page]. + +## Examples + +You can find documentation about the desired architecture for a new feature +built with Vue.js [here][vue-section]. + +[team-page]: https://about.gitlab.com/team +[vue-section]: vue.md#frontend.html#how-to-build-a-new-feature-with-vue-js diff --git a/doc/development/fe_guide/design_patterns.md b/doc/development/fe_guide/design_patterns.md new file mode 100644 index 00000000000..e05887a19af --- /dev/null +++ b/doc/development/fe_guide/design_patterns.md @@ -0,0 +1,78 @@ +# Design Patterns + +## Singletons + +When exactly one object is needed for a given task, prefer to define it as a +`class` rather than as an object literal. Prefer also to explicitly restrict +instantiation, unless flexibility is important (e.g. for testing). + +```javascript +// bad + +const MyThing = { + prop1: 'hello', + method1: () => {} +}; + +export default MyThing; + +// good + +class MyThing { + constructor() { + this.prop1 = 'hello'; + } + method1() {} +} + +export default new MyThing(); + +// best + +export default class MyThing { + constructor() { + if (!this.prototype.singleton) { + this.init(); + this.prototype.singleton = this; + } + return this.prototype.singleton; + } + + init() { + this.prop1 = 'hello'; + } + + method1() {} +} + +``` + +## Manipulating the DOM in a JS Class + +When writing a class that needs to manipulate the DOM guarantee a container option is provided. +This is useful when we need that class to be instantiated more than once in the same page. + +Bad: +```javascript +class Foo { + constructor() { + document.querySelector('.bar'); + } +} +new Foo(); +``` + +Good: +```javascript +class Foo { + constructor(opts) { + document.querySelector(`${opts.container} .bar`); + } +} + +new Foo({ container: '.my-element' }); +``` +You can find an example of the above in this [class][container-class-example]; + + +[container-class-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md new file mode 100644 index 00000000000..f963bffde37 --- /dev/null +++ b/doc/development/fe_guide/index.md @@ -0,0 +1,92 @@ +# Frontend Development Guidelines + +This document describes various guidelines to ensure consistency and quality +across GitLab's frontend team. + +## Overview + +GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] with +[Hamlit][hamlit]. Be wary of [the limitations that come with using +Hamlit][hamlit-limits]. We also use [SCSS][scss] and plain JavaScript with +modern ECMAScript standards supported through [Babel][babel] and ES module +support through [webpack][webpack]. + +We also utilize [webpack][webpack] to handle the bundling, minification, and +compression of our assets. + +Working with our frontend assets requires Node (v4.3 or greater) and Yarn +(v0.17 or greater). You can find information on how to install these on our +[installation guide][install]. + +[jQuery][jquery] is used throughout the application's JavaScript, with +[Vue.js][vue] for particularly advanced, dynamic elements. + +### Browser Support + +For our currently-supported browsers, see our [requirements][requirements]. + +--- + +## [Architecture](architecture.md) +How we go about making fundamental design decisions in GitLab's frontend team +or make changes to our frontend development guidelines. + +--- + +## [Testing](testing.md) +How we write frontend tests, run the GitLab test suite, and debug test related +issues. + +--- + +## [Design Patterns](design_patterns.md) +Common JavaScript design patterns in GitLab's codebase. + +--- + +## [Vue.js Best Practices](vue.md) +Vue specific design patterns and practices. + +--- + +## Style Guides + +### [JavaScript Style Guide](style_guide_js.md) + +We use eslint to enforce our JavaScript style guides. Our guide is based on +the excellent [Airbnb][airbnb-js-style-guide] style guide with a few small +changes. + +### [SCSS Style Guide](style_guide_scss.md) + +Our SCSS conventions which are enforced through [scss-lint][scss-lint]. + +--- + +## [Performance](performance.md) +Best practices for monitoring and maximizing frontend performance. + +--- + +## [Security](security.md) +Frontend security practices. + +--- + +## [Accessibility](accessibility.md) +Our accessibility standards and resources. + + +[rails]: http://rubyonrails.org/ +[haml]: http://haml.info/ +[hamlit]: https://github.com/k0kubun/hamlit +[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations +[scss]: http://sass-lang.com/ +[babel]: https://babeljs.io/ +[webpack]: https://webpack.js.org/ +[jquery]: https://jquery.com/ +[vue]: http://vuejs.org/ +[airbnb-js-style-guide]: https://github.com/airbnb/javascript +[scss-lint]: https://github.com/brigade/scss-lint +[install]: ../../install/installation.md#4-node +[requirements]: ../../install/requirements.md#supported-web-browsers diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md new file mode 100644 index 00000000000..2d76bb18cff --- /dev/null +++ b/doc/development/fe_guide/performance.md @@ -0,0 +1,95 @@ +# Performance + +## Best Practices + +### Realtime Components + +When writing code for realtime features we have to keep a couple of things in mind: +1. Do not overload the server with requests. +1. It should feel realtime. + +Thus, we must strike a balance between sending requests and the feeling of realtime. +Use the following rules when creating realtime solutions. + +1. The server will tell you how much to poll by sending `Poll-Interval` in the header. +Use that as your polling interval. This way it is easy for system administrators to change the +polling rate. +A `Poll-Interval: -1` means you should disable polling, and this must be implemented. +1. A response with HTTP status `4XX` or `5XX` should disable polling as well. +1. Use a common library for polling. +1. Poll on active tabs only. Use a common library to find out which tab currently has eyes on it. +Please use [Focus](https://gitlab.com/andrewn/focus). Specifically [Eyeballs Detector](https://gitlab.com/andrewn/focus/blob/master/lib/eyeballs-detector.js). +1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be +controlled by the server. +1. The backend code will most likely be using etags. You do not and should not check for status +`304 Not Modified`. The browser will transform it for you. + +## Reducing Asset Footprint + +### Page-specific JavaScript + +Certain pages may require the use of a third party library, such as [d3][d3] for +the User Activity Calendar and [Chart.js][chartjs] for the Graphs pages. These +libraries increase the page size significantly, and impact load times due to +bandwidth bottlenecks and the browser needing to parse more JavaScript. + +In cases where libraries are only used on a few specific pages, we use +"page-specific JavaScript" to prevent the main `main.js` file from +becoming unnecessarily large. + +Steps to split page-specific JavaScript from the main `main.js`: + +1. Create a directory for the specific page(s), e.g. `graphs/`. +1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`. +1. Add the new "bundle" file to the list of entry files in `config/webpack.config.js`. + - For example: `graphs: './graphs/graphs_bundle.js',`. +1. Move code reliant on these libraries into the `graphs` directory. +1. In `graphs_bundle.js` add CommonJS `require('./path_to_some_component.js');` statements to load any other files in this directory. Make sure to use relative urls. +1. In the relevant views, add the scripts to the page with the following: + +```haml +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('lib_chart') + = page_specific_javascript_bundle_tag('graphs') +``` + +The above loads `chart.js` and `graphs_bundle.js` for this page only. `chart.js` +is separated from the bundle file so it can be cached separately from the bundle +and reused for other pages that also rely on the library. For an example, see +[this Haml file][page-specific-js-example]. + +### Code Splitting + +> *TODO* flesh out this section once webpack is ready for code-splitting + +### Minimizing page size + +A smaller page size means the page loads faster (especially important on mobile +and poor connections), the page is parsed more quickly by the browser, and less +data is used for users with capped data plans. + +General tips: + +- Don't add new fonts. +- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF. +- Compress and minify assets wherever possible (For CSS/JS, Sprockets and webpack do this for us). +- If some functionality can reasonably be achieved without adding extra libraries, avoid them. +- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages. + +------- + +## Additional Resources + +- [WebPage Test][web-page-test] for testing site loading time and size. +- [Google PageSpeed Insights][pagespeed-insights] grades web pages and provides feedback to improve the page. +- [Profiling with Chrome DevTools][google-devtools-profiling] +- [Browser Diet][browser-diet] is a community-built guide that catalogues practical tips for improving web page performance. + + +[web-page-test]: http://www.webpagetest.org/ +[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/ +[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en +[browser-diet]: https://browserdiet.com/ +[d3]: https://d3js.org/ +[chartjs]: http://www.chartjs.org/ +[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8 diff --git a/doc/development/fe_guide/security.md b/doc/development/fe_guide/security.md new file mode 100644 index 00000000000..19e72c1d368 --- /dev/null +++ b/doc/development/fe_guide/security.md @@ -0,0 +1,92 @@ +# Security +### Resources + +[Mozilla’s HTTP Observatory CLI][observatory-cli] and the +[Qualys SSL Labs Server Test][qualys-ssl] are good resources for finding +potential problems and ensuring compliance with security best practices. + +<!-- Uncomment these sections when CSP/SRI are implemented. +### Content Security Policy (CSP) + +Content Security Policy is a web standard that intends to mitigate certain +forms of Cross-Site Scripting (XSS) as well as data injection. + +Content Security Policy rules should be taken into consideration when +implementing new features, especially those that may rely on connection with +external services. + +GitLab's CSP is used for the following: + +- Blocking plugins like Flash and Silverlight from running at all on our pages. +- Blocking the use of scripts and stylesheets downloaded from external sources. +- Upgrading `http` requests to `https` when possible. +- Preventing `iframe` elements from loading in most contexts. + +Some exceptions include: + +- Scripts from Google Analytics and Piwik if either is enabled. +- Connecting with GitHub, Bitbucket, GitLab.com, etc. to allow project importing. +- Connecting with Google, Twitter, GitHub, etc. to allow OAuth authentication. + +We use [the Secure Headers gem][secure_headers] to enable Content +Security Policy headers in the GitLab Rails app. + +Some resources on implementing Content Security Policy: + +- [MDN Article on CSP][mdn-csp] +- [GitHub’s CSP Journey on the GitHub Engineering Blog][github-eng-csp] +- The Dropbox Engineering Blog's series on CSP: [1][dropbox-csp-1], [2][dropbox-csp-2], [3][dropbox-csp-3], [4][dropbox-csp-4] + +### Subresource Integrity (SRI) + +Subresource Integrity prevents malicious assets from being provided by a CDN by +guaranteeing that the asset downloaded is identical to the asset the server +is expecting. + +The Rails app generates a unique hash of the asset, which is used as the +asset's `integrity` attribute. The browser generates the hash of the asset +on-load and will reject the asset if the hashes do not match. + +All CSS and JavaScript assets should use Subresource Integrity. + +Some resources on implementing Subresource Integrity: + +- [MDN Article on SRI][mdn-sri] +- [Subresource Integrity on the GitHub Engineering Blog][github-eng-sri] + +--> + +### Including external resources + +External fonts, CSS, and JavaScript should never be used with the exception of +Google Analytics and Piwik - and only when the instance has enabled it. Assets +should always be hosted and served locally from the GitLab instance. Embedded +resources via `iframes` should never be used except in certain circumstances +such as with ReCaptcha, which cannot be used without an `iframe`. + +### Avoiding inline scripts and styles + +In order to protect users from [XSS vulnerabilities][xss], we will disable +inline scripts in the future using Content Security Policy. + +While inline scripts can be useful, they're also a security concern. If +user-supplied content is unintentionally left un-sanitized, malicious users can +inject scripts into the web app. + +Inline styles should be avoided in almost all cases, they should only be used +when no alternatives can be found. This allows reusability of styles as well as +readability. + + +[observatory-cli]: https://github.com/mozilla/http-observatory-cli +[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html +[secure_headers]: https://github.com/twitter/secureheaders +[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP +[github-eng-csp]: http://githubengineering.com/githubs-csp-journey/ +[dropbox-csp-1]: https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/ +[dropbox-csp-2]: https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/ +[dropbox-csp-3]: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/ +[dropbox-csp-4]: https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/ +[mdn-sri]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity +[github-eng-sri]: http://githubengineering.com/subresource-integrity/ +[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md new file mode 100644 index 00000000000..034cfe73d33 --- /dev/null +++ b/doc/development/fe_guide/style_guide_js.md @@ -0,0 +1,408 @@ +# Style guides and linting +See the relevant style guides for our guidelines and for information on linting: + +## JavaScript +We defer to [Airbnb][airbnb-js-style-guide] on most style-related +conventions and enforce them with eslint. + +See [our current .eslintrc][eslintrc] for specific rules and patterns. + +### Common + +#### ESlint + +- **Never** disable eslint rules unless you have a good reason. You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */` at the top, but legacy files are a special case. Any time you develop a new feature or refactor an existing one, you should abide by the eslint rules. + +- **Never Ever EVER** disable eslint globally for a file + + ```javascript + // bad + /* eslint-disable */ + + // better + /* eslint-disable some-rule, some-other-rule */ + + // best + // nothing :) + ``` + +- If you do need to disable a rule for a single violation, try to do it as locally as possible + + ```javascript + // bad + /* eslint-disable no-new */ + + import Foo from 'foo'; + + new Foo(); + + // better + import Foo from 'foo'; + + // eslint-disable-next-line no-new + new Foo(); + ``` + +- When they are needed _always_ place ESlint directive comment blocks on the first line of a script, followed by any global declarations, then a blank newline prior to any imports or code. + + ```javascript + // bad + /* global Foo */ + /* eslint-disable no-new */ + import Bar from './bar'; + + // good + /* eslint-disable no-new */ + /* global Foo */ + + import Bar from './bar'; + ``` + +- **Never** disable the `no-undef` rule. Declare globals with `/* global Foo */` instead. + +- When declaring multiple globals, always use one `/* global [name] */` line per variable. + + ```javascript + // bad + /* globals Flash, Cookies, jQuery */ + + // good + /* global Flash */ + /* global Cookies */ + /* global jQuery */ + ``` + +#### Modules, Imports, and Exports +- Use ES module syntax to import modules + + ```javascript + // bad + require('foo'); + + // good + import Foo from 'foo'; + + // bad + module.exports = Foo; + + // good + export default Foo; + ``` + +- Relative paths + + Unless you are writing a test, always reference other scripts using relative paths instead of `~` + + In **app/assets/javascripts**: + ```javascript + // bad + import Foo from '~/foo' + + // good + import Foo from '../foo'; + ``` + + In **spec/javascripts**: + ```javascript + // bad + import Foo from '../../app/assets/javascripts/foo' + + // good + import Foo from '~/foo'; + ``` + +- Avoid using IIFE. Although we have a lot of examples of files which wrap their contents in IIFEs (immediately-invoked function expressions), this is no longer necessary after the transition from Sprockets to webpack. Do not use them anymore and feel free to remove them when refactoring legacy code. + +- Avoid adding to the global namespace. + + ```javascript + // bad + window.MyClass = class { /* ... */ }; + + // good + export default class MyClass { /* ... */ } + ``` + +- Side effects are forbidden in any script which contains exports + + ```javascript + // bad + export default class MyClass { /* ... */ } + + document.addEventListener("DOMContentLoaded", function(event) { + new MyClass(); + } + ``` + + +#### Data Mutation and Pure functions +- Strive to write many small pure functions, and minimize where mutations occur. + + ```javascript + // bad + const values = {foo: 1}; + + function impureFunction(items) { + const bar = 1; + + items.foo = items.a * bar + 2; + + return items.a; + } + + const c = impureFunction(values); + + // good + var values = {foo: 1}; + + function pureFunction (foo) { + var bar = 1; + + foo = foo * bar + 2; + + return foo; + } + + var c = pureFunction(values.foo); + ``` + +- Avoid constructors with side-effects + + +#### Parse Strings into Numbers +- `parseInt()` is preferable over `Number()` or `+` + + ```javascript + // bad + +'10' // 10 + + // good + Number('10') // 10 + + // better + parseInt('10', 10); + ``` + + +### Vue.js + + +#### Basic Rules +- Only include one Vue.js component per file. +- Export components as plain objects: + + ```javascript + export default { + template: `<h1>I'm a component</h1> + } + ``` + +#### Naming +- **Extensions**: Use `.vue` extension for Vue components. +- **Reference Naming**: Use PascalCase for Vue components and camelCase for their instances: + + ```javascript + // bad + import cardBoard from 'cardBoard'; + + // good + import CardBoard from 'cardBoard' + + // bad + components: { + CardBoard: CardBoard + }; + + // good + components: { + cardBoard: CardBoard + }; + ``` +- **Props Naming**: Avoid using DOM component prop names. + + ```javascript + // bad + <component class="btn"> + + // good + <component cssClass="btn"> + ``` + +#### Alignment +- Follow these alignment styles for the template method: + + ```javascript + // bad + <component v-if="bar" + param="baz" /> + + // good + <component + v-if="bar" + param="baz" + /> + + // if props fit in one line then keep it on the same line + <component bar="bar" /> + ``` + +#### Quotes +- Always use double quotes `"` inside templates and single quotes `'` for all other JS. + + ```javascript + // bad + template: ` + <button :class='style'>Button</button> + ` + + // good + template: ` + <button :class="style">Button</button> + ` + ``` + +#### Props +- Props should be declared as an object + + ```javascript + // bad + props: ['foo'] + + // good + props: { + foo: { + type: String, + required: false, + default: 'bar' + } + } + ``` + +- Required key should always be provided when declaring a prop + + ```javascript + // bad + props: { + foo: { + type: String, + } + } + + // good + props: { + foo: { + type: String, + required: false, + default: 'bar' + } + } + ``` + +- Default key should always be provided if the prop is not required: + + ```javascript + // bad + props: { + foo: { + type: String, + required: false, + } + } + + // good + props: { + foo: { + type: String, + required: false, + default: 'bar' + } + } + + // good + props: { + foo: { + type: String, + required: true + } + } + ``` + +#### Data +- `data` method should always be a function + + ```javascript + // bad + data: { + foo: 'foo' + } + + // good + data() { + return { + foo: 'foo' + }; + } + ``` + +#### Directives + +- Shorthand `@` is preferable over `v-on` + + ```javascript + // bad + <component v-on:click="eventHandler"/> + + + // good + <component @click="eventHandler"/> + ``` + +- Shorthand `:` is preferable over `v-bind` + + ```javascript + // bad + <component v-bind:class="btn"/> + + + // good + <component :class="btn"/> + ``` + +#### Closing tags +- Prefer self closing component tags + + ```javascript + // bad + <component></component> + + // good + <component /> + ``` + +#### Ordering +- Order for a Vue Component: + 1. `name` + 2. `props` + 3. `data` + 4. `components` + 5. `computedProps` + 6. `methods` + 7. lifecycle methods + 1. `beforeCreate` + 2. `created` + 3. `beforeMount` + 4. `mounted` + 5. `beforeUpdate` + 6. `updated` + 7. `activated` + 8. `deactivated` + 9. `beforeDestroy` + 10. `destroyed` + 8. `template` + + +## SCSS +- [SCSS](style_guide_scss.md) + +[airbnb-js-style-guide]: https://github.com/airbnb/javascript +[eslintrc]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc diff --git a/doc/development/scss_styleguide.md b/doc/development/fe_guide/style_guide_scss.md index a79f4073cde..77b308c4a43 100644 --- a/doc/development/scss_styleguide.md +++ b/doc/development/fe_guide/style_guide_scss.md @@ -35,7 +35,7 @@ between the property and its value. ```scss // Bad -.container-item { +.container-item { width: 100px; height: 100px; margin-top: 0; } @@ -63,7 +63,7 @@ between the property and its value. } ``` -Note that there is an exception for single-line rulesets, although these are +Note that there is an exception for single-line rulesets, although these are not typically recommended. ```scss @@ -72,7 +72,7 @@ p { margin: 0; padding: 0; } ### Colors -HEX (hexadecimal) colors should use shorthand where possible, and should use +HEX (hexadecimal) colors should use shorthand where possible, and should use lower case letters to differentiate between letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`. @@ -111,7 +111,7 @@ p { ### Semicolons -Always include semicolons after every property. When the stylesheets are +Always include semicolons after every property. When the stylesheets are minified, the semicolons will be removed automatically. ```scss @@ -144,7 +144,7 @@ padding: 10px; ### Zero Units -Omit length units on zero values, they're unnecessary and not including them +Omit length units on zero values, they're unnecessary and not including them is slightly more performant. ```scss @@ -161,36 +161,56 @@ is slightly more performant. ### Selectors with a `js-` Prefix -Do not use any selector prefixed with `js-` for styling purposes. These -selectors are intended for use only with JavaScript to allow for removal or +Do not use any selector prefixed with `js-` for styling purposes. These +selectors are intended for use only with JavaScript to allow for removal or renaming without breaking styling. +### IDs +Don't use ID selectors in CSS. + +```scss +// Bad +#my-element { + padding: 0; +} + +// Good +.my-element { + padding: 0; +} +``` + +### Variables +Before adding a new variable for a color or a size, guarantee: +1. There isn't already one +2. There isn't a similar one we can use instead. + ## Linting -We use [SCSS Lint][scss-lint] to check for style guide conformity. It uses the -ruleset in `.scss-lint.yml`, which is located in the home directory of the +We use [SCSS Lint][scss-lint] to check for style guide conformity. It uses the +ruleset in `.scss-lint.yml`, which is located in the home directory of the project. -To check if any warnings will be produced by your changes, you can run `rake -scss_lint` in the GitLab directory. SCSS Lint will also run in GitLab CI to +To check if any warnings will be produced by your changes, you can run `rake +scss_lint` in the GitLab directory. SCSS Lint will also run in GitLab CI to catch any warnings. -If the Rake task is throwing warnings you don't understand, SCSS Lint's +If the Rake task is throwing warnings you don't understand, SCSS Lint's documentation includes [a full list of their linters][scss-lint-documentation]. ### Fixing issues -If you want to automate changing a large portion of the codebase to conform to +If you want to automate changing a large portion of the codebase to conform to the SCSS style guide, you can use [CSSComb][csscomb]. First install -[Node][node] and [NPM][npm], then run `npm install csscomb -g` to install -CSSComb globally (system-wide). Run it in the GitLab directory with +[Node][node] and [NPM][npm], then run `npm install csscomb -g` to install +CSSComb globally (system-wide). Run it in the GitLab directory with `csscomb app/assets/stylesheets` to automatically fix issues with CSS/SCSS. Note that this won't fix every problem, but it should fix a majority. ### Ignoring issues -If you want a line or set of lines to be ignored by the linter, you can use +If you want a line or set of lines to be ignored by the linter, you can use `// scss-lint:disable RuleName` ([more info][disabling-linters]): ```scss @@ -203,8 +223,8 @@ If you want a line or set of lines to be ignored by the linter, you can use ``` Make sure a comment is added on the line above the `disable` rule, otherwise the -linter will throw a warning. `DisableLinterReason` is enabled to make sure the -style guide isn't being ignored, and to communicate to others why the style +linter will throw a warning. `DisableLinterReason` is enabled to make sure the +style guide isn't being ignored, and to communicate to others why the style guide is ignored in this instance. [csscomb]: https://github.com/csscomb/csscomb.js diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md new file mode 100644 index 00000000000..bb6adeacc4c --- /dev/null +++ b/doc/development/fe_guide/testing.md @@ -0,0 +1,129 @@ +# Frontend Testing + +There are two types of tests you'll encounter while developing frontend code +at GitLab. We use Karma and Jasmine for JavaScript unit testing, and RSpec +feature tests with Capybara for integration testing. + +Feature tests need to be written for all new features. Regression tests ought +to be written for all bug fixes to prevent them from recurring in the future. + +See [the Testing Standards and Style Guidelines](/doc/development/testing.md) +for more information on general testing practices at GitLab. + +## Karma test suite + +GitLab uses the [Karma][karma] test runner with [Jasmine][jasmine] as its test +framework for our JavaScript unit tests. For tests that rely on DOM +manipulation we use fixtures which are pre-compiled from HAML source files and +served during testing by the [jasmine-jquery][jasmine-jquery] plugin. + +### Running frontend tests + +`rake karma` runs the frontend-only (JavaScript) tests. +It consists of two subtasks: + +- `rake karma:fixtures` (re-)generates fixtures +- `rake karma:tests` actually executes the tests + +As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`) +is sufficient (and saves you some time). + +### Live testing and focused testing + +While developing locally, it may be helpful to keep karma running so that you +can get instant feedback on as you write tests and modify code. To do this +you can start karma with `npm run karma-start`. It will compile the javascript +assets and run a server at `http://localhost:9876/` where it will automatically +run the tests on any browser which connects to it. You can enter that url on +multiple browsers at once to have it run the tests on each in parallel. + +While karma is running, any changes you make will instantly trigger a recompile +and retest of the entire test suite, so you can see instantly if you've broken +a test with your changes. You can use [jasmine focused][jasmine-focus] or +excluded tests (with `fdescribe` or `xdescribe`) to get karma to run only the +tests you want while you're working on a specific feature, but make sure to +remove these directives when you commit your code. + +## RSpec Feature Integration Tests + +Information on setting up and running RSpec integration tests with +[Capybara][capybara] can be found in the +[general testing guide](/doc/development/testing.md). + +## Gotchas + +### Errors due to use of unsupported JavaScript features + +Similar errors will be thrown if you're using JavaScript features not yet +supported by the PhantomJS test runner which is used for both Karma and RSpec +tests. We polyfill some JavaScript objects for older browsers, but some +features are still unavailable: + +- Array.from +- Array.first +- Async functions +- Generators +- Array destructuring +- For..Of +- Symbol/Symbol.iterator +- Spread + +Until these are polyfilled appropriately, they should not be used. Please +update this list with additional unsupported features. + +### RSpec errors due to JavaScript + +By default RSpec unit tests will not run JavaScript in the headless browser +and will simply rely on inspecting the HTML generated by rails. + +If an integration test depends on JavaScript to run correctly, you need to make +sure the spec is configured to enable JavaScript when the tests are run. If you +don't do this you'll see vague error messages from the spec runner. + +To enable a JavaScript driver in an `rspec` test, add `js: true` to the +individual spec or the context block containing multiple specs that need +JavaScript enabled: + +```ruby + +# For one spec +it 'presents information about abuse report', js: true do + # assertions... +end + +describe "Admin::AbuseReports", js: true do + it 'presents information about abuse report' do + # assertions... + end + it 'shows buttons for adding to abuse report' do + # assertions... + end +end +``` + +### Spinach errors due to missing JavaScript + +> **Note:** Since we are discouraging the use of Spinach when writing new +> feature tests, you shouldn't ever need to use this. This information is kept +> available for legacy purposes only. + +In Spinach, the JavaScript driver is enabled differently. In the `*.feature` +file for the failing spec, add the `@javascript` flag above the Scenario: + +``` +@javascript +Scenario: Developer can approve merge request + Given I am a "Shop" developer + And I visit project "Shop" merge requests page + And merge request 'Bug NS-04' must be approved + And I click link "Bug NS-04" + When I click link "Approve" + Then I should see approved merge request "Bug NS-04" + +``` + +[capybara]: http://teamcapybara.github.io/capybara/ +[jasmine]: https://jasmine.github.io/ +[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html +[jasmine-jquery]: https://github.com/velesin/jasmine-jquery +[karma]: http://karma-runner.github.io/ diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md new file mode 100644 index 00000000000..3e3406e7d6a --- /dev/null +++ b/doc/development/fe_guide/vue.md @@ -0,0 +1,104 @@ +# Vue + +For more complex frontend features, we recommend using Vue.js. It shares +some ideas with React.js as well as Angular. + +To get started with Vue, read through [their documentation][vue-docs]. + +## When to use Vue.js + +We recommend using Vue for more complex features. Here are some guidelines for when to use Vue.js: + +- If you are starting a new feature or refactoring an old one that highly interacts with the DOM; +- For real time data updates; +- If you are creating a component that will be reused elsewhere; + +## When not to use Vue.js + +We don't want to refactor all GitLab frontend code into Vue.js, here are some guidelines for +when not to use Vue.js: + +- Adding or changing static information; +- Features that highly depend on jQuery will be hard to work with Vue.js + +As always, the Frontend Architectural Experts are available to help with any Vue or JavaScript questions. + +## How to build a new feature with Vue.js + +**Components, Stores and Services** + +In some features implemented with Vue.js, like the [issue board][issue-boards] +or [environments table][environments-table] +you can find a clear separation of concerns: + +``` +new_feature +├── components +│ └── component.js.es6 +│ └── ... +├── store +│ └── new_feature_store.js.es6 +├── service +│ └── new_feature_service.js.es6 +├── new_feature_bundle.js.es6 +``` +_For consistency purposes, we recommend you to follow the same structure._ + +Let's look into each of them: + +**A `*_bundle.js` file** + +This is the index file of your new feature. This is where the root Vue instance +of the new feature should be. + +The Store and the Service should be imported and initialized in this file and provided as a prop to the main component. + +Don't forget to follow [these steps.][page_specific_javascript] + +**A folder for Components** + +This folder holds all components that are specific of this new feature. +If you need to use or create a component that will probably be used somewhere +else, please refer to `vue_shared/components`. + +A good thumb rule to know when you should create a component is to think if +it will be reusable elsewhere. + +For example, tables are used in a quite amount of places across GitLab, a table +would be a good fit for a component. On the other hand, a table cell used only +in one table would not be a good use of this pattern. + +You can read more about components in Vue.js site, [Component System][component-system] + +**A folder for the Store** + +The Store is a class that allows us to manage the state in a single +source of truth. + +The concept we are trying to follow is better explained by Vue documentation +itself, please read this guide: [State Management][state-management] + +**A folder for the Service** + +The Service is used only to communicate with the server. +It does not store or manipulate any data. +We use [vue-resource][vue-resource-repo] to +communicate with the server. + +The [issue boards service][issue-boards-service] +is a good example of this pattern. + +## Style guide + +Please refer to the Vue section of our [style guide](style_guide_js.md#vuejs) +for best practices while writing your Vue components and templates. + + +[vue-docs]: http://vuejs.org/guide/index.html +[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards +[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments +[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript +[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components +[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch +[vue-resource-repo]: https://github.com/pagekit/vue-resource +[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6 diff --git a/doc/development/frontend.md b/doc/development/frontend.md index 50105a486d0..f46cc377f95 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -1,511 +1,4 @@ -# Frontend Development Guidelines - -This document describes various guidelines to ensure consistency and quality -across GitLab's frontend team. - -## Overview - -GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] with -[Hamlit][hamlit]. Be wary of [the limitations that come with using -Hamlit][hamlit-limits]. We also use [SCSS][scss] and plain JavaScript with -[ES6 by way of Babel][es6]. - -The asset pipeline is [Sprockets][sprockets], which handles the concatenation, -minification, and compression of our assets. - -[jQuery][jquery] is used throughout the application's JavaScript, with -[Vue.js][vue] for particularly advanced, dynamic elements. - -### Architecture - -The Frontend Architect is an expert who makes high-level frontend design choices -and decides on technical standards, including coding standards, and frameworks. - -When you are assigned a new feature that requires architectural design, -make sure it is discussed with one of the Frontend Architecture Experts. - -This rule also applies if you plan to change the architecture of an existing feature. - -These decisions should be accessible to everyone, so please document it on the Merge Request. - -You can find the Frontend Architecture experts on the [team page][team-page]. - -You can find documentation about the desired architecture for a new feature built with Vue.js in [here][vue-section]. - -### Realtime - -When writing code for realtime features we have to keep a couple of things in mind: -1. Do not overload the server with requests. -1. It should feel realtime. - -Thus, we must strike a balance between sending requests and the feeling of realtime. -Use the following rules when creating realtime solutions. - -1. The server will tell you how much to poll by sending `Poll-Interval` in the header. -Use that as your polling interval. This way it is easy for system administrators to change the -polling rate. -A `Poll-Interval: -1` means you should disable polling, and this must be implemented. -1. A response with HTTP status `4XX` or `5XX` should disable polling as well. -1. Use a common library for polling. -1. Poll on active tabs only. Use a common library to find out which tab currently has eyes on it. -Please use [Focus](https://gitlab.com/andrewn/focus). Specifically [Eyeballs Detector](https://gitlab.com/andrewn/focus/blob/master/lib/eyeballs-detector.js). -1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be -controlled by the server. -1. The backend code will most likely be using etags. You do not and should not check for status -`304 Not Modified`. The browser will transform it for you. - -### Vue - -For more complex frontend features, we recommend using Vue.js. It shares -some ideas with React.js as well as Angular. - -To get started with Vue, read through [their documentation][vue-docs]. - -#### How to build a new feature with Vue.js -**Components, Stores and Services** - -In some features implemented with Vue.js, like the [issue board][issue-boards] -or [environments table][environments-table] -you can find a clear separation of concerns: - -``` -new_feature -├── components -│ └── component.js.es6 -│ └── ... -├── store -│ └── new_feature_store.js.es6 -├── service -│ └── new_feature_service.js.es6 -├── new_feature_bundle.js.es6 -``` -_For consistency purposes, we recommend you to follow the same structure._ - -Let's look into each of them: - -**A `*_bundle.js` file** - -This is the index file of your new feature. This is where the root Vue instance -of the new feature should be. - -The Store and the Service should be imported and initialized in this file and provided as a prop to the main component. - -Don't forget to follow [these steps.][page_specific_javascript] - -**A folder for Components** - -This folder holds all components that are specific of this new feature. -If you need to use or create a component that will probably be used somewhere -else, please refer to `vue_shared/components`. - -A good thumb rule to know when you should create a component is to think if -it will be reusable elsewhere. - -For example, tables are used in a quite amount of places across GitLab, a table -would be a good fit for a component. -On the other hand, a table cell used only in on table, would not be a good use -of this pattern. - -You can read more about components in Vue.js site, [Component System][component-system] - -**A folder for the Store** - -The Store is a class that allows us to manage the state in a single -source of truth. - -The concept we are trying to follow is better explained by Vue documentation -itself, please read this guide: [State Management][state-management] - -**A folder for the Service** - -The Service is used only to communicate with the server. -It does not store or manipulate any data. -We use [vue-resource][vue-resource-repo] to -communicate with the server. - -The [issue boards service][issue-boards-service] -is a good example of this pattern. - -## Performance - -### Resources - -- [WebPage Test][web-page-test] for testing site loading time and size. -- [Google PageSpeed Insights][pagespeed-insights] grades web pages and provides feedback to improve the page. -- [Profiling with Chrome DevTools][google-devtools-profiling] -- [Browser Diet][browser-diet] is a community-built guide that catalogues practical tips for improving web page performance. - -### Page-specific JavaScript - -Certain pages may require the use of a third party library, such as [d3][d3] for -the User Activity Calendar and [Chart.js][chartjs] for the Graphs pages. These -libraries increase the page size significantly, and impact load times due to -bandwidth bottlenecks and the browser needing to parse more JavaScript. - -In cases where libraries are only used on a few specific pages, we use -"page-specific JavaScript" to prevent the main `application.js` file from -becoming unnecessarily large. - -Steps to split page-specific JavaScript from the main `application.js`: - -1. Create a directory for the specific page(s), e.g. `graphs/`. -1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`. -1. In `graphs_bundle.js` add the line `//= require_tree .`, this adds all other files in the directory to the bundle. -1. Add any necessary libraries to `app/assets/javascripts/lib/`, all files directly descendant from this directory will be precompiled as separate assets, in this case `chart.js` would be added. -1. Add the new "bundle" file to the list of precompiled assets in -`config/application.rb`. - - For example: `config.assets.precompile << "graphs/graphs_bundle.js"`. -1. Move code reliant on these libraries into the `graphs` directory. -1. In the relevant views, add the scripts to the page with the following: - -```haml -- content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/chart.js') - = page_specific_javascript_tag('graphs/graphs_bundle.js') -``` - -The above loads `chart.js` and `graphs_bundle.js` for this page only. `chart.js` -is separated from the bundle file so it can be cached separately from the bundle -and reused for other pages that also rely on the library. For an example, see -[this Haml file][page-specific-js-example]. - -### Minimizing page size - -A smaller page size means the page loads faster (especially important on mobile -and poor connections), the page is parsed more quickly by the browser, and less -data is used for users with capped data plans. - -General tips: - -- Don't add new fonts. -- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF. -- Compress and minify assets wherever possible (For CSS/JS, Sprockets does this for us). -- If some functionality can reasonably be achieved without adding extra libraries, avoid them. -- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages. - -## Accessibility - -### Resources - -[Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] -are useful for testing for potential accessibility problems in GitLab. - -Accessibility best-practices and more in-depth information is available on -[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools. - -## Security - -### Resources - -[Mozilla’s HTTP Observatory CLI][observatory-cli] and the -[Qualys SSL Labs Server Test][qualys-ssl] are good resources for finding -potential problems and ensuring compliance with security best practices. - -<!-- Uncomment these sections when CSP/SRI are implemented. -### Content Security Policy (CSP) - -Content Security Policy is a web standard that intends to mitigate certain -forms of Cross-Site Scripting (XSS) as well as data injection. - -Content Security Policy rules should be taken into consideration when -implementing new features, especially those that may rely on connection with -external services. - -GitLab's CSP is used for the following: - -- Blocking plugins like Flash and Silverlight from running at all on our pages. -- Blocking the use of scripts and stylesheets downloaded from external sources. -- Upgrading `http` requests to `https` when possible. -- Preventing `iframe` elements from loading in most contexts. - -Some exceptions include: - -- Scripts from Google Analytics and Piwik if either is enabled. -- Connecting with GitHub, Bitbucket, GitLab.com, etc. to allow project importing. -- Connecting with Google, Twitter, GitHub, etc. to allow OAuth authentication. - -We use [the Secure Headers gem][secure_headers] to enable Content -Security Policy headers in the GitLab Rails app. - -Some resources on implementing Content Security Policy: - -- [MDN Article on CSP][mdn-csp] -- [GitHub’s CSP Journey on the GitHub Engineering Blog][github-eng-csp] -- The Dropbox Engineering Blog's series on CSP: [1][dropbox-csp-1], [2][dropbox-csp-2], [3][dropbox-csp-3], [4][dropbox-csp-4] - -### Subresource Integrity (SRI) - -Subresource Integrity prevents malicious assets from being provided by a CDN by -guaranteeing that the asset downloaded is identical to the asset the server -is expecting. - -The Rails app generates a unique hash of the asset, which is used as the -asset's `integrity` attribute. The browser generates the hash of the asset -on-load and will reject the asset if the hashes do not match. - -All CSS and JavaScript assets should use Subresource Integrity. For implementation details, -see the documentation for [the Sprockets implementation of SRI][sprockets-sri]. - -Some resources on implementing Subresource Integrity: - -- [MDN Article on SRI][mdn-sri] -- [Subresource Integrity on the GitHub Engineering Blog][github-eng-sri] - ---> - -### Including external resources - -External fonts, CSS, and JavaScript should never be used with the exception of -Google Analytics and Piwik - and only when the instance has enabled it. Assets -should always be hosted and served locally from the GitLab instance. Embedded -resources via `iframes` should never be used except in certain circumstances -such as with ReCaptcha, which cannot be used without an `iframe`. - -### Avoiding inline scripts and styles - -In order to protect users from [XSS vulnerabilities][xss], we will disable inline scripts in the future using Content Security Policy. - -While inline scripts can be useful, they're also a security concern. If -user-supplied content is unintentionally left un-sanitized, malicious users can -inject scripts into the web app. - -Inline styles should be avoided in almost all cases, they should only be used -when no alternatives can be found. This allows reusability of styles as well as -readability. - -## Style guides and linting - -See the relevant style guides for our guidelines and for information on linting: - -- [SCSS][scss-style-guide] -- JavaScript - We defer to [AirBnb][airbnb-js-style-guide] on most style-related -conventions and enforce them with eslint. See [our current .eslintrc][eslintrc] -for specific rules and patterns. - -## Testing - -Feature tests need to be written for all new features. Regression tests -also need to be written for all bug fixes to prevent them from occurring -again in the future. - -See [the Testing Standards and Style Guidelines](testing.md) for more -information. - -### Running frontend tests - -`rake karma` runs the frontend-only (JavaScript) tests. -It consists of two subtasks: - -- `rake karma:fixtures` (re-)generates fixtures -- `rake karma:tests` actually executes the tests - -As long as the fixtures don't change, `rake karma:tests` is sufficient -(and saves you some time). - -Please note: Not all of the frontend fixtures are generated. Some are still static -files. These will not be touched by `rake karma:fixtures`. - -## Design Patterns - -### Singletons - -When exactly one object is needed for a given task, prefer to define it as a -`class` rather than as an object literal. Prefer also to explicitly restrict -instantiation, unless flexibility is important (e.g. for testing). - -```javascript -// bad - -gl.MyThing = { - prop1: 'hello', - method1: () => {} -}; - -// good - -class MyThing { - constructor() { - this.prop1 = 'hello'; - } - method1() {} -} - -gl.MyThing = new MyThing(); - -// best - -let singleton; - -class MyThing { - constructor() { - if (!singleton) { - singleton = this; - singleton.init(); - } - return singleton; - } - - init() { - this.prop1 = 'hello'; - } - - method1() {} -} - -gl.MyThing = MyThing; - -``` - -### Manipulating the DOM in a JS Class - -When writing a class that needs to manipulate the DOM guarantee a container option is provided. -This is useful when we need that class to be instantiated more than once in the same page. - -Bad: -```javascript -class Foo { - constructor() { - document.querySelector('.bar'); - } -} -new Foo(); -``` - -Good: -```javascript -class Foo { - constructor(opts) { - document.querySelector(`${opts.container} .bar`); - } -} - -new Foo({ container: '.my-element' }); -``` -You can find an example of the above in this [class][container-class-example]; - -## Supported browsers - -For our currently-supported browsers, see our [requirements][requirements]. - - -## Gotchas - -### Spec errors due to use of ES6 features in `.js` files - -If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being -thrown in Karma, Spinach, or Rspec tests but can't reproduce them manually, -you may have included `ES6`-style JavaScript in files that don't have the -`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file -you're working in (`git mv <file.js> <file.js.es6>`). - -### Spec errors due to use of unsupported JavaScript - -Similar errors will be thrown if you're using JavaScript features not yet -supported by our test runner's version of webkit, whether or not you've updated -the file extension. Examples of unsupported JavaScript features are: - -- Array.from -- Array.find -- Array.first -- Object.assign -- Async functions -- Generators -- Array destructuring -- For Of -- Symbol/Symbol.iterator -- Spread - -Until these are polyfilled or transpiled appropriately, they should not be used. -Please update this list with additional unsupported features or when any of -these are made usable. - -### Spec errors due to JavaScript not enabled - -If, as a result of a change you've made, a feature now depends on JavaScript to -run correctly, you need to make sure a JavaScript web driver is enabled when -specs are run. If you don't you'll see vague error messages from the spec -runner, and an explosion of vague console errors in the HTML snapshot. - -To enable a JavaScript driver in an `rspec` test, add `js: true` to the -individual spec or the context block containing multiple specs that need -JavaScript enabled: - -```ruby - -# For one spec -it 'presents information about abuse report', js: true do - # assertions... -end - -describe "Admin::AbuseReports", js: true do - it 'presents information about abuse report' do - # assertions... - end - it 'shows buttons for adding to abuse report' do - # assertions... - end -end -``` - -In Spinach, the JavaScript driver is enabled differently. In the `*.feature` -file for the failing spec, add the `@javascript` flag above the Scenario: - -``` -@javascript -Scenario: Developer can approve merge request - Given I am a "Shop" developer - And I visit project "Shop" merge requests page - And merge request 'Bug NS-04' must be approved - And I click link "Bug NS-04" - When I click link "Approve" - Then I should see approved merge request "Bug NS-04" - -``` +# Frontend Development Guidelines -[rails]: http://rubyonrails.org/ -[haml]: http://haml.info/ -[hamlit]: https://github.com/k0kubun/hamlit -[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations -[scss]: http://sass-lang.com/ -[es6]: https://babeljs.io/ -[sprockets]: https://github.com/rails/sprockets -[jquery]: https://jquery.com/ -[vue]: http://vuejs.org/ -[vue-docs]: http://vuejs.org/guide/index.html -[web-page-test]: http://www.webpagetest.org/ -[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/ -[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en -[browser-diet]: https://browserdiet.com/ -[d3]: https://d3js.org/ -[chartjs]: http://www.chartjs.org/ -[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8 -[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools -[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules -[observatory-cli]: https://github.com/mozilla/http-observatory-cli -[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html -[secure_headers]: https://github.com/twitter/secureheaders -[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP -[github-eng-csp]: http://githubengineering.com/githubs-csp-journey/ -[dropbox-csp-1]: https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/ -[dropbox-csp-2]: https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/ -[dropbox-csp-3]: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/ -[dropbox-csp-4]: https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/ -[mdn-sri]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity -[github-eng-sri]: http://githubengineering.com/subresource-integrity/ -[sprockets-sri]: https://github.com/rails/sprockets-rails#sri-support -[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting -[scss-style-guide]: scss_styleguide.md -[requirements]: ../install/requirements.md#supported-web-browsers -[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards -[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments -[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript -[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components -[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch -[vue-resource-repo]: https://github.com/pagekit/vue-resource -[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6 -[airbnb-js-style-guide]: https://github.com/airbnb/javascript -[eslintrc]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc -[team-page]: https://about.gitlab.com/team -[vue-section]: https://docs.gitlab.com/ce/development/frontend.html#how-to-build-a-new-feature-with-vue-js -[container-class-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js +This page has moved [here](fe_guide/index.md). diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index dcd978c4cd3..ec9e4dcc59d 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -42,6 +42,20 @@ To run several tests inside one directory: If you want to use [Spring](https://github.com/rails/spring) set `ENABLE_SPRING=1` in your environment. +## Compile Frontend Assets + +You shouldn't ever need to compile frontend assets manually in development, but +if you ever need to test how the assets get compiled in a production +environment you can do so with the following command: + +``` +RAILS_ENV=production NODE_ENV=production bundle exec rake gitlab:assets:compile +``` + +This will compile and minify all JavaScript and CSS assets and copy them along +with all other frontend assets (images, fonts, etc) into `/public/assets` where +they can be easily inspected. + ## Generate API documentation for project services (e.g. Slack) ``` diff --git a/doc/development/testing.md b/doc/development/testing.md index 5ac7b8dadeb..5bc958f5a96 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -34,16 +34,17 @@ GitLab uses [factory_girl] as a test fixture replacement. GitLab uses [Karma] to run its [Jasmine] JavaScript specs. They can be run on the command line via `bundle exec karma`. -- JavaScript tests live in `spec/javascripts/`, matching the folder structure of - `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js` has a corresponding - `spec/javascripts/behaviors/autosize_spec.js` file. +- JavaScript tests live in `spec/javascripts/`, matching the folder structure + of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js` + has a corresponding `spec/javascripts/behaviors/autosize_spec.js` file. - Haml fixtures required for JavaScript tests live in `spec/javascripts/fixtures`. They should contain the bare minimum amount of markup necessary for the test. > **Warning:** Keep in mind that a Rails view may change and invalidate your test, but everything will still pass because your fixture - doesn't reflect the latest view. + doesn't reflect the latest view. Because of this we encourage you to + generate fixtures from actual rails views whenever possible. - Keep in mind that in a CI environment, these tests are run in a headless browser and you will not have access to certain APIs, such as @@ -53,6 +54,8 @@ the command line via `bundle exec karma`. [Karma]: https://github.com/karma-runner/karma [Jasmine]: https://github.com/jasmine/jasmine +For more information, see the [frontend testing guide](fe_guide/testing.md). + ## RSpec ### General Guidelines diff --git a/doc/install/installation.md b/doc/install/installation.md index 177e1a9378b..a6b10176450 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -109,7 +109,8 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -**Note:** The current supported Ruby versions are 2.1.x and 2.3.x. 2.3.x is preferred, and support for 2.1.x will be dropped in the future. +**Note:** The current supported Ruby version is 2.3.x. GitLab 9.0 dropped support +for Ruby 2.1.x. The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab in production, frequently leads to hard to diagnose problems. For example, @@ -456,6 +457,36 @@ Make GitLab start on boot: sudo update-rc.d gitlab defaults 21 +### Install Gitaly + +As of GitLab 9.0 Gitaly is an **optional** component. Its +configuration is expected to change in GitLab 9.1. It is OK to wait +with setting up Gitaly until you upgrade to GitLab 9.1 or later. + + # 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 + + # Restrict Gitaly socket access + sudo chmod 0700 /home/git/gitlab/tmp/sockets/private + sudo chown git /home/git/gitlab/tmp/sockets/private + + # Configure Gitaly + echo 'GITALY_SOCKET_PATH=/home/git/gitlab/tmp/sockets/private/gitaly.socket' | \ + sudo -u git tee -a /home/git/gitaly/env + + # Enable Gitaly in the init script + echo 'gitaly_enabled=true' | sudo tee -a /etc/default/gitlab + +Next, edit `/home/git/gitlab/config/gitlab.yml` and make sure `socket_path` in +the `gitaly:` section is uncommented. + + # <- gitlab.yml indentation starts here + gitaly: + socket_path: tmp/sockets/private/gitaly.socket + +For more information about configuring Gitaly see +[doc/administration/gitaly](../administration/gitaly). + ### Setup Logrotate sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab diff --git a/doc/profile/README.md b/doc/profile/README.md index 6f8359d87fa..54e44d65959 100644 --- a/doc/profile/README.md +++ b/doc/profile/README.md @@ -1,4 +1,4 @@ -# Profile Settings +# Profile settings -- [Preferences](preferences.md) -- [Two-factor Authentication (2FA)](two_factor_authentication.md) +- [Preferences](../user/profile/preferences.md) +- [Two-factor Authentication (2FA)](../user/profile/account/two_factor_authentication.md) diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md index 4f2b00f3dd1..cc16f3afe41 100644 --- a/doc/profile/preferences.md +++ b/doc/profile/preferences.md @@ -1,36 +1 @@ -# Profile Preferences - -Settings in the **Profile > Preferences** page allow the user to customize -various aspects of the site to their liking. - -## Syntax highlighting theme - -_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a -list of supported languages visit the rouge website._ - -Changing this setting allows the user to customize the theme used when viewing -syntax highlighted code on the site. - -The default is **White**. - -## Behavior - -### Default Dashboard - -For users who have access to a large number of projects but only keep up with a -select few, the amount of activity on the default Dashboard page can be -overwhelming. - -Changing this setting allows the user to redefine what their default dashboard -will be. Setting it to **Starred Projects** will make that Dashboard view the -default when signing in or clicking the application logo in the upper left. - -The default is **Your Projects**. - -### Default Project view - -It allows user to choose what content he or she want to see on project page. - -The default is **Readme**. - -[rouge]: http://rouge.jneen.net/ "Rouge website" +This document was moved to [another location](../user/profile/preferences.md). diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md index 327ecb7cdc2..aa1c659717e 100644 --- a/doc/update/8.13-to-8.14.md +++ b/doc/update/8.13-to-8.14.md @@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-14-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v4.0.3 +sudo -u git -H git checkout v4.1.1 ``` ### 6. Update gitlab-workhorse diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md index 626507c0482..6308317b1f2 100644 --- a/doc/update/8.17-to-9.0.md +++ b/doc/update/8.17-to-9.0.md @@ -25,9 +25,8 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ### 3. Update Ruby -We will continue supporting Ruby < 2.3 for the time being but we recommend you -upgrade to Ruby 2.3 if you're running a source installation, as this is the same -version that ships with our Omnibus package. +NOTE: GitLab 9.0 only supports Ruby 2.3.x and dropped support for Ruby 2.1.x. Be +sure to upgrade your interpreter if necessary. You can check which version you are running with `ruby -v`. @@ -98,54 +97,32 @@ cd /home/git/gitlab sudo -u git -H git checkout 9-0-stable-ee ``` -### 6. Install libs, migrations, etc. +### 6. Update gitlab-shell ```bash -cd /home/git/gitlab - -# MySQL installations (note: the line below states '--without postgres') -sudo -u git -H bundle install --without postgres development test --deployment - -# PostgreSQL installations (note: the line below states '--without mysql') -sudo -u git -H bundle install --without mysql development test --deployment - -# Optional: clean up old gems -sudo -u git -H bundle clean - -# Run database migrations -sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production - -# Install/update frontend asset dependencies -sudo -u git -H npm install --production +cd /home/git/gitlab-shell -# Clean up assets and cache -sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) ``` -**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). - ### 7. Update gitlab-workhorse Install and compile gitlab-workhorse. This requires [Go 1.5](https://golang.org/dl) which should already be on your system from -GitLab 8.1. - -```bash -cd /home/git/gitlab - -sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production -``` - -### 8. Update gitlab-shell +GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. ```bash -cd /home/git/gitlab-shell +cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v5.0.0 +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) +sudo -u git -H make ``` -### 9. Update configuration files +### 8. Update configuration files #### New configuration options for `gitlab.yml` @@ -282,14 +259,48 @@ For Ubuntu 16.04.1 LTS: sudo systemctl daemon-reload ``` -### 10. Start application +### 9. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production + +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). + +### 10. Optional: install Gitaly + +Gitaly is still an optional component of GitLab. If you want to save time +during your 9.0 upgrade **you can skip this step**. + +If you do want to set up Gitaly in GitLab 9.0 then follow [Gitaly section of the installation +guide](../install/installation.md#install-gitaly). + +### 11. Start application ```bash sudo service gitlab start sudo service nginx restart ``` -### 11. Check application status +### 12. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md new file mode 100644 index 00000000000..e5038835027 --- /dev/null +++ b/doc/user/profile/preferences.md @@ -0,0 +1,64 @@ +# Profile preferences + +A user's profile preferences page allows the user to customize various aspects +of GitLab to their liking. + +To navigate to your profile's preferences, click your avatar icon in the top +right corner and select **Settings**. From there on, choose the **Preferences** +tab. + +## Syntax highlighting theme + +>**Note:** +GitLab uses the [rouge Ruby library][rouge] for syntax highlighting. For a +list of supported languages visit the rouge website. + +Changing this setting allows you to customize the color theme when viewing any +syntax highlighted code on GitLab. + +The default one is **White**, and you can choose among 5 different colors: + +- White +- Dark +- Solarized light +- Solarized dark +- Monokai + +## Behavior + +The following settings allow you to customize the behavior of GitLab's layout +and default views of your dashboard and the projects' landing pages. + +### Layout width + +GitLab can be set up to use different widths depending on your liking. Choose +between the fixed (max. 1200px) and the fluid (100%) application layout. + +### Default dashboard + +For users who have access to a large number of projects but only keep up with a +select few, the amount of activity on the default Dashboard page can be +overwhelming. Changing this setting allows you to redefine what your default +dashboard will be. + +You have 6 options here that you can use for your default dashboard view: + +- Your projects (default) +- Starred projects +- Your projects' activity +- Starred projects' activity +- Your groups +- Your [Todos] + +### Default project view + +The default project view settings allows you to choose what content you want to +see on a project's landing page. + +You can choose between 2 options: + +- Show the files and the readme (default) +- Show the project's activity + +[rouge]: http://rouge.jneen.net/ "Rouge website" +[todos]: ../../workflow/todos.md diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 7aa9b46081a..f94357abec9 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -36,6 +36,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | <kbd>g</kbd> + <kbd>p</kbd> | Go to projects | | <kbd>g</kbd> + <kbd>i</kbd> | Go to issues | | <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests | +| <kbd>g</kbd> + <kbd>t</kbd> | Go to todos | ## Project diff --git a/lib/api/issues.rb b/lib/api/issues.rb index b3183357625..fd2674910d2 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -91,7 +91,7 @@ module API use :issues_params end get ":id/issues" do - project = find_project(params[:id]) + project = find_project!(params[:id]) issues = find_issues(project_id: project.id) diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb index cead03b1e6b..54c6a8060b8 100644 --- a/lib/api/v3/issues.rb +++ b/lib/api/v3/issues.rb @@ -103,7 +103,7 @@ module API use :issues_params end get ":id/issues" do - project = find_project(params[:id]) + project = find_project!(params[:id]) issues = find_issues(project_id: project.id) diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index 651b55523c0..123c92fd250 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -2,7 +2,6 @@ module Banzai module Filter # HTML filter that wraps links around inline images. class ImageLinkFilter < HTML::Pipeline::Filter - # Find every image that isn't already wrapped in an `a` tag, create # a new node (a link to the image source), copy the image as a child # of the anchor, and then replace the img with the link-wrapped version. @@ -12,7 +11,8 @@ module Banzai 'a', class: 'no-attachment-icon', href: img['src'], - target: '_blank' + target: '_blank', + rel: 'noopener noreferrer' ) link.children = img.clone diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb index b64a1287d4d..35cb10eae5d 100644 --- a/lib/banzai/filter/video_link_filter.rb +++ b/lib/banzai/filter/video_link_filter.rb @@ -43,6 +43,7 @@ module Banzai element['title'] || element['alt'], href: element['src'], target: '_blank', + rel: 'noopener noreferrer', title: "Download '#{element['title'] || element['alt']}'") download_paragraph = doc.document.create_element('p') download_paragraph.children = link diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb index 1877429ac46..ee034d9cc56 100644 --- a/lib/gitlab/ci/build/step.rb +++ b/lib/gitlab/ci/build/step.rb @@ -7,13 +7,12 @@ module Gitlab WHEN_ALWAYS = 'always'.freeze attr_reader :name - attr_writer :script - attr_accessor :timeout, :when, :allow_failure + attr_accessor :script, :timeout, :when, :allow_failure class << self def from_commands(job) self.new(:script).tap do |step| - step.script = job.commands + step.script = job.commands.split("\n") step.timeout = job.timeout step.when = WHEN_ON_SUCCESS end @@ -36,10 +35,6 @@ module Gitlab @name = name @allow_failure = false end - - def script - @script.split("\n") - end end end end diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index e0fdf3f3d64..0829c1c318e 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -5,35 +5,44 @@ module Gitlab CE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ce.git'.freeze EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze CHECK_DIR = Rails.root.join('ee_compat_check') - MAX_FETCH_DEPTH = 500 IGNORED_FILES_REGEX = /(VERSION|CHANGELOG\.md:\d+)/.freeze - - attr_reader :repo_dir, :patches_dir, :ce_repo, :ce_branch + PLEASE_READ_THIS_BANNER = %Q{ + ============================================================ + ===================== PLEASE READ THIS ===================== + ============================================================ + }.freeze + THANKS_FOR_READING_BANNER = %Q{ + ============================================================ + ==================== THANKS FOR READING ==================== + ============================================================\n + }.freeze + + attr_reader :ee_repo_dir, :patches_dir, :ce_repo, :ce_branch, :ee_branch_found + attr_reader :failed_files def initialize(branch:, ce_repo: CE_REPO) - @repo_dir = CHECK_DIR.join('repo') + @ee_repo_dir = CHECK_DIR.join('ee-repo') @patches_dir = CHECK_DIR.join('patches') @ce_branch = branch @ce_repo = ce_repo end def check - ensure_ee_repo ensure_patches_dir - generate_patch(ce_branch, ce_patch_full_path) - Dir.chdir(repo_dir) do - step("In the #{repo_dir} directory") + ensure_ee_repo + Dir.chdir(ee_repo_dir) do + step("In the #{ee_repo_dir} directory") status = catch(:halt_check) do ce_branch_compat_check! - delete_ee_branch_locally! + delete_ee_branches_locally! ee_branch_presence_check! ee_branch_compat_check! end - delete_ee_branch_locally! + delete_ee_branches_locally! if status.nil? true @@ -46,11 +55,13 @@ module Gitlab private def ensure_ee_repo - if Dir.exist?(repo_dir) - step("#{repo_dir} already exists") + if Dir.exist?(ee_repo_dir) + step("#{ee_repo_dir} already exists") else - cmd = %W[git clone --branch master --single-branch --depth 200 #{EE_REPO} #{repo_dir}] - step("Cloning #{EE_REPO} into #{repo_dir}", cmd) + step( + "Cloning #{EE_REPO} into #{ee_repo_dir}", + %W[git clone --branch master --single-branch --depth=200 #{EE_REPO} #{ee_repo_dir}] + ) end end @@ -61,23 +72,18 @@ module Gitlab def generate_patch(branch, patch_path) FileUtils.rm(patch_path, force: true) - depth = 0 - loop do - depth += 50 - cmd = %W[git fetch --depth #{depth} origin --prune +refs/heads/master:refs/remotes/origin/master] - Gitlab::Popen.popen(cmd) - _, status = Gitlab::Popen.popen(%w[git merge-base FETCH_HEAD HEAD]) + find_merge_base_with_master(branch: branch) - raise "#{branch} is too far behind master, please rebase it!" if depth >= MAX_FETCH_DEPTH - break if status.zero? - end + step( + "Generating the patch against origin/master in #{patch_path}", + %w[git format-patch origin/master --stdout] + ) do |output, status| + throw(:halt_check, :ko) unless status.zero? - step("Generating the patch against master in #{patch_path}") - output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout]) - throw(:halt_check, :ko) unless status.zero? + File.write(patch_path, output) - File.write(patch_path, output) - throw(:halt_check, :ko) unless File.exist?(patch_path) + throw(:halt_check, :ko) unless File.exist?(patch_path) + end end def ce_branch_compat_check! @@ -88,9 +94,17 @@ module Gitlab end def ee_branch_presence_check! - status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}]) + _, status = step("Fetching origin/#{ee_branch_prefix}", %W[git fetch origin #{ee_branch_prefix}]) - unless status.zero? + if status.zero? + @ee_branch_found = ee_branch_prefix + else + _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}]) + end + + if status.zero? + @ee_branch_found = ee_branch_suffix + else puts puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg @@ -99,9 +113,9 @@ module Gitlab end def ee_branch_compat_check! - step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD]) + step("Checking out origin/#{ee_branch_found}", %W[git checkout -b #{ee_branch_found} FETCH_HEAD]) - generate_patch(ee_branch, ee_patch_full_path) + generate_patch(ee_branch_found, ee_patch_full_path) unless check_patch(ee_patch_full_path).zero? puts @@ -116,36 +130,72 @@ module Gitlab def check_patch(patch_path) step("Checking out master", %w[git checkout master]) - step("Reseting to latest master", %w[git reset --hard origin/master]) - - step("Checking if #{patch_path} applies cleanly to EE/master") - output, status = Gitlab::Popen.popen(%W[git apply --check --3way #{patch_path}]) - - unless status.zero? - failed_files = output.lines.reduce([]) do |memo, line| - if line.start_with?('error: patch failed:') - file = line.sub(/\Aerror: patch failed: /, '') - memo << file unless file =~ IGNORED_FILES_REGEX + step("Resetting to latest master", %w[git reset --hard origin/master]) + step( + "Checking if #{patch_path} applies cleanly to EE/master", + %W[git apply --check --3way #{patch_path}] + ) do |output, status| + unless status.zero? + @failed_files = output.lines.reduce([]) do |memo, line| + if line.start_with?('error: patch failed:') + file = line.sub(/\Aerror: patch failed: /, '') + memo << file unless file =~ IGNORED_FILES_REGEX + end + memo end - memo + + status = 0 if failed_files.empty? end - if failed_files.empty? - status = 0 - else - puts "\nConflicting files:" - failed_files.each do |file| - puts " - #{file}" - end + status + end + end + + def delete_ee_branches_locally! + command(%w[git checkout master]) + command(%W[git branch --delete --force #{ee_branch_prefix}]) + command(%W[git branch --delete --force #{ee_branch_suffix}]) + end + + def merge_base_found? + step( + "Finding merge base with master", + %w[git merge-base origin/master HEAD] + ) do |output, status| + if status.zero? + puts "Merge base was found: #{output}" + true end end + end + + def find_merge_base_with_master(branch:) + return if merge_base_found? + + # Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403) + # In total we go (20 + 54 + 148 + 403 = 625) commits deeper + depth = 20 + success = + (3..6).any? do |factor| + depth += Math.exp(factor).to_i + # Repository is initially cloned with a depth of 20 so we need to fetch + # deeper in the case the branch has more than 20 commits on top of master + fetch(branch: branch, depth: depth) + fetch(branch: 'master', depth: depth) + + merge_base_found? + end - status + raise "\n#{branch} is too far behind master, please rebase it!\n" unless success end - def delete_ee_branch_locally! - command(%w[git checkout master]) - step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}]) + def fetch(branch:, depth:) + step( + "Fetching deeper...", + %W[git fetch --depth=#{depth} --prune origin +refs/heads/#{branch}:refs/remotes/origin/#{branch}] + ) do |output, status| + raise "Fetch failed: #{output}" unless status.zero? + end end def ce_patch_name @@ -156,8 +206,12 @@ module Gitlab @ce_patch_full_path ||= patches_dir.join(ce_patch_name) end - def ee_branch - @ee_branch ||= "#{ce_branch}-ee" + def ee_branch_suffix + @ee_branch_suffix ||= "#{ce_branch}-ee" + end + + def ee_branch_prefix + @ee_branch_prefix ||= "ee-#{ce_branch}" end def ee_patch_name @@ -178,98 +232,125 @@ module Gitlab if cmd start = Time.now puts "\n$ #{cmd.join(' ')}" - status = command(cmd) - puts "\nFinished in #{Time.now - start} seconds" - status + + output, status = command(cmd) + puts "\n==> Finished in #{Time.now - start} seconds" + + if block_given? + yield(output, status) + else + [output, status] + end end end def command(cmd) - output, status = Gitlab::Popen.popen(cmd) - puts output - - status + Gitlab::Popen.popen(cmd) end def applies_cleanly_msg(branch) - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 🎉 Congratulations!! 🎉 - The #{branch} branch applies cleanly to EE/master! + The `#{branch}` branch applies cleanly to EE/master! - Much ❤️!! - =================================================================\n - MSG + Much ❤️! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } end def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 💥 Oh no! 💥 - The #{ce_branch} branch does not apply cleanly to the current - EE/master, and no #{ee_branch} branch was found in the EE repository. + The `#{ce_branch}` branch does not apply cleanly to the current + EE/master, and no `#{ee_branch_prefix}` or `#{ee_branch_suffix}` branch + was found in the EE repository. - Please create a #{ee_branch} branch that includes changes from - #{ce_branch} but also specific changes than can be applied cleanly - to EE/master. + #{conflicting_files_msg} + + We advise you to create a `#{ee_branch_prefix}` or `#{ee_branch_suffix}` + branch that includes changes from `#{ce_branch}` but also specific changes + than can be applied cleanly to EE/master. In some cases, the conflicts + are trivial and you can ignore the warning from this job. As always, + use your best judgment! There are different ways to create such branch: - 1. Create a new branch based on the CE branch and rebase it on top of EE/master + 1. Create a new branch from master and cherry-pick your CE commits # In the EE repo - $ git fetch #{ce_repo} #{ce_branch} - $ git checkout -b #{ee_branch} FETCH_HEAD - - # You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit - # before rebasing to limit the conflicts-resolving steps during the rebase $ git fetch origin - $ git rebase origin/master + $ git checkout -b #{ee_branch_prefix} origin/master + $ git fetch #{ce_repo} #{ce_branch} + $ git cherry-pick SHA # Repeat for all the commits you want to pick - At this point you will likely have conflicts. - Solve them, and continue/finish the rebase. + You can squash the `#{ce_branch}` commits into a single "Port of #{ce_branch} to EE" commit. - You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE". + 2. Apply your branch's patch to EE - 2. Create a new branch from master and cherry-pick your CE commits + # In the CE repo + $ git fetch origin master + $ git format-patch origin/master --stdout > #{ce_branch}.patch # In the EE repo - $ git fetch origin - $ git checkout -b #{ee_branch} origin/master - $ git fetch #{ce_repo} #{ce_branch} - $ git cherry-pick SHA # Repeat for all the commits you want to pick + $ git fetch origin master + $ git checkout -b #{ee_branch_prefix} origin/master + $ git apply --3way path/to/#{ce_branch}.patch + + At this point you might have conflicts such as: - You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit. + error: patch failed: lib/gitlab/ee_compat_check.rb:5 + Falling back to three-way merge... + Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts. + U lib/gitlab/ee_compat_check.rb - Don't forget to push your branch to #{EE_REPO}: + Resolve them, stage the changes and commit them. + + ⚠️ Don't forget to push your branch to gitlab-ee: # In the EE repo - $ git push origin #{ee_branch} + $ git push origin #{ee_branch_prefix} + + ⚠️ Also, don't forget to create a new merge request on gitlab-ce and + cross-link it with the CE merge request. - You can then retry this failed build, and hopefully it should pass. + Once this is done, you can retry this failed build, and it should pass. - Stay 💪 ! - =================================================================\n - MSG + Stay 💪 ! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } end def ee_branch_doesnt_apply_cleanly_msg - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 💥 Oh no! 💥 - The #{ce_branch} does not apply cleanly to the current - EE/master, and even though a #{ee_branch} branch exists in the EE - repository, it does not apply cleanly either to EE/master! + The `#{ce_branch}` does not apply cleanly to the current EE/master, and + even though a `#{ee_branch_found}` branch + exists in the EE repository, it does not apply cleanly either to + EE/master! + + #{conflicting_files_msg} - Please update the #{ee_branch}, push it again to #{EE_REPO}, and + Please update the `#{ee_branch_found}`, push it again to gitlab-ee, and retry this build. - Stay 💪 ! - =================================================================\n - MSG + Stay 💪 ! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } + end + + def conflicting_files_msg + failed_files.reduce("The conflicts detected were as follows:\n") do |memo, file| + memo << "\n - #{file}" + end end end end diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 9532e432f78..0039fc01c8f 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -1,7 +1,7 @@ module Gitlab module EtagCaching class Store - EXPIRY_TIME = 10.minutes + EXPIRY_TIME = 20.minutes REDIS_NAMESPACE = 'etag:'.freeze def get(key) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 5534d4af439..1ce47ef2b05 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -2,6 +2,8 @@ require 'gitaly' module Gitlab module GitalyClient + SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'.freeze + def self.gitaly_address if Gitlab.config.gitaly.socket_path "unix://#{Gitlab.config.gitaly.socket_path}" @@ -39,5 +41,10 @@ module Gitlab yield is_enabled end end + + def self.expected_server_version + path = Rails.root.join(SERVER_VERSION_FILE) + path.read.chomp + end end end diff --git a/lib/gitlab/testing/request_blocker_middleware.rb b/lib/gitlab/testing/request_blocker_middleware.rb new file mode 100644 index 00000000000..aa67fa08577 --- /dev/null +++ b/lib/gitlab/testing/request_blocker_middleware.rb @@ -0,0 +1,61 @@ +# rubocop:disable Style/ClassVars + +# This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests +# Rack middleware that keeps track of the number of active requests and can block new requests. +module Gitlab + module Testing + class RequestBlockerMiddleware + @@num_active_requests = Concurrent::AtomicFixnum.new(0) + @@block_requests = Concurrent::AtomicBoolean.new(false) + + # Returns the number of requests the server is currently processing. + def self.num_active_requests + @@num_active_requests.value + end + + # Prevents the server from accepting new requests. Any new requests will return an HTTP + # 503 status. + def self.block_requests! + @@block_requests.value = true + end + + # Allows the server to accept requests again. + def self.allow_requests! + @@block_requests.value = false + end + + def initialize(app) + @app = app + end + + def call(env) + increment_active_requests + if block_requests? + block_request(env) + else + @app.call(env) + end + ensure + decrement_active_requests + end + + private + + def block_requests? + @@block_requests.true? + end + + def block_request(env) + [503, {}, []] + end + + def increment_active_requests + @@num_active_requests.increment + end + + def decrement_active_requests + @@num_active_requests.decrement + end + end + end +end diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb new file mode 100644 index 00000000000..7e14a566696 --- /dev/null +++ b/lib/gitlab/url_blocker.rb @@ -0,0 +1,59 @@ +require 'resolv' + +module Gitlab + class UrlBlocker + class << self + # Used to specify what hosts and port numbers should be prohibited for project + # imports. + VALID_PORTS = [22, 80, 443].freeze + + def blocked_url?(url) + return false if url.nil? + + blocked_ips = ["127.0.0.1", "::1", "0.0.0.0"] + blocked_ips.concat(Socket.ip_address_list.map(&:ip_address)) + + begin + uri = Addressable::URI.parse(url) + # Allow imports from the GitLab instance itself but only from the configured ports + return false if internal?(uri) + + return true if blocked_port?(uri.port) + + server_ips = Resolv.getaddresses(uri.hostname) + return true if (blocked_ips & server_ips).any? + rescue Addressable::URI::InvalidURIError + return true + end + + false + end + + private + + def blocked_port?(port) + return false if port.blank? + + port < 1024 && !VALID_PORTS.include?(port) + end + + def internal?(uri) + internal_web?(uri) || internal_shell?(uri) + end + + def internal_web?(uri) + uri.hostname == config.gitlab.host && + (uri.port.blank? || uri.port == config.gitlab.port) + end + + def internal_shell?(uri) + uri.hostname == config.gitlab_shell.ssh_host && + (uri.port.blank? || uri.port == config.gitlab_shell.ssh_port) + end + + def config + Gitlab.config + end + end + end +end diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index c81dc7e30d0..9ce13feb79a 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -18,6 +18,12 @@ module Gitlab false end + def self.http_credentials_for_user(user) + return {} unless user.respond_to?(:username) + + { user: user.username } + end + def initialize(url, credentials: nil) @url = Addressable::URI.parse(url.strip) @credentials = credentials diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 2248763c106..8f1d1fdc02e 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -96,8 +96,8 @@ module Gitlab end def level_value(level) - return string_options[level] if level.is_a? String - level + return level.to_i if level.to_i.to_s == level.to_s && string_options.key(level.to_i) + string_options[level] || PRIVATE end def string_level(level) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 5fd7f0f98bd..09e121e5120 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -48,6 +48,10 @@ gitlab_pages_pid_path="$pid_path/gitlab-pages.pid" gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090" gitlab_pages_log="$app_root/log/gitlab-pages.log" shell_path="/bin/bash" +gitaly_enabled=false +gitaly_dir=$(cd $app_root/../gitaly 2> /dev/null && pwd) +gitaly_pid_path="$pid_path/gitaly.pid" +gitaly_log="$app_root/log/gitaly.log" # Read configuration variable file if it is present test -f /etc/default/gitlab && . /etc/default/gitlab @@ -101,13 +105,20 @@ check_pids(){ gppid=0 fi fi + if [ "$gitaly_enabled" = true ]; then + if [ -f "$gitaly_pid_path" ]; then + gapid=$(cat "$gitaly_pid_path") + else + gapid=0 + fi + fi } ## Called when we have started the two processes and are waiting for their pid files. wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing its pid i=0; - while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; } || { [ "$gitlab_pages_enabled" = true ] && [ ! -f $gitlab_pages_pid_path ]; }; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; } || { [ "$gitlab_pages_enabled" = true ] && [ ! -f $gitlab_pages_pid_path ]; } || { [ "$gitaly_enabled" = true ] && [ ! -f $gitaly_pid_path ]; }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -164,7 +175,15 @@ check_status(){ gitlab_pages_status="-1" fi fi - if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; } && { [ "$gitlab_pages_enabled" != true ] || [ $gitlab_pages_status = 0 ]; }; then + if [ "$gitaly_enabled" = true ]; then + if [ $gapid -ne 0 ]; then + kill -0 "$gapid" 2>/dev/null + gitaly_status="$?" + else + gitaly_status="-1" + fi + fi + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; } && { [ "$gitlab_pages_enabled" != true ] || [ $gitlab_pages_status = 0 ]; } && { [ "$gitaly_enabled" != true ] || [ $gitaly_status = 0 ]; }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -213,12 +232,19 @@ check_stale_pids(){ exit 1 fi fi + if [ "$gitaly_enabled" = true ] && [ "$gapid" != "0" ] && [ "$gitaly_status" != "0" ]; then + echo "Removing stale Gitaly pid. This is most likely caused by Gitaly crashing the last time it ran." + if ! rm "$gitaly_pid_path"; then + echo "Unable to remove stale pid, exiting" + exit 1 + fi + fi } ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" != "0" ]; }; then echo "GitLab is not running." exit fi @@ -243,6 +269,9 @@ start_gitlab() { if [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then echo "Starting GitLab Pages" fi + if [ "$gitaly_enabled" = true ] && [ "$gitaly_status" != "0" ]; then + echo "Starting Gitaly" + fi # Then check if the service is running. If it is: don't start again. if [ "$web_status" = "0" ]; then @@ -292,6 +321,16 @@ start_gitlab() { fi fi + if [ "$gitaly_enabled" = true ]; then + if [ "$gitaly_status" = "0" ]; then + echo "Gitaly is already running with pid $gapid, not restarting" + else + $app_root/bin/daemon_with_pidfile $gitaly_pid_path \ + $app_root/bin/with_env $gitaly_dir/env \ + $gitaly_dir/gitaly >> $gitaly_log 2>&1 & + fi + fi + # Wait for the pids to be planted wait_for_pids # Finally check the status to tell wether or not GitLab is running @@ -322,13 +361,17 @@ stop_gitlab() { echo "Shutting down gitlab-pages" kill -- $(cat $gitlab_pages_pid_path) fi + if [ "$gitaly_status" = "0" ]; then + echo "Shutting down Gitaly" + kill -- $(cat $gitaly_pid_path) + fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; } || { [ "$gitaly_enabled" = true ] && [ "$gitaly_status" = "0" ]; }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" != "0" ]; }; then printf "\n" break fi @@ -343,6 +386,7 @@ stop_gitlab() { rm "$mail_room_pid_path" 2>/dev/null fi rm -f "$gitlab_pages_pid_path" + rm -f "$gitaly_pid_path" print_status } @@ -350,7 +394,7 @@ stop_gitlab() { ## Prints the status of GitLab and its components. print_status() { check_status - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" != "0" ]; }; then echo "GitLab is not running." return fi @@ -383,7 +427,14 @@ print_status() { printf "The GitLab Pages is \033[31mnot running\033[0m.\n" fi fi - if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" = "0" ]; }; then + if [ "$gitaly_enabled" = true ]; then + if [ "$gitaly_status" = "0" ]; then + echo "Gitaly with pid $gapid is running." + else + printf "Gitaly is \033[31mnot running\033[0m.\n" + fi + fi + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" = "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" = "0" ]; }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -414,7 +465,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; } || { [ "$gitaly_enabled" = true ] && [ "$gitaly_status" = "0" ]; }; then stop_gitlab fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index f6642527639..9472c3c992f 100644 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -84,3 +84,7 @@ mail_room_pid_path="$pid_path/mail_room.pid" # shell other than "bash" # The default is "/bin/bash" shell_path="/bin/bash" + +# This variable controls whether the init script starts/stops Gitaly +gitaly_enabled=false +gitaly_log="$app_root/log/gitaly.log" diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 098f9851b45..003d57adbbd 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -3,16 +3,16 @@ namespace :gitlab do desc 'GitLab | Assets | Compile all frontend assets' task compile: [ 'yarn:check', - 'assets:precompile', + 'rake:assets:precompile', 'webpack:compile', - 'gitlab:assets:fix_urls' + 'fix_urls' ] desc 'GitLab | Assets | Clean up old compiled frontend assets' - task clean: ['assets:clean'] + task clean: ['rake:assets:clean'] desc 'GitLab | Assets | Remove all compiled frontend assets' - task purge: ['assets:clobber'] + task purge: ['rake:assets:clobber'] desc 'GitLab | Assets | Uninstall frontend dependencies' task purge_modules: ['yarn:clobber'] diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake new file mode 100644 index 00000000000..c288e17ac8d --- /dev/null +++ b/lib/tasks/gitlab/gitaly.rake @@ -0,0 +1,23 @@ +namespace :gitlab do + namespace :gitaly do + desc "GitLab | Install or upgrade gitaly" + task :install, [:dir] => :environment do |t, args| + warn_user_is_not_gitlab + unless args.dir.present? + abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]") + end + + tag = "v#{Gitlab::GitalyClient.expected_server_version}" + repo = 'https://gitlab.com/gitlab-org/gitaly.git' + + checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir) + + _, status = Gitlab::Popen.popen(%w[which gmake]) + command = status.zero? ? 'gmake' : 'make' + + Dir.chdir(args.dir) do + run_command!([command]) + end + end + end +end diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb index bb755ae689b..cdba2262bc2 100644 --- a/lib/tasks/gitlab/task_helpers.rb +++ b/lib/tasks/gitlab/task_helpers.rb @@ -81,7 +81,7 @@ module Gitlab def run_command!(command) output, status = Gitlab::Popen.popen(command) - raise Gitlab::TaskFailedError unless status.zero? + raise Gitlab::TaskFailedError.new(output) unless status.zero? output end diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index f5caca3ddbf..8938bc515f5 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -3,10 +3,12 @@ require Rails.root.join('lib/gitlab/database/migration_helpers') require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes') require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes') +require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like') desc 'GitLab | Sets up PostgreSQL' task setup_postgresql: :environment do NamespacesProjectsPathLowerIndexes.new.up AddUsersLowerUsernameEmailIndexes.new.up AddLowerPathIndexToRoutes.new.up + IndexRoutesPathForLike.new.up end diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake new file mode 100644 index 00000000000..95735f43802 --- /dev/null +++ b/lib/tasks/tokens.rake @@ -0,0 +1,38 @@ +require_relative '../../app/models/concerns/token_authenticatable.rb' + +namespace :tokens do + desc "Reset all GitLab user auth tokens" + task reset_all_auth: :environment do + reset_all_users_token(:reset_authentication_token!) + end + + desc "Reset all GitLab email tokens" + task reset_all_email: :environment do + reset_all_users_token(:reset_incoming_email_token!) + end + + def reset_all_users_token(reset_token_method) + TmpUser.find_in_batches do |batch| + puts "Processing batch starting with user ID: #{batch.first.id}" + STDOUT.flush + + batch.each(&reset_token_method) + end + end +end + +class TmpUser < ActiveRecord::Base + include TokenAuthenticatable + + self.table_name = 'users' + + def reset_authentication_token! + write_new_token(:authentication_token) + save!(validate: false) + end + + def reset_incoming_email_token! + write_new_token(:incoming_email_token) + save!(validate: false) + end +end diff --git a/package.json b/package.json index b3d038bd3d1..7b6c4556e2c 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "stats-webpack-plugin": "^0.4.3", "timeago.js": "^2.0.5", "underscore": "^1.8.3", - "vue": "^2.1.10", + "visibilityjs": "^1.2.4", + "vue": "^2.2.4", "vue-resource": "^0.9.3", "webpack": "^2.2.1", "webpack-bundle-analyzer": "^2.3.0" diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb new file mode 100644 index 00000000000..84a1ce773a1 --- /dev/null +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Admin::ApplicationSettingsController do + include StubENV + + let(:admin) { create(:admin) } + + before do + sign_in(admin) + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + + describe 'PATCH #update' do + it 'updates the default_project_visibility for string value' do + patch :update, application_setting: { default_project_visibility: "20" } + + expect(response).to redirect_to(admin_application_settings_path) + expect(ApplicationSetting.current.default_project_visibility).to eq Gitlab::VisibilityLevel::PUBLIC + end + + it 'falls back to default with default_project_visibility setting is omitted' do + patch :update, application_setting: {} + + expect(response).to redirect_to(admin_application_settings_path) + expect(ApplicationSetting.current.default_project_visibility).to eq Gitlab::VisibilityLevel::PRIVATE + end + end +end diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index fa4cc0ebbe0..51f23e4eeb9 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -112,6 +112,17 @@ describe Import::BitbucketController do post :create, format: :js end end + + context 'when the Bitbucket user is unauthorized' do + render_views + + it 'returns unauthorized' do + allow(controller).to receive(:current_user).and_return(user) + allow(user).to receive(:can?).and_return(false) + + post :create, format: :js + end + end end context "when the repository owner is not the Bitbucket user" do diff --git a/spec/controllers/projects/builds_controller_specs.rb b/spec/controllers/projects/builds_controller_specs.rb new file mode 100644 index 00000000000..d501f7b3155 --- /dev/null +++ b/spec/controllers/projects/builds_controller_specs.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Projects::BuildsController do + include ApiHelpers + + let(:project) { create(:empty_project, :public) } + + describe 'GET trace.json' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:user) { create(:user) } + + context 'when user is logged in as developer' do + before do + project.add_developer(user) + sign_in(user) + get_trace + end + + it 'traces build log' do + expect(response).to have_http_status(:ok) + expect(json_response['id']).to eq build.id + expect(json_response['status']).to eq build.status + end + end + + context 'when user is logged in as non member' do + before do + sign_in(user) + get_trace + end + + it 'traces build log' do + expect(response).to have_http_status(:ok) + expect(json_response['id']).to eq build.id + expect(json_response['status']).to eq build.status + end + end + + def get_trace + get :trace, namespace_id: project.namespace, + project_id: project, + id: build.id, + format: :json + end + end +end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 8263301c439..c467ab9fb8a 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -152,6 +152,24 @@ describe Projects::IssuesController do it_behaves_like 'update invalid issuable', Issue + context 'changing the assignee' do + it 'limits the attributes exposed on the assignee' do + assignee = create(:user) + project.add_developer(assignee) + + put :update, + namespace_id: project.namespace.to_param, + project_id: project, + id: issue.iid, + issue: { assignee_id: assignee.id }, + format: :json + body = JSON.parse(response.body) + + expect(body['assignee'].keys) + .to match_array(%w(name username avatar_url)) + end + end + context 'when moving issue to another private project' do let(:another_project) { create(:empty_project, :private) } @@ -223,10 +241,27 @@ describe Projects::IssuesController do expect(spam_logs.first.recaptcha_verified).to be_falsey end - it 'renders verify template' do - update_spam_issue + context 'as HTML' do + it 'renders verify template' do + update_spam_issue + + expect(response).to render_template(:verify) + end + end + + context 'as JSON' do + before do + update_issue({ title: 'Spam Title', description: 'Spam lives here' }, format: :json) + end + + it 'renders json errors' do + expect(json_response) + .to eql("errors" => ["Your issue has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."]) + end - expect(response).to render_template(:verify) + it 'returns 422 status' do + expect(response).to have_http_status(422) + end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 250d64f7055..c310d830e81 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -203,6 +203,24 @@ describe Projects::MergeRequestsController do end describe 'PUT update' do + context 'changing the assignee' do + it 'limits the attributes exposed on the assignee' do + assignee = create(:user) + project.add_developer(assignee) + + put :update, + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + merge_request: { assignee_id: assignee.id }, + format: :json + body = JSON.parse(response.body) + + expect(body['assignee'].keys) + .to match_array(%w(name username avatar_url)) + end + end + context 'there is no source project' do let(:project) { create(:project) } let(:fork_project) { create(:forked_project_with_submodules) } diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 6b0d084614b..f78086211f7 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -172,7 +172,7 @@ FactoryGirl.define do { image: 'ruby:2.1', services: ['postgres'], - after_script: "ls\ndate", + after_script: %w(ls date), artifacts: { name: 'artifacts_file', untracked: false, diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 21487541507..ae0bbbd6aeb 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -4,7 +4,6 @@ FactoryGirl.define do author association :source_project, :repository, factory: :project target_project { source_project } - project { target_project } # $ git log --pretty=oneline feature..master # 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index b4095095887..5099441dce2 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -9,6 +9,13 @@ feature 'Admin updates settings', feature: true do visit admin_application_settings_path end + scenario 'Change visibility settings' do + choose "application_setting_default_project_visibility_20" + click_button 'Save' + + expect(page).to have_content "Application settings saved successfully" + end + scenario 'Change application settings' do uncheck 'Gravatar enabled' fill_in 'Home page URL', with: 'https://about.gitlab.com/' @@ -27,6 +34,7 @@ feature 'Admin updates settings', feature: true do fill_in 'Username', with: 'test_user' fill_in 'service_push_channel', with: '#test_channel' page.check('Notify only broken pipelines') + page.check('Notify only default branch') check_all_events click_on 'Save' diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index a7c22615b89..58b14e09740 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe "Dashboard Issues Feed", feature: true do describe "GET /issues" do - let!(:user) { create(:user) } + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } let!(:project1) { create(:project) } let!(:project2) { create(:project) } @@ -31,7 +32,7 @@ describe "Dashboard Issues Feed", feature: true do end context "issue with basic fields" do - let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') } + let!(:issue2) { create(:issue, author: user, assignee: assignee, project: project2, description: 'test desc') } it "renders issue fields" do visit issues_dashboard_path(:atom, private_token: user.private_token) @@ -39,8 +40,8 @@ describe "Dashboard Issues Feed", feature: true do entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") expect(entry).to be_present - expect(entry).to have_selector('author email', text: issue2.author_email) - expect(entry).to have_selector('assignee email', text: issue2.author_email) + expect(entry).to have_selector('author email', text: issue2.author_public_email) + expect(entry).to have_selector('assignee email', text: issue2.assignee_public_email) expect(entry).not_to have_selector('labels') expect(entry).not_to have_selector('milestone') expect(entry).to have_selector('description', text: issue2.description) @@ -50,7 +51,7 @@ describe "Dashboard Issues Feed", feature: true do context "issue with label and milestone" do let!(:milestone1) { create(:milestone, project: project1, title: 'v1') } let!(:label1) { create(:label, project: project1, title: 'label1') } - let!(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone1) } + let!(:issue1) { create(:issue, author: user, assignee: assignee, project: project1, milestone: milestone1) } before do issue1.labels << label1 @@ -62,8 +63,8 @@ describe "Dashboard Issues Feed", feature: true do entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") expect(entry).to be_present - expect(entry).to have_selector('author email', text: issue1.author_email) - expect(entry).to have_selector('assignee email', text: issue1.author_email) + expect(entry).to have_selector('author email', text: issue1.author_public_email) + expect(entry).to have_selector('assignee email', text: issue1.assignee_public_email) expect(entry).to have_selector('labels label', text: label1.title) expect(entry).to have_selector('milestone', text: milestone1.title) expect(entry).not_to have_selector('description') diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index a01a050a013..b3903ec2faf 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe 'Issues Feed', feature: true do describe 'GET /issues' do - let!(:user) { create(:user) } + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } let!(:group) { create(:group) } let!(:project) { create(:project) } - let!(:issue) { create(:issue, author: user, project: project) } + let!(:issue) { create(:issue, author: user, assignee: assignee, project: project) } before do project.team << [user, :developer] @@ -20,7 +21,8 @@ describe 'Issues Feed', feature: true do expect(response_headers['Content-Type']). to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{project.name} issues") - expect(body).to have_selector('author email', text: issue.author_email) + expect(body).to have_selector('author email', text: issue.author_public_email) + expect(body).to have_selector('assignee email', text: issue.author_public_email) expect(body).to have_selector('entry summary', text: issue.title) end end @@ -33,7 +35,8 @@ describe 'Issues Feed', feature: true do expect(response_headers['Content-Type']). to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{project.name} issues") - expect(body).to have_selector('author email', text: issue.author_email) + expect(body).to have_selector('author email', text: issue.author_public_email) + expect(body).to have_selector('assignee email', text: issue.author_public_email) expect(body).to have_selector('entry summary', text: issue.title) end end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index d17a418b8c3..1c0f97d8a1c 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -23,6 +23,20 @@ describe 'Issue Boards add issue modal', :feature, :js do wait_for_vue_resource end + it 'resets filtered search state' do + visit namespace_project_board_path(project.namespace, project, board, search: 'testing') + + wait_for_vue_resource + + click_button('Add issues') + + page.within('.add-issues-modal') do + expect(find('.form-control').value).to eq('') + expect(page).to have_selector('.clear-search', visible: false) + expect(find('.form-control')[:placeholder]).to eq('Search or filter results...') + end + end + context 'modal interaction' do it 'opens modal' do click_button('Add issues') diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 0e305c52358..881f1fca4d1 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -11,12 +11,16 @@ describe 'Commits' do stub_ci_pipeline_to_return_yaml_file end + let(:creator) { create(:user) } + let!(:pipeline) do create(:ci_pipeline, project: project, + user: creator, ref: project.default_branch, sha: project.commit.sha, - status: :success) + status: :success, + created_at: 5.months.ago) end context 'commit status is Generic Commit Status' do @@ -80,7 +84,8 @@ describe 'Commits' do it 'shows pipeline`s data' do expect(page).to have_content pipeline.sha[0..7] expect(page).to have_content pipeline.git_commit_message - expect(page).to have_content pipeline.git_author_name + expect(page).to have_content pipeline.user.name + expect(page).to have_content pipeline.created_at.strftime('%b %d, %Y') end end @@ -150,7 +155,7 @@ describe 'Commits' do it do expect(page).to have_content pipeline.sha[0..7] expect(page).to have_content pipeline.git_commit_message - expect(page).to have_content pipeline.git_author_name + expect(page).to have_content pipeline.user.name expect(page).to have_link('Download artifacts') expect(page).not_to have_link('Cancel running') expect(page).not_to have_link('Retry') @@ -169,7 +174,7 @@ describe 'Commits' do it do expect(page).to have_content pipeline.sha[0..7] expect(page).to have_content pipeline.git_commit_message - expect(page).to have_content pipeline.git_author_name + expect(page).to have_content pipeline.user.name expect(page).not_to have_link('Download artifacts') expect(page).not_to have_link('Cancel running') expect(page).not_to have_link('Retry') diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 62a2c54c94c..3642c0bfb5b 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -21,6 +21,11 @@ feature 'Dashboard shortcuts', feature: true, js: true do find('body').native.send_key('m') check_page_title('Merge Requests') + + find('body').native.send_key('g') + find('body').native.send_key('t') + + check_page_title('Todos') end def check_page_title(title) diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index 773ae4b38bc..9daaaa8e555 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -7,6 +7,7 @@ describe 'Explore Groups page', js: true, feature: true do let!(:group) { create(:group) } let!(:public_group) { create(:group, :public) } let!(:private_group) { create(:group, :private) } + let!(:empty_project) { create(:empty_project, group: public_group) } before do group.add_owner(user) @@ -43,4 +44,23 @@ describe 'Explore Groups page', js: true, feature: true do expect(page).not_to have_content(private_group.full_name) expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 end + + it 'shows non-archived projects count' do + # Initially project is not archived + expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1") + + # Archive project + empty_project.archive! + visit explore_groups_path + + # Check project count + expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("0") + + # Unarchive project + empty_project.unarchive! + visit explore_groups_path + + # Check project count + expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1") + end end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 0832a3656a8..f1ad4a55246 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -12,6 +12,33 @@ feature 'Create New Merge Request', feature: true, js: true do login_as user end + it 'selects the source branch sha when a tag with the same name exists' do + visit namespace_project_merge_requests_path(project.namespace, project) + + click_link 'New Merge Request' + expect(page).to have_content('Source branch') + expect(page).to have_content('Target branch') + + first('.js-source-branch').click + first('.dropdown-source-branch .dropdown-content a', text: 'v1.1.0').click + + expect(page).to have_content "b83d6e3" + end + + it 'selects the target branch sha when a tag with the same name exists' do + visit namespace_project_merge_requests_path(project.namespace, project) + + click_link 'New Merge Request' + + expect(page).to have_content('Source branch') + expect(page).to have_content('Target branch') + + first('.js-target-branch').click + first('.dropdown-target-branch .dropdown-content a', text: 'v1.1.0').click + + expect(page).to have_content "b83d6e3" + end + it 'generates a diff for an orphaned branch' do visit namespace_project_merge_requests_path(project.namespace, project) @@ -46,6 +73,12 @@ feature 'Create New Merge Request', feature: true, js: true do end end + it 'populates source branch button' do + visit new_namespace_project_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' }) + + expect(find('.js-source-branch')).to have_content('fix') + end + it 'allows to change the diff view' do visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' }) diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb index 0ceaf7bc830..79105b1ee46 100644 --- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -35,6 +35,8 @@ feature 'Merge immediately', :feature, :js do click_link 'Merge Immediately' expect(find('.js-merge-when-pipeline-succeeds-button')).to have_content('Merge in progress') + + wait_for_ajax end end end diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index c2545b0c259..decad589c23 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -1,101 +1,62 @@ require 'spec_helper' -feature 'Member autocomplete', feature: true do - include WaitForAjax - +feature 'Member autocomplete', :js do let(:project) { create(:project, :public) } let(:user) { create(:user) } - let(:participant) { create(:user) } let(:author) { create(:user) } + let(:note) { create(:note, noteable: noteable, project: noteable.project) } before do - allow_any_instance_of(Commit).to receive(:author).and_return(author) - login_as user + note # actually create the note + login_as(user) end - shared_examples "open suggestions" do - it 'displays suggestions' do - expect(page).to have_selector('.atwho-view', visible: true) - end - - it 'suggests author' do - page.within('.atwho-view', visible: true) do - expect(page).to have_content(author.username) + shared_examples "open suggestions when typing @" do + before do + page.within('.new-note') do + find('#note_note').send_keys('@') end end - it 'suggests participant' do + it 'suggests noteable author and note author' do page.within('.atwho-view', visible: true) do - expect(page).to have_content(participant.username) + expect(page).to have_content(author.username) + expect(page).to have_content(note.author.username) end end end - context 'adding a new note on a Issue', js: true do + context 'adding a new note on a Issue' do + let(:noteable) { create(:issue, author: author, project: project) } before do - issue = create(:issue, author: author, project: project) - create(:note, note: 'Ultralight Beam', noteable: issue, - project: project, author: participant) - visit_issue(project, issue) + visit namespace_project_issue_path(project.namespace, project, noteable) end - context 'when typing @' do - include_examples "open suggestions" - before do - open_member_suggestions - end - end + include_examples "open suggestions when typing @" end - context 'adding a new note on a Merge Request ', js: true do + context 'adding a new note on a Merge Request' do + let(:noteable) do + create(:merge_request, source_project: project, + target_project: project, author: author) + end before do - merge = create(:merge_request, source_project: project, target_project: project, author: author) - create(:note, note: 'Ultralight Beam', noteable: merge, - project: project, author: participant) - visit_merge_request(project, merge) + visit namespace_project_merge_request_path(project.namespace, project, noteable) end - context 'when typing @' do - include_examples "open suggestions" - before do - open_member_suggestions - end - end + include_examples "open suggestions when typing @" end - context 'adding a new note on a Commit ', js: true do - let(:commit) { project.commit } + context 'adding a new note on a Commit' do + let(:noteable) { project.commit } + let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) } before do - allow(commit).to receive(:author).and_return(author) - create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA') - visit_commit(project, commit) - end - - context 'when typing @' do - include_examples "open suggestions" - before do - open_member_suggestions - end - end - end + allow_any_instance_of(Commit).to receive(:author).and_return(author) - def open_member_suggestions - page.within('.new-note') do - find('#note_note').send_keys('@') + visit namespace_project_commit_path(project.namespace, project, noteable) end - wait_for_ajax - end - - def visit_issue(project, issue) - visit namespace_project_issue_path(project.namespace, project, issue) - end - - def visit_merge_request(project, merge) - visit namespace_project_merge_request_path(project.namespace, project, merge) - end - def visit_commit(project, commit) - visit namespace_project_commit_path(project.namespace, project, commit) + include_examples "open suggestions when typing @" end end diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb index 03d08c12612..5686868a0c4 100644 --- a/spec/features/projects/blobs/user_create_spec.rb +++ b/spec/features/projects/blobs/user_create_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' feature 'New blob creation', feature: true, js: true do include WaitForAjax + include TargetBranchHelpers given(:user) { create(:user) } given(:role) { :developer } @@ -20,19 +21,6 @@ feature 'New blob creation', feature: true, js: true do execute_script("ace.edit('editor').setValue('#{content}')") end - def select_branch_index(index) - first('button.js-target-branch').click - wait_for_ajax - all('a[data-group="Branches"]')[index].click - end - - def create_new_branch(name) - first('button.js-target-branch').click - click_link 'Create new branch' - fill_in 'new_branch_name', with: name - click_button 'Create' - end - def commit_file click_button 'Commit Changes' end @@ -53,12 +41,12 @@ feature 'New blob creation', feature: true, js: true do context 'with different target branch' do background do edit_file - select_branch_index(0) + select_branch('feature') commit_file end scenario 'creates the blob in the different branch' do - expect(page).to have_content 'test' + expect(page).to have_content 'feature' expect(page).to have_content 'successfully created' end end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 030043d14aa..b2a3b111c9e 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -53,6 +53,7 @@ describe "Compare", js: true do dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click dropdown.fill_in("Filter by Git revision", with: selection) - find_link(selection, visible: true).click + wait_for_ajax + dropdown.find_all("a[data-ref=\"#{selection}\"]", visible: true).last.click end end diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb new file mode 100644 index 00000000000..0b997f130ea --- /dev/null +++ b/spec/features/projects/deploy_keys_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'Project deploy keys', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project_empty_repo) } + + before do + project.team << [user, :master] + login_as(user) + end + + describe 'removing key' do + before do + create(:deploy_keys_project, project: project) + end + + it 'removes association between project and deploy key' do + visit namespace_project_settings_repository_path(project.namespace, project) + + page.within '.deploy-keys' do + expect { click_on 'Remove' } + .to change { project.deploy_keys.count }.by(-1) + end + end + end +end diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb index 8b302a6aa23..4c28205da9b 100644 --- a/spec/features/projects/group_links_spec.rb +++ b/spec/features/projects/group_links_spec.rb @@ -8,7 +8,7 @@ feature 'Project group links', feature: true, js: true do let!(:group) { create(:group) } background do - project.team << [master, :master] + project.add_master(master) login_as(master) end @@ -29,4 +29,26 @@ feature 'Project group links', feature: true, js: true do end end end + + context 'nested group project' do + let!(:nested_group) { create(:group, parent: group) } + let!(:another_group) { create(:group) } + let!(:project) { create(:project, namespace: nested_group) } + + background do + group.add_master(master) + another_group.add_master(master) + end + + it 'does not show ancestors' do + visit namespace_project_settings_members_path(project.namespace, project) + + click_link 'Search for a group' + + page.within '.select2-drop' do + expect(page).to have_content(another_group.name) + expect(page).not_to have_content(group.name) + end + end + end end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index b64c15e0adc..de25d45f447 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -61,7 +61,7 @@ feature 'Projects > Members > User requests access', feature: true do click_link('Settings') end - page.within('.page-with-layout-nav .sub-nav') do + page.within('.sub-nav') do click_link('Members') end end diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb new file mode 100644 index 00000000000..da3eaed707a --- /dev/null +++ b/spec/features/projects/milestones/milestones_sorting_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +feature 'Milestones sorting', :feature, :js do + include SortingHelper + let(:user) { create(:user) } + let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) } + + before do + # Milestones + create(:milestone, + due_date: 10.days.from_now, + created_at: 2.hours.ago, + title: "aaa", project: project) + create(:milestone, + due_date: 11.days.from_now, + created_at: 1.hour.ago, + title: "bbb", project: project) + login_as(user) + end + + scenario 'visit project milestones and sort by due_date_asc' do + visit namespace_project_milestones_path(project.namespace, project) + + expect(page).to have_button('Due soon') + + # assert default sorting + within '.milestones' do + expect(page.all('ul.content-list > li').first.text).to include('aaa') + expect(page.all('ul.content-list > li').last.text).to include('bbb') + end + + click_button 'Due soon' + + sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text) + + expect(sort_options[0]).to eq('Due soon') + expect(sort_options[1]).to eq('Due later') + expect(sort_options[2]).to eq('Start soon') + expect(sort_options[3]).to eq('Start later') + expect(sort_options[4]).to eq('Name, ascending') + expect(sort_options[5]).to eq('Name, descending') + + click_link 'Due later' + + expect(page).to have_button('Due later') + + within '.milestones' do + expect(page.all('ul.content-list > li').first.text).to include('bbb') + expect(page.all('ul.content-list > li').last.text).to include('aaa') + end + end +end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 9f06e52ab55..5a53e48f5f8 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -45,7 +45,7 @@ describe 'Pipeline', :feature, :js do include_context 'pipeline builds' let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) } before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 162056671e0..2272b19bc8f 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -442,7 +442,7 @@ describe 'Pipelines', :feature, :js do context 'when project is public' do let(:project) { create(:project, :public) } - it { expect(page).to have_content 'No pipelines to show' } + it { expect(page).to have_content 'Build with confidence' } it { expect(page).to have_http_status(:success) } end diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb new file mode 100644 index 00000000000..2065abfb248 --- /dev/null +++ b/spec/features/projects/user_create_dir_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +feature 'New directory creation', feature: true, js: true do + include WaitForAjax + include TargetBranchHelpers + + given(:user) { create(:user) } + given(:role) { :developer } + given(:project) { create(:project) } + + background do + login_as(user) + project.team << [user, role] + visit namespace_project_tree_path(project.namespace, project, 'master') + open_new_directory_modal + fill_in 'dir_name', with: 'new_directory' + end + + def open_new_directory_modal + first('.add-to-tree').click + click_link 'New directory' + end + + def create_directory + click_button 'Create directory' + end + + context 'with default target branch' do + background do + create_directory + end + + scenario 'creates the directory in the default branch' do + expect(page).to have_content 'master' + expect(page).to have_content 'The directory has been successfully created' + expect(page).to have_content 'new_directory' + end + end + + context 'with different target branch' do + background do + select_branch('feature') + create_directory + end + + scenario 'creates the directory in the different branch' do + expect(page).to have_content 'feature' + expect(page).to have_content 'The directory has been successfully created' + end + end + + context 'with a new target branch' do + given(:new_branch_name) { 'new-feature' } + + background do + create_new_branch(new_branch_name) + create_directory + end + + scenario 'creates the directory in the new branch' do + expect(page).to have_content new_branch_name + expect(page).to have_content 'The directory has been successfully created' + end + + scenario 'redirects to the merge request' do + expect(page).to have_content 'New Merge Request' + expect(page).to have_content "From #{new_branch_name} into master" + expect(page).to have_content 'Add new directory' + expect(current_path).to eq(new_namespace_project_merge_request_path(project.namespace, project)) + end + end +end diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb new file mode 100644 index 00000000000..6825b95c8aa --- /dev/null +++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'Projects > Wiki > User views Git access wiki page', :feature do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:wiki_page) do + WikiPages::CreateService.new( + project, + user, + title: 'home', + content: '[some link](other-page)' + ).execute + end + + before do + login_as(user) + end + + scenario 'Visit Wiki Page Current Commit' do + visit namespace_project_wiki_path(project.namespace, project, wiki_page) + + click_link 'Clone repository' + expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}") + expect(page).to have_text(project.wiki.http_url_to_repo(user)) + end +end diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb index 336c4092c98..659cd7c7af7 100644 --- a/spec/features/user_callout_spec.rb +++ b/spec/features/user_callout_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'User Callouts', js: true do let(:user) { create(:user) } + let(:another_user) { create(:user) } let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') } before do @@ -32,6 +33,11 @@ describe 'User Callouts', js: true do within('.user-callout') do find('.close-user-callout').click end - expect(page).not_to have_selector('#user-callout') + expect(page).not_to have_selector('.user-callout') + end + + it 'does not show callout on another users profile' do + visit user_path(another_user) + expect(page).not_to have_selector('.user-callout') end end diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb new file mode 100644 index 00000000000..1d75fe434b0 --- /dev/null +++ b/spec/features/users/projects_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe 'Projects tab on a user profile', :feature, :js do + include WaitForAjax + + let(:user) { create(:user) } + let!(:project) { create(:empty_project, namespace: user.namespace) } + let!(:project2) { create(:empty_project, namespace: user.namespace) } + + before do + allow(Project).to receive(:default_per_page).and_return(1) + + login_as(user) + + visit user_path(user) + + page.within('.user-profile-nav') do + click_link('Personal projects') + end + + wait_for_ajax + end + + it 'paginates results' do + expect(page).to have_content(project2.name) + + click_link('Next') + + expect(page).to have_content(project.name) + end +end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb new file mode 100644 index 00000000000..581726c1d0e --- /dev/null +++ b/spec/helpers/avatars_helper_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe AvatarsHelper do + let(:user) { create(:user) } + + describe '#user_avatar' do + subject { helper.user_avatar(user: user) } + + it "links to the user's profile" do + is_expected.to include("href=\"#{user_path(user)}\"") + end + + it "has the user's name as title" do + is_expected.to include("title=\"#{user.name}\"") + end + + it "contains the user's avatar image" do + is_expected.to include(CGI.escapeHTML(user.avatar_url(16))) + end + end +end diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb new file mode 100644 index 00000000000..e5143a0263d --- /dev/null +++ b/spec/helpers/namespaces_helper_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe NamespacesHelper, type: :helper do + let!(:admin) { create(:admin) } + let!(:admin_group) { create(:group, :private) } + let!(:user) { create(:user) } + let!(:user_group) { create(:group, :private) } + + before do + admin_group.add_owner(admin) + user_group.add_owner(user) + end + + describe '#namespaces_options' do + it 'returns groups without being a member for admin' do + allow(helper).to receive(:current_user).and_return(admin) + + options = helper.namespaces_options(user_group.id, display_path: true, extra_group: user_group.id) + + expect(options).to include(admin_group.name) + expect(options).to include(user_group.name) + end + + it 'returns only allowed namespaces for user' do + allow(helper).to receive(:current_user).and_return(user) + + options = helper.namespaces_options + + expect(options).not_to include(admin_group.name) + expect(options).to include(user_group.name) + end + end +end diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index 21e0e74e008..50060a0925d 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -1,40 +1,6 @@ require "spec_helper" describe TodosHelper do - include GitlabRoutingHelper - - describe '#todo_target_path' do - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, target_project: project, source_project: project) } - let(:issue) { create(:issue, project: project) } - let(:note) { create(:note_on_issue, noteable: issue, project: project) } - - let(:mr_todo) { build(:todo, project: project, target: merge_request) } - let(:issue_todo) { build(:todo, project: project, target: issue) } - let(:note_todo) { build(:todo, project: project, target: issue, note: note) } - let(:build_failed_todo) { build(:todo, :build_failed, project: project, target: merge_request) } - - it 'returns correct path to the todo MR' do - expect(todo_target_path(mr_todo)). - to eq("/#{project.full_path}/merge_requests/#{merge_request.iid}") - end - - it 'returns correct path to the todo issue' do - expect(todo_target_path(issue_todo)). - to eq("/#{project.full_path}/issues/#{issue.iid}") - end - - it 'returns correct path to the todo note' do - expect(todo_target_path(note_todo)). - to eq("/#{project.full_path}/issues/#{issue.iid}#note_#{note.id}") - end - - it 'returns correct path to build_todo MR when pipeline failed' do - expect(todo_target_path(build_failed_todo)). - to eq("/#{project.full_path}/merge_requests/#{merge_request.iid}/pipelines") - end - end - describe '#todo_projects_options' do let(:projects) { create_list(:empty_project, 3) } let(:user) { create(:user) } diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb new file mode 100644 index 00000000000..03f78de8e91 --- /dev/null +++ b/spec/helpers/users_helper_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe UsersHelper do + let(:user) { create(:user) } + + describe '#user_link' do + subject { helper.user_link(user) } + + it "links to the user's profile" do + is_expected.to include("href=\"#{user_path(user)}\"") + end + + it "has the user's email as title" do + is_expected.to include("title=\"#{user.email}\"") + end + end +end diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index be31f644e20..73d18458366 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -1,10 +1,11 @@ -/* global Vue */ /* global List */ /* global ListLabel */ /* global listObj */ /* global boardsMockInterceptor */ /* global BoardService */ +import Vue from 'vue'; + require('~/boards/models/list'); require('~/boards/models/label'); require('~/boards/stores/boards_store'); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 1d1069600fc..e21f4ca2bc0 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,12 +1,13 @@ /* eslint-disable comma-dangle, one-var, no-unused-vars */ -/* global Vue */ /* global BoardService */ /* global boardsMockInterceptor */ -/* global Cookies */ /* global listObj */ /* global listObjDuplicate */ /* global ListIssue */ +import Vue from 'vue'; +import Cookies from 'js-cookie'; + require('~/lib/utils/url_utility'); require('~/boards/models/issue'); require('~/boards/models/label'); diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index 4340a571017..1a5e9e9fd07 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -1,9 +1,10 @@ -/* global Vue */ /* global ListUser */ /* global ListLabel */ /* global listObj */ /* global ListIssue */ +import Vue from 'vue'; + require('~/boards/models/issue'); require('~/boards/models/label'); require('~/boards/models/list'); diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index d49d3af33d9..66fc01fa1e5 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle */ -/* global Vue */ /* global boardsMockInterceptor */ /* global BoardService */ /* global List */ @@ -7,6 +6,8 @@ /* global listObj */ /* global listObjDuplicate */ +import Vue from 'vue'; + require('~/lib/utils/url_utility'); require('~/boards/models/issue'); require('~/boards/models/label'); diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js index 1815847f3fa..80db816aff8 100644 --- a/spec/javascripts/boards/modal_store_spec.js +++ b/spec/javascripts/boards/modal_store_spec.js @@ -1,4 +1,3 @@ -/* global Vue */ /* global ListIssue */ require('~/boards/models/issue'); diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index 75efcc06585..bc2e092db65 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -33,7 +33,8 @@ describe('Pipelines table in Commits and Merge requests', () => { }); setTimeout(() => { - expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); + expect(component.$el.querySelector('.empty-state')).toBeDefined(); + expect(component.$el.querySelector('.realtime-loading')).toBe(null); done(); }, 1); }); @@ -63,6 +64,7 @@ describe('Pipelines table in Commits and Merge requests', () => { setTimeout(() => { expect(component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1); + expect(component.$el.querySelector('.realtime-loading')).toBe(null); done(); }, 0); }); @@ -92,7 +94,8 @@ describe('Pipelines table in Commits and Merge requests', () => { }); setTimeout(() => { - expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); + expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined(); + expect(component.$el.querySelector('.realtime-loading')).toBe(null); done(); }, 0); }); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index ae9c263d1d7..848c7656a8d 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -58,7 +58,7 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper }); describe('search', () => { - const defaultParams = '?scope=all&utf8=✓&state=opened'; + const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened'; it('should search with a single word', (done) => { input.value = 'searchTerm'; @@ -246,5 +246,17 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); }); }); + + describe('toggleInputContainerFocus', () => { + it('toggles on focus', () => { + input.focus(); + expect(document.querySelector('.filtered-search-input-container').classList.contains('focus')).toEqual(true); + }); + + it('toggles on blur', () => { + input.blur(); + expect(document.querySelector('.filtered-search-input-container').classList.contains('focus')).toEqual(false); + }); + }); }); })(); diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml new file mode 100644 index 00000000000..418a38a0e2e --- /dev/null +++ b/spec/javascripts/fixtures/pipelines.html.haml @@ -0,0 +1,14 @@ +%div + #pipelines-list-vue{ data: { endpoint: 'foo', + "css-class" => 'foo', + "help-page-path" => 'foo', + "new-pipeline-path" => 'foo', + "can-create-pipeline" => 'true', + "all-path" => 'foo', + "pending-path" => 'foo', + "running-path" => 'foo', + "finished-path" => 'foo', + "branches-path" => 'foo', + "tags-path" => 'foo', + "has-ci" => 'foo', + "ci-lint-path" => 'foo' } } diff --git a/spec/javascripts/fixtures/pipelines_table.html.haml b/spec/javascripts/fixtures/pipelines_table.html.haml index fbe4a434f76..ad1682704bb 100644 --- a/spec/javascripts/fixtures/pipelines_table.html.haml +++ b/spec/javascripts/fixtures/pipelines_table.html.haml @@ -1,2 +1 @@ -#commit-pipeline-table-view{ data: { endpoint: "endpoint" } } -.pipeline-svgs{ data: { "commit_icon_svg": "svg"} } +#commit-pipeline-table-view{ data: { endpoint: "endpoint", "help-page-path": "foo" } } diff --git a/spec/javascripts/issuable_time_tracker_spec.js b/spec/javascripts/issuable_time_tracker_spec.js index cb068a4f879..0a830f25e29 100644 --- a/spec/javascripts/issuable_time_tracker_spec.js +++ b/spec/javascripts/issuable_time_tracker_spec.js @@ -1,7 +1,7 @@ -/* eslint-disable */ +/* eslint-disable no-unused-vars, space-before-function-paren, func-call-spacing, no-spaced-func, semi, max-len, quotes, space-infix-ops, padded-blocks */ + +import Vue from 'vue'; -require('jquery'); -require('vue'); require('~/issuable/time_tracking/components/time_tracker'); function initTimeTrackingComponent(opts) { diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index f4d3e77e515..d2e24eb7eb2 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -163,5 +163,72 @@ require('~/lib/utils/common_utils'); expect(gl.utils.isMetaClick(e)).toBe(true); }); }); + + describe('gl.utils.backOff', () => { + it('solves the promise from the callback', (done) => { + const expectedResponseValue = 'Success!'; + gl.utils.backOff((next, stop) => ( + new Promise((resolve) => { + resolve(expectedResponseValue); + }).then((resp) => { + stop(resp); + }) + )).then((respBackoff) => { + expect(respBackoff).toBe(expectedResponseValue); + done(); + }); + }); + + it('catches the rejected promise from the callback ', (done) => { + const errorMessage = 'Mistakes were made!'; + gl.utils.backOff((next, stop) => { + new Promise((resolve, reject) => { + reject(new Error(errorMessage)); + }).then((resp) => { + stop(resp); + }).catch(err => stop(err)); + }).catch((errBackoffResp) => { + expect(errBackoffResp instanceof Error).toBe(true); + expect(errBackoffResp.message).toBe(errorMessage); + done(); + }); + }); + + it('solves the promise correctly after retrying a third time', (done) => { + let numberOfCalls = 1; + const expectedResponseValue = 'Success!'; + gl.utils.backOff((next, stop) => ( + new Promise((resolve) => { + resolve(expectedResponseValue); + }).then((resp) => { + if (numberOfCalls < 3) { + numberOfCalls += 1; + next(); + } else { + stop(resp); + } + }) + )).then((respBackoff) => { + expect(respBackoff).toBe(expectedResponseValue); + expect(numberOfCalls).toBe(3); + done(); + }); + }, 10000); + + it('rejects the backOff promise after timing out', (done) => { + const expectedResponseValue = 'Success!'; + gl.utils.backOff(next => ( + new Promise((resolve) => { + resolve(expectedResponseValue); + }).then(() => { + setTimeout(next(), 5000); // it will time out + }) + ), 3000).catch((errBackoffResp) => { + expect(errBackoffResp instanceof Error).toBe(true); + expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT'); + done(); + }); + }, 10000); + }); }); })(); diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js new file mode 100644 index 00000000000..c794a632417 --- /dev/null +++ b/spec/javascripts/lib/utils/poll_spec.js @@ -0,0 +1,163 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import Poll from '~/lib/utils/poll'; + +Vue.use(VueResource); + +class ServiceMock { + constructor(endpoint) { + this.service = Vue.resource(endpoint); + } + + fetch() { + return this.service.get(); + } +} + +describe('Poll', () => { + let callbacks; + + beforeEach(() => { + callbacks = { + success: () => {}, + error: () => {}, + }; + + spyOn(callbacks, 'success'); + spyOn(callbacks, 'error'); + }); + + it('calls the success callback when no header for interval is provided', (done) => { + const successInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { status: 200 })); + }; + + Vue.http.interceptors.push(successInterceptor); + + new Poll({ + resource: new ServiceMock('endpoint'), + method: 'fetch', + successCallback: callbacks.success, + errorCallback: callbacks.error, + }).makeRequest(); + + setTimeout(() => { + expect(callbacks.success).toHaveBeenCalled(); + expect(callbacks.error).not.toHaveBeenCalled(); + done(); + }, 0); + + Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor); + }); + + it('calls the error callback whe the http request returns an error', (done) => { + const errorInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { status: 500 })); + }; + + Vue.http.interceptors.push(errorInterceptor); + + new Poll({ + resource: new ServiceMock('endpoint'), + method: 'fetch', + successCallback: callbacks.success, + errorCallback: callbacks.error, + }).makeRequest(); + + setTimeout(() => { + expect(callbacks.success).not.toHaveBeenCalled(); + expect(callbacks.error).toHaveBeenCalled(); + done(); + }, 0); + + Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor); + }); + + it('should call the success callback when the interval header is -1', (done) => { + const intervalInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': -1 } })); + }; + + Vue.http.interceptors.push(intervalInterceptor); + + new Poll({ + resource: new ServiceMock('endpoint'), + method: 'fetch', + successCallback: callbacks.success, + errorCallback: callbacks.error, + }).makeRequest(); + + setTimeout(() => { + expect(callbacks.success).toHaveBeenCalled(); + expect(callbacks.error).not.toHaveBeenCalled(); + done(); + }, 0); + + Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor); + }); + + it('starts polling when http status is 200 and interval header is provided', (done) => { + const pollInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } })); + }; + + Vue.http.interceptors.push(pollInterceptor); + + const service = new ServiceMock('endpoint'); + spyOn(service, 'fetch').and.callThrough(); + + new Poll({ + resource: service, + method: 'fetch', + data: { page: 1 }, + successCallback: callbacks.success, + errorCallback: callbacks.error, + }).makeRequest(); + + setTimeout(() => { + expect(service.fetch.calls.count()).toEqual(2); + expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); + expect(callbacks.success).toHaveBeenCalled(); + expect(callbacks.error).not.toHaveBeenCalled(); + done(); + }, 5); + + Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + }); + + describe('stop', () => { + it('stops polling when method is called', (done) => { + const pollInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { status: 200, headers: { 'poll-interval': 2 } })); + }; + + Vue.http.interceptors.push(pollInterceptor); + + const service = new ServiceMock('endpoint'); + spyOn(service, 'fetch').and.callThrough(); + + const Polling = new Poll({ + resource: service, + method: 'fetch', + data: { page: 1 }, + successCallback: () => { + Polling.stop(); + }, + errorCallback: callbacks.error, + }); + + spyOn(Polling, 'stop').and.callThrough(); + + Polling.makeRequest(); + + setTimeout(() => { + expect(service.fetch.calls.count()).toEqual(1); + expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); + expect(Polling.stop).toHaveBeenCalled(); + done(); + }, 100); + + Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + }); + }); +}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 5cdb6473eda..d658f680f97 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -8,9 +8,6 @@ jasmine.getJSONFixtures().fixturesPath = 'base/spec/javascripts/fixtures'; require('~/commons/index.js'); window.$ = window.jQuery = require('jquery'); window._ = require('underscore'); -window.Cookies = require('js-cookie'); -window.Vue = require('vue'); -window.Vue.use(require('vue-resource')); // stub expected globals window.gl = window.gl || {}; @@ -38,7 +35,8 @@ testsContext.keys().forEach(function (path) { if (process.env.BABEL_ENV === 'coverage') { // exempt these files from the coverage report const troubleMakers = [ - './blob_edit/blob_edit_bundle.js', + './blob_edit/blob_bundle.js', + './boards/boards_bundle.js', './cycle_analytics/components/stage_plan_component.js', './cycle_analytics/components/stage_staging_component.js', './cycle_analytics/components/stage_test_component.js', diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js index 205e72af600..2398149d3ad 100644 --- a/spec/javascripts/user_callout_spec.js +++ b/spec/javascripts/user_callout_spec.js @@ -1,7 +1,7 @@ -const UserCallout = require('~/user_callout'); +import Cookies from 'js-cookie'; +import UserCallout from '~/user_callout'; const USER_CALLOUT_COOKIE = 'user_callout_dismissed'; -const Cookie = window.Cookies; describe('UserCallout', function () { const fixtureName = 'static/user_callout.html.raw'; @@ -9,7 +9,7 @@ describe('UserCallout', function () { beforeEach(() => { loadFixtures(fixtureName); - Cookie.remove(USER_CALLOUT_COOKIE); + Cookies.remove(USER_CALLOUT_COOKIE); this.userCallout = new UserCallout(); this.closeButton = $('.close-user-callout'); @@ -18,25 +18,25 @@ describe('UserCallout', function () { }); it('does not show when cookie is set not defined', () => { - expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeUndefined(); + expect(Cookies.get(USER_CALLOUT_COOKIE)).toBeUndefined(); expect(this.userCalloutContainer.is(':visible')).toBe(true); }); it('shows when cookie is set to false', () => { - Cookie.set(USER_CALLOUT_COOKIE, 'false'); + Cookies.set(USER_CALLOUT_COOKIE, 'false'); - expect(Cookie.get(USER_CALLOUT_COOKIE)).toBeDefined(); + expect(Cookies.get(USER_CALLOUT_COOKIE)).toBeDefined(); expect(this.userCalloutContainer.is(':visible')).toBe(true); }); it('hides when user clicks on the dismiss-icon', () => { this.closeButton.click(); - expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true'); + expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true'); }); it('hides when user clicks on the "check it out" button', () => { this.userCalloutBtn.click(); - expect(Cookie.get(USER_CALLOUT_COOKIE)).toBe('true'); + expect(Cookies.get(USER_CALLOUT_COOKIE)).toBe('true'); }); }); @@ -46,7 +46,7 @@ describe('UserCallout when cookie is present', function () { beforeEach(() => { loadFixtures(fixtureName); - Cookie.set(USER_CALLOUT_COOKIE, 'true'); + Cookies.set(USER_CALLOUT_COOKIE, 'true'); this.userCallout = new UserCallout(); this.userCalloutContainer = $('.user-callout'); }); diff --git a/spec/javascripts/vue_pipelines_index/empty_state_spec.js b/spec/javascripts/vue_pipelines_index/empty_state_spec.js new file mode 100644 index 00000000000..733337168dc --- /dev/null +++ b/spec/javascripts/vue_pipelines_index/empty_state_spec.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import emptyStateComp from '~/vue_pipelines_index/components/empty_state'; + +describe('Pipelines Empty State', () => { + let component; + let EmptyStateComponent; + + beforeEach(() => { + EmptyStateComponent = Vue.extend(emptyStateComp); + + component = new EmptyStateComponent({ + propsData: { + helpPagePath: 'foo', + }, + }).$mount(); + }); + + it('should render empty state SVG', () => { + expect(component.$el.querySelector('.svg-content svg')).toBeDefined(); + }); + + it('should render emtpy state information', () => { + expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence'); + + expect( + component.$el.querySelector('p').textContent, + ).toContain('Continous Integration can help catch bugs by running your tests automatically'); + + expect( + component.$el.querySelector('p').textContent, + ).toContain('Continuous Deployment can help you deliver code to your product environment'); + }); + + it('should render a link with provided help path', () => { + expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual('foo'); + expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines'); + }); +}); diff --git a/spec/javascripts/vue_pipelines_index/error_state_spec.js b/spec/javascripts/vue_pipelines_index/error_state_spec.js new file mode 100644 index 00000000000..524e018b1fa --- /dev/null +++ b/spec/javascripts/vue_pipelines_index/error_state_spec.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import errorStateComp from '~/vue_pipelines_index/components/error_state'; + +describe('Pipelines Error State', () => { + let component; + let ErrorStateComponent; + + beforeEach(() => { + ErrorStateComponent = Vue.extend(errorStateComp); + + component = new ErrorStateComponent().$mount(); + }); + + it('should render error state SVG', () => { + expect(component.$el.querySelector('.svg-content svg')).toBeDefined(); + }); + + it('should render emtpy state information', () => { + expect( + component.$el.querySelector('h4').textContent, + ).toContain('The API failed to fetch the pipelines'); + }); +}); diff --git a/spec/javascripts/vue_pipelines_index/mock_data.js b/spec/javascripts/vue_pipelines_index/mock_data.js new file mode 100644 index 00000000000..2365a662b9f --- /dev/null +++ b/spec/javascripts/vue_pipelines_index/mock_data.js @@ -0,0 +1,107 @@ +export default { + pipelines: [{ + id: 115, + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + path: '/root/review-app/pipelines/115', + details: { + status: { + icon: 'icon_status_failed', + text: 'failed', + label: 'failed', + group: 'failed', + has_details: true, + details_path: '/root/review-app/pipelines/115', + }, + duration: null, + finished_at: '2017-03-17T19:00:15.996Z', + stages: [{ + name: 'build', + title: 'build: failed', + status: { + icon: 'icon_status_failed', + text: 'failed', + label: 'failed', + group: 'failed', + has_details: true, + details_path: '/root/review-app/pipelines/115#build', + }, + path: '/root/review-app/pipelines/115#build', + dropdown_path: '/root/review-app/pipelines/115/stage.json?stage=build', + }, + { + name: 'review', + title: 'review: skipped', + status: { + icon: 'icon_status_skipped', + text: 'skipped', + label: 'skipped', + group: 'skipped', + has_details: true, + details_path: '/root/review-app/pipelines/115#review', + }, + path: '/root/review-app/pipelines/115#review', + dropdown_path: '/root/review-app/pipelines/115/stage.json?stage=review', + }], + artifacts: [], + manual_actions: [{ + name: 'stop_review', + path: '/root/review-app/builds/3766/play', + }], + }, + flags: { + latest: true, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: false, + }, + ref: { + name: 'thisisabranch', + path: '/root/review-app/tree/thisisabranch', + tag: false, + branch: true, + }, + commit: { + id: '9e87f87625b26c42c59a2ee0398f81d20cdfe600', + short_id: '9e87f876', + title: 'Update README.md', + created_at: '2017-03-15T22:58:28.000+00:00', + parent_ids: ['3744f9226e699faec2662a8b267e5d3fd0bfff0e'], + message: 'Update README.md', + author_name: 'Root', + author_email: 'admin@example.com', + authored_date: '2017-03-15T22:58:28.000+00:00', + committer_name: 'Root', + committer_email: 'admin@example.com', + committed_date: '2017-03-15T22:58:28.000+00:00', + author: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + commit_url: 'http://localhost:3000/root/review-app/commit/9e87f87625b26c42c59a2ee0398f81d20cdfe600', + commit_path: '/root/review-app/commit/9e87f87625b26c42c59a2ee0398f81d20cdfe600', + }, + retry_path: '/root/review-app/pipelines/115/retry', + created_at: '2017-03-15T22:58:33.436Z', + updated_at: '2017-03-17T19:00:15.997Z', + }], + count: { + all: 52, + running: 0, + pending: 0, + finished: 52, + }, +}; diff --git a/spec/javascripts/vue_pipelines_index/nav_controls_spec.js b/spec/javascripts/vue_pipelines_index/nav_controls_spec.js new file mode 100644 index 00000000000..659c4854a56 --- /dev/null +++ b/spec/javascripts/vue_pipelines_index/nav_controls_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import navControlsComp from '~/vue_pipelines_index/components/nav_controls'; + +describe('Pipelines Nav Controls', () => { + let NavControlsComponent; + + beforeEach(() => { + NavControlsComponent = Vue.extend(navControlsComp); + }); + + it('should render link to create a new pipeline', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: true, + helpPagePath: 'foo', + ciLintPath: 'foo', + canCreatePipeline: true, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelector('.btn-create').textContent).toContain('Run Pipeline'); + expect(component.$el.querySelector('.btn-create').getAttribute('href')).toEqual(mockData.newPipelinePath); + }); + + it('should not render link to create pipeline if no permission is provided', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: true, + helpPagePath: 'foo', + ciLintPath: 'foo', + canCreatePipeline: false, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelector('.btn-create')).toEqual(null); + }); + + it('should render link for CI lint', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: true, + helpPagePath: 'foo', + ciLintPath: 'foo', + canCreatePipeline: true, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint'); + expect(component.$el.querySelector('.btn-default').getAttribute('href')).toEqual(mockData.ciLintPath); + }); + + it('should render link to help page when CI is not enabled', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: false, + helpPagePath: 'foo', + ciLintPath: 'foo', + canCreatePipeline: true, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines'); + expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual(mockData.helpPagePath); + }); + + it('should not render link to help page when CI is enabled', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: true, + helpPagePath: 'foo', + ciLintPath: 'foo', + canCreatePipeline: true, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelector('.btn-info')).toEqual(null); + }); +}); diff --git a/spec/javascripts/vue_pipelines_index/pipelines_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_spec.js new file mode 100644 index 00000000000..725f6cb2d7a --- /dev/null +++ b/spec/javascripts/vue_pipelines_index/pipelines_spec.js @@ -0,0 +1,114 @@ +import Vue from 'vue'; +import pipelinesComp from '~/vue_pipelines_index/pipelines'; +import Store from '~/vue_pipelines_index/stores/pipelines_store'; +import pipelinesData from './mock_data'; + +describe('Pipelines', () => { + preloadFixtures('static/pipelines.html.raw'); + + let PipelinesComponent; + + beforeEach(() => { + loadFixtures('static/pipelines.html.raw'); + + PipelinesComponent = Vue.extend(pipelinesComp); + }); + + describe('successfull request', () => { + describe('with pipelines', () => { + const pipelinesInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify(pipelinesData), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesInterceptor, + ); + }); + + it('should render table', (done) => { + const component = new PipelinesComponent({ + propsData: { + store: new Store(), + }, + }).$mount(); + + setTimeout(() => { + expect(component.$el.querySelector('.table-holder')).toBeDefined(); + expect(component.$el.querySelector('.realtime-loading')).toBe(null); + done(); + }); + }); + }); + + describe('without pipelines', () => { + const emptyInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(emptyInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, emptyInterceptor, + ); + }); + + it('should render empty state', (done) => { + const component = new PipelinesComponent({ + propsData: { + store: new Store(), + }, + }).$mount(); + + setTimeout(() => { + expect(component.$el.querySelector('.empty-state')).toBeDefined(); + expect(component.$el.querySelector('.realtime-loading')).toBe(null); + done(); + }); + }); + }); + }); + + describe('unsuccessfull request', () => { + const errorInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 500, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(errorInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, errorInterceptor, + ); + }); + + it('should render error state', (done) => { + const component = new PipelinesComponent({ + propsData: { + store: new Store(), + }, + }).$mount(); + + setTimeout(() => { + expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined(); + expect(component.$el.querySelector('.realtime-loading')).toBe(null); + done(); + }); + }); + }); +}); diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb index 2a314a744ca..49457b129e3 100644 --- a/spec/lib/gitlab/ci/build/step_spec.rb +++ b/spec/lib/gitlab/ci/build/step_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::Ci::Build::Step do end context 'when after_script is not empty' do - let(:job) { create(:ci_build, options: { after_script: "ls -la\ndate" }) } + let(:job) { create(:ci_build, options: { after_script: ['ls -la', 'date'] }) } it 'fabricates an object' do expect(subject.name).to eq(:after_script) diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb new file mode 100644 index 00000000000..a504d299307 --- /dev/null +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::UrlBlocker, lib: true do + describe '#blocked_url?' do + it 'allows imports from configured web host and port' do + import_url = "http://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git" + expect(described_class.blocked_url?(import_url)).to be false + end + + it 'allows imports from configured SSH host and port' do + import_url = "http://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git" + expect(described_class.blocked_url?(import_url)).to be false + end + + it 'returns true for bad localhost hostname' do + expect(described_class.blocked_url?('https://localhost:65535/foo/foo.git')).to be true + end + + it 'returns true for bad port' do + expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true + end + + it 'returns true for invalid URL' do + expect(described_class.blocked_url?('http://:8080')).to be true + end + + it 'returns false for legitimate URL' do + expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false + end + end +end diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 3fd361de458..fc144a2556a 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -5,6 +5,7 @@ describe Gitlab::UrlSanitizer, lib: true do let(:url_sanitizer) do described_class.new("https://github.com/me/project.git", credentials: credentials) end + let(:user) { double(:user, username: 'john.doe') } describe '.sanitize' do def sanitize_url(url) @@ -53,12 +54,33 @@ describe Gitlab::UrlSanitizer, lib: true do end end + describe '.valid?' do + it 'validates url strings' do + expect(described_class.valid?(nil)).to be(false) + expect(described_class.valid?('valid@project:url.git')).to be(true) + expect(described_class.valid?('123://invalid:url')).to be(false) + end + end + + describe '.http_credentials_for_user' do + it { expect(described_class.http_credentials_for_user(user)).to eq({ user: 'john.doe' }) } + it { expect(described_class.http_credentials_for_user('foo')).to eq({}) } + end + describe '#sanitized_url' do it { expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") } end describe '#credentials' do it { expect(url_sanitizer.credentials).to eq(credentials) } + + context 'when user is given to #initialize' do + let(:url_sanitizer) do + described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user)) + end + + it { expect(url_sanitizer.credentials).to eq({ user: 'john.doe' }) } + end end describe '#full_url' do @@ -69,13 +91,13 @@ describe Gitlab::UrlSanitizer, lib: true do expect(sanitizer.full_url).to eq('user@server:project.git') end - end - describe '.valid?' do - it 'validates url strings' do - expect(described_class.valid?(nil)).to be(false) - expect(described_class.valid?('valid@project:url.git')).to be(true) - expect(described_class.valid?('123://invalid:url')).to be(false) + context 'when user is given to #initialize' do + let(:url_sanitizer) do + described_class.new("https://github.com/me/project.git", credentials: described_class.http_credentials_for_user(user)) + end + + it { expect(url_sanitizer.full_url).to eq("https://john.doe@github.com/me/project.git") } end end end diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb new file mode 100644 index 00000000000..3255c6f1ef7 --- /dev/null +++ b/spec/lib/gitlab/visibility_level_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::VisibilityLevel, lib: true do + describe '.level_value' do + it 'converts "public" to integer value' do + expect(described_class.level_value('public')).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + + it 'converts string integer to integer value' do + expect(described_class.level_value('20')).to eq(20) + end + + it 'defaults to PRIVATE when string value is not valid' do + expect(described_class.level_value('invalid')).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + it 'defaults to PRIVATE when integer value is not valid' do + expect(described_class.level_value(100)).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end +end diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index e1877d5fde0..5ca936f28f0 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -5,6 +5,16 @@ describe Notify do include EmailSpec::Matchers include_context 'gitlab email notification' + shared_examples 'a new user email' do + it 'is sent to the new user with the correct subject and body' do + aggregate_failures do + is_expected.to deliver_to new_user_address + is_expected.to have_subject(/^Account was created for you$/i) + is_expected.to have_body_text(new_user_address) + end + end + end + describe 'profile notifications' do describe 'for new users, the email' do let(:example_site_path) { root_path } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 6ee91576676..4b72eb2eaa3 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -24,14 +24,14 @@ describe Notify do let(:previous_assignee) { create(:user, name: 'Previous Assignee') } shared_examples 'an assignee email' do - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + it 'is sent to the assignee as the author' do + sender = subject.header[:from].addrs.first - it 'is sent to the assignee' do - is_expected.to deliver_to assignee.email + aggregate_failures do + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + expect(subject).to deliver_to(assignee.email) + end end end @@ -49,12 +49,11 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' - it 'has the correct subject' do - is_expected.to have_referable_subject(issue) - end - - it 'contains a link to the new issue' do - is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue) + is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + end end context 'when enabled email_author_in_body' do @@ -63,7 +62,7 @@ describe Notify do end it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text issue.author_name + is_expected.to have_html_escaped_body_text(issue.author_name) is_expected.to have_body_text 'wrote:' end end @@ -95,20 +94,13 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_referable_subject(issue, reply: true) - end - - it 'contains the name of the previous assignee' do - is_expected.to have_html_escaped_body_text previous_assignee.name - end - - it 'contains the name of the new assignee' do - is_expected.to have_html_escaped_body_text assignee.name - end - - it 'contains a link to the issue' do - is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_html_escaped_body_text(assignee.name) + is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + end end end @@ -129,16 +121,12 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_referable_subject(issue, reply: true) - end - - it 'contains the names of the added labels' do - is_expected.to have_body_text 'foo, bar, and baz' - end - - it 'contains a link to the issue' do - is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + end end end @@ -158,20 +146,13 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_referable_subject(issue, reply: true) - end - - it 'contains the new status' do - is_expected.to have_body_text status - end - - it 'contains the user name' do - is_expected.to have_html_escaped_body_text current_user.name - end - - it 'contains a link to the issue' do - is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(status) + is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue) + end end end @@ -189,18 +170,15 @@ describe Notify do is_expected.to have_body_text 'Issue was moved to another project' end - it 'has the correct subject' do - is_expected.to have_referable_subject(issue, reply: true) - end - - it 'contains link to new issue' do + it 'has the correct subject and body' do new_issue_url = namespace_project_issue_path(new_issue.project.namespace, new_issue.project, new_issue) - is_expected.to have_body_text new_issue_url - end - it 'contains a link to the original issue' do - is_expected.to have_body_text namespace_project_issue_path(project.namespace, project, issue) + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(new_issue_url) + is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue)) + end end end end @@ -220,20 +198,13 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' - it 'has the correct subject' do - is_expected.to have_referable_subject(merge_request) - end - - it 'contains a link to the new merge request' do - is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request) - end - - it 'contains the source branch for the merge request' do - is_expected.to have_body_text merge_request.source_branch - end - - it 'contains the target branch for the merge request' do - is_expected.to have_body_text merge_request.target_branch + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request) + is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_body_text(merge_request.source_branch) + is_expected.to have_body_text(merge_request.target_branch) + end end context 'when enabled email_author_in_body' do @@ -275,20 +246,13 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_referable_subject(merge_request, reply: true) - end - - it 'contains the name of the previous assignee' do - is_expected.to have_html_escaped_body_text previous_assignee.name - end - - it 'contains the name of the new assignee' do - is_expected.to have_html_escaped_body_text assignee.name - end - - it 'contains a link to the merge request' do - is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + is_expected.to have_html_escaped_body_text(assignee.name) + end end end @@ -309,16 +273,10 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do + it 'has the correct subject and body' do is_expected.to have_referable_subject(merge_request, reply: true) - end - - it 'contains the names of the added labels' do - is_expected.to have_body_text 'foo, bar, and baz' - end - - it 'contains a link to the merge request' do - is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request) + is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) end end @@ -338,20 +296,13 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_referable_subject(merge_request, reply: true) - end - - it 'contains the new status' do - is_expected.to have_body_text status - end - - it 'contains the user name' do - is_expected.to have_html_escaped_body_text current_user.name - end - - it 'contains a link to the merge request' do - is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text(status) + is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + end end end @@ -371,16 +322,12 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_referable_subject(merge_request, reply: true) - end - - it 'contains the new status' do - is_expected.to have_body_text 'merged' - end - - it 'contains a link to the merge request' do - is_expected.to have_body_text namespace_project_merge_request_path(project.namespace, project, merge_request) + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text('merged') + is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request)) + end end end end @@ -395,16 +342,10 @@ describe Notify do it_behaves_like 'it should not have Gmail Actions links' it_behaves_like "a user cannot unsubscribe through footer link" - it 'has the correct subject' do - is_expected.to have_subject "#{project.name} | Project was moved" - end - - it 'contains name of project' do + it 'has the correct subject and body' do + is_expected.to have_subject("#{project.name} | Project was moved") is_expected.to have_html_escaped_body_text project.name_with_namespace - end - - it 'contains new user role' do - is_expected.to have_body_text project.ssh_url_to_repo + is_expected.to have_body_text(project.ssh_url_to_repo) end end @@ -597,14 +538,14 @@ describe Notify do shared_examples 'a note email' do it_behaves_like 'it should have Gmail Actions links' - it 'is sent as the author' do + it 'is sent to the given recipient as the author' do sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(note_author.name) - expect(sender.address).to eq(gitlab_sender) - end - it 'is sent to the given recipient' do - is_expected.to deliver_to recipient.notification_email + aggregate_failures do + expect(sender.display_name).to eq(note_author.name) + expect(sender.address).to eq(gitlab_sender) + expect(subject).to deliver_to(recipient.notification_email) + end end it 'contains the message from the note' do @@ -641,12 +582,11 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'a user cannot unsubscribe through footer link' - it 'has the correct subject' do - is_expected.to have_subject "Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})" - end - - it 'contains a link to the commit' do - is_expected.to have_body_text commit.short_id + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_subject("Re: #{project.name} | #{commit.title.strip} (#{commit.short_id})") + is_expected.to have_body_text(commit.short_id) + end end end @@ -664,12 +604,11 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Merge request link' it_behaves_like 'an unsubscribeable thread' - it 'has the correct subject' do - is_expected.to have_referable_subject(merge_request, reply: true) - end - - it 'contains a link to the merge request note' do - is_expected.to have_body_text note_on_merge_request_path + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text note_on_merge_request_path + end end end @@ -687,12 +626,11 @@ describe Notify do it_behaves_like 'it should show Gmail Actions View Issue link' it_behaves_like 'an unsubscribeable thread' - it 'has the correct subject' do - is_expected.to have_referable_subject(issue, reply: true) - end - - it 'contains a link to the issue note' do - is_expected.to have_body_text note_on_issue_path + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(note_on_issue_path) + end end end end @@ -717,14 +655,14 @@ describe Notify do it_behaves_like 'it should have Gmail Actions links' - it 'is sent as the author' do + it 'is sent to the given recipient as the author' do sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(note_author.name) - expect(sender.address).to eq(gitlab_sender) - end - it 'is sent to the given recipient' do - is_expected.to deliver_to recipient.notification_email + aggregate_failures do + expect(sender.display_name).to eq(note_author.name) + expect(sender.address).to eq(gitlab_sender) + expect(subject).to deliver_to(recipient.notification_email) + end end it 'contains the message from the note' do @@ -934,21 +872,20 @@ describe Notify do is_expected.to deliver_to 'new-email@mail.com' end - it 'has the correct subject' do - is_expected.to have_subject 'Confirmation instructions | A Nice Suffix' - end - - it 'includes a link to the site' do - is_expected.to have_body_text example_site_path + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_subject('Confirmation instructions | A Nice Suffix') + is_expected.to have_body_text(example_site_path) + end end end describe 'email on push for a created branch' do let(:example_site_path) { root_path } let(:user) { create(:user) } - let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") } + let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") } - subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :create) } + subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) } it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'a user cannot unsubscribe through footer link' @@ -961,12 +898,11 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_subject "[Git][#{project.full_path}] Pushed new branch master" - end - - it 'contains a link to the branch' do - is_expected.to have_body_text tree_path + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_subject("[Git][#{project.full_path}] Pushed new branch empty-branch") + is_expected.to have_body_text(tree_path) + end end end @@ -988,12 +924,11 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_subject "[Git][#{project.full_path}] Pushed new tag v1.0" - end - - it 'contains a link to the tag' do - is_expected.to have_body_text tree_path + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_subject("[Git][#{project.full_path}] Pushed new tag v1.0") + is_expected.to have_body_text(tree_path) + end end end @@ -1064,24 +999,14 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_subject "[Git][#{project.full_path}][master] #{commits.length} commits: Ruby files modified" - end - - it 'includes commits list' do - is_expected.to have_body_text 'Change some files' - end - - it 'includes diffs with character-level highlighting' do - is_expected.to have_body_text 'def</span> <span class="nf">archive_formats_regex' - end - - it 'contains a link to the diff' do - is_expected.to have_body_text diff_path - end - - it 'does not contain the misleading footer' do - is_expected.not_to have_body_text 'you are a member of' + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_subject("[Git][#{project.full_path}][master] #{commits.length} commits: Ruby files modified") + is_expected.to have_body_text('Change some files') + is_expected.to have_body_text('def</span> <span class="nf">archive_formats_regex') + is_expected.to have_body_text(diff_path) + is_expected.not_to have_body_text('you are a member of') + end end context "when set to send from committer email if domain matches" do @@ -1098,13 +1023,13 @@ describe Notify do end it "is sent from the committer email" do - sender = subject.header[:from].addrs[0] - expect(sender.address).to eq(user.email) - end + from = subject.header[:from].addrs.first + reply = subject.header[:reply_to].addrs.first - it "is set to reply to the committer email" do - sender = subject.header[:reply_to].addrs[0] - expect(sender.address).to eq(user.email) + aggregate_failures do + expect(from.address).to eq(user.email) + expect(reply.address).to eq(user.email) + end end end @@ -1115,13 +1040,13 @@ describe Notify do end it "is sent from the default email" do - sender = subject.header[:from].addrs[0] - expect(sender.address).to eq(gitlab_sender) - end + from = subject.header[:from].addrs.first + reply = subject.header[:reply_to].addrs.first - it "is set to reply to the default email" do - sender = subject.header[:reply_to].addrs[0] - expect(sender.address).to eq(gitlab_sender_reply_to) + aggregate_failures do + expect(from.address).to eq(gitlab_sender) + expect(reply.address).to eq(gitlab_sender_reply_to) + end end end @@ -1132,13 +1057,13 @@ describe Notify do end it "is sent from the default email" do - sender = subject.header[:from].addrs[0] - expect(sender.address).to eq(gitlab_sender) - end + from = subject.header[:from].addrs.first + reply = subject.header[:reply_to].addrs.first - it "is set to reply to the default email" do - sender = subject.header[:reply_to].addrs[0] - expect(sender.address).to eq(gitlab_sender_reply_to) + aggregate_failures do + expect(from.address).to eq(gitlab_sender) + expect(reply.address).to eq(gitlab_sender_reply_to) + end end end end @@ -1166,20 +1091,13 @@ describe Notify do expect(sender.address).to eq(gitlab_sender) end - it 'has the correct subject' do - is_expected.to have_subject "[Git][#{project.full_path}][master] #{commits.first.title}" - end - - it 'includes commits list' do - is_expected.to have_body_text 'Change some files' - end - - it 'includes diffs with character-level highlighting' do - is_expected.to have_body_text 'def</span> <span class="nf">archive_formats_regex' - end - - it 'contains a link to the diff' do - is_expected.to have_body_text diff_path + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_subject("[Git][#{project.full_path}][master] #{commits.first.title}") + is_expected.to have_body_text('Change some files') + is_expected.to have_body_text('def</span> <span class="nf">archive_formats_regex') + is_expected.to have_body_text(diff_path) + end end end diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index f134da441c2..82abad0e2f6 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -110,6 +110,24 @@ describe HasStatus do it { is_expected.to eq 'running' } end + context 'when one status finished and second is still created' do + let!(:statuses) do + [create(type, status: :success), create(type, status: :created)] + end + + it { is_expected.to eq 'running' } + end + + context 'when there is a manual status before created status' do + let!(:statuses) do + [create(type, status: :success), + create(type, status: :manual, allow_failure: false), + create(type, status: :created)] + end + + it { is_expected.to eq 'manual' } + end + context 'when one status is a blocking manual action' do let!(:statuses) do [create(type, status: :failed), diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 9574796a945..4522206fab1 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -44,6 +44,34 @@ describe Issue, "Issuable" do it { expect(described_class).to respond_to(:assigned) } end + describe 'author_name' do + it 'is delegated to author' do + expect(issue.author_name).to eq issue.author.name + end + + it 'returns nil when author is nil' do + issue.author_id = nil + issue.save(validate: false) + + expect(issue.author_name).to eq nil + end + end + + describe 'assignee_name' do + it 'is delegated to assignee' do + issue.update!(assignee: create(:user)) + + expect(issue.assignee_name).to eq issue.assignee.name + end + + it 'returns nil when assignee is nil' do + issue.assignee_id = nil + issue.save(validate: false) + + expect(issue.assignee_name).to eq nil + end + end + describe "before_save" do describe "#update_cache_counts" do context "when previous assignee exists" do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 73977d031f9..b8584301baa 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -670,4 +670,41 @@ describe Issue, models: true do expect(attrs_hash).to include('time_estimate') end end + + describe '#check_for_spam' do + let(:project) { create :project, visibility_level: visibility_level } + let(:issue) { create :issue, project: project } + + subject do + issue.assign_attributes(description: description) + issue.check_for_spam? + end + + context 'when project is public and spammable attributes changed' do + let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } + let(:description) { 'woo' } + + it 'returns true' do + is_expected.to be_truthy + end + end + + context 'when project is private' do + let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } + let(:description) { issue.description } + + it 'returns false' do + is_expected.to be_falsey + end + end + + context 'when spammable attributes have not changed' do + let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } + let(:description) { issue.description } + + it 'returns false' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 757f3921450..67d48557184 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -215,10 +215,12 @@ describe Namespace, models: true do end describe '#descendants' do - let!(:group) { create(:group) } + let!(:group) { create(:group, path: 'git_lab') } let!(:nested_group) { create(:group, parent: group) } let!(:deep_nested_group) { create(:group, parent: nested_group) } let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } + let!(:another_group) { create(:group, path: 'gitllab') } + let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) } it 'returns the correct descendants' do expect(very_deep_nested_group.descendants.to_a).to eq([]) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 618ce2b6d53..59a2560ca06 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -218,6 +218,20 @@ describe Project, models: true do expect(project2.import_data).to be_nil end + it "does not allow blocked import_url localhost" do + project2 = build(:empty_project, import_url: 'http://localhost:9000/t.git') + + expect(project2).to be_invalid + expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') + end + + it "does not allow blocked import_url port" do + project2 = build(:empty_project, import_url: 'http://github.com:25/t.git') + + expect(project2).to be_invalid + expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') + end + describe 'project pending deletion' do let!(:project_pending_deletion) do create(:empty_project, @@ -1748,11 +1762,14 @@ describe Project, models: true do end describe 'inside_path' do - let!(:project1) { create(:empty_project) } + let!(:project1) { create(:empty_project, namespace: create(:namespace, path: 'name_pace')) } let!(:project2) { create(:empty_project) } + let!(:project3) { create(:empty_project, namespace: create(:namespace, path: 'namespace')) } let!(:path) { project1.namespace.full_path } - it { expect(Project.inside_path(path)).to eq([project1]) } + it 'returns correct project' do + expect(Project.inside_path(path)).to eq([project1]) + end end describe '#route_map_for' do @@ -1900,10 +1917,8 @@ describe Project, models: true do context 'when no user is given' do it 'returns the url to the repo without a username' do - url = project.http_url_to_repo - - expect(url).to eq(project.http_url_to_repo) - expect(url).not_to include('@') + expect(project.http_url_to_repo).to eq("#{project.web_url}.git") + expect(project.http_url_to_repo).not_to include('@') end end @@ -1911,7 +1926,7 @@ describe Project, models: true do it 'returns the url to the repo with the username' do user = build_stubbed(:user) - expect(project.http_url_to_repo(user)).to match(%r{https?:\/\/#{user.username}@}) + expect(project.http_url_to_repo(user)).to start_with("http://#{user.username}@") end end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 58b57bd4fef..b5b9cd024b0 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -35,10 +35,23 @@ describe ProjectWiki, models: true do end describe "#http_url_to_repo" do - it "provides the full http url to the repo" do - gitlab_url = Gitlab.config.gitlab.url - repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git" - expect(subject.http_url_to_repo).to eq(repo_http_url) + let(:project) { create :empty_project } + + context 'when no user is given' do + it 'returns the url to the repo without a username' do + expected_url = "#{Gitlab.config.gitlab.url}/#{subject.path_with_namespace}.git" + + expect(project_wiki.http_url_to_repo).to eq(expected_url) + expect(project_wiki.http_url_to_repo).not_to include('@') + end + end + + context 'when user is given' do + it 'returns the url to the repo with the username' do + user = build_stubbed(:user) + + expect(project_wiki.http_url_to_repo(user)).to start_with("http://#{user.username}@") + end end end diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 0b222022e62..171a51fcc5b 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Route, models: true do - let!(:group) { create(:group, path: 'gitlab', name: 'gitlab') } + let!(:group) { create(:group, path: 'git_lab', name: 'git_lab') } let!(:route) { group.route } describe 'relationships' do @@ -14,10 +14,24 @@ describe Route, models: true do it { is_expected.to validate_uniqueness_of(:path) } end + describe '.inside_path' do + let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) } + let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) } + let!(:another_group) { create(:group, path: 'other') } + let!(:similar_group) { create(:group, path: 'gitllab') } + let!(:another_group_nested) { create(:group, path: 'another', name: 'another', parent: similar_group) } + + it 'returns correct routes' do + expect(Route.inside_path('git_lab')).to match_array([nested_group.route, deep_nested_group.route]) + end + end + describe '#rename_descendants' do let!(:nested_group) { create(:group, path: 'test', name: 'test', parent: group) } let!(:deep_nested_group) { create(:group, path: 'foo', name: 'foo', parent: nested_group) } let!(:similar_group) { create(:group, path: 'gitlab-org', name: 'gitlab-org') } + let!(:another_group) { create(:group, path: 'gittlab', name: 'gitllab') } + let!(:another_group_nested) { create(:group, path: 'git_lab', name: 'git_lab', parent: another_group) } context 'path update' do context 'when route name is set' do @@ -28,6 +42,8 @@ describe Route, models: true do expect(described_class.exists?(path: 'bar/test')).to be_truthy expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy expect(described_class.exists?(path: 'gitlab-org')).to be_truthy + expect(described_class.exists?(path: 'gittlab')).to be_truthy + expect(described_class.exists?(path: 'gittlab/git_lab')).to be_truthy end end @@ -43,14 +59,22 @@ describe Route, models: true do end context 'name update' do - before { route.update_attributes(name: 'bar') } - it "updates children routes with new path" do + route.update_attributes(name: 'bar') + expect(described_class.exists?(name: 'bar')).to be_truthy expect(described_class.exists?(name: 'bar / test')).to be_truthy expect(described_class.exists?(name: 'bar / test / foo')).to be_truthy expect(described_class.exists?(name: 'gitlab-org')).to be_truthy end + + it 'handles a rename from nil' do + # Note: using `update_columns` to skip all validation and callbacks + route.update_columns(name: nil) + + expect { route.update_attributes(name: 'bar') } + .to change { route.name }.from(nil).to('bar') + end end end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 219ab1989ea..8095d01b69e 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -198,4 +198,47 @@ describe Snippet, models: true do expect(snippet.participants).to include(note1.author, note2.author) end end + + describe '#check_for_spam' do + let(:snippet) { create :snippet, visibility_level: visibility_level } + + subject do + snippet.assign_attributes(title: title) + snippet.check_for_spam? + end + + context 'when public and spammable attributes changed' do + let(:visibility_level) { Snippet::PUBLIC } + let(:title) { 'woo' } + + it 'returns true' do + is_expected.to be_truthy + end + end + + context 'when private' do + let(:visibility_level) { Snippet::PRIVATE } + let(:title) { snippet.title } + + it 'returns false' do + is_expected.to be_falsey + end + + it 'returns true when switching to public' do + snippet.save! + snippet.visibility_level = Snippet::PUBLIC + + expect(snippet.check_for_spam?).to be_truthy + end + end + + context 'when spammable attributes have not changed' do + let(:visibility_level) { Snippet::PUBLIC } + let(:title) { snippet.title } + + it 'returns false' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index e7738ca3034..52f68fed2cc 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -534,6 +534,12 @@ describe API::Issues, api: true do describe "GET /projects/:id/issues" do let(:base_url) { "/projects/#{project.id}" } + it 'returns 404 when project does not exist' do + get api('/projects/1000/issues', non_member) + + expect(response).to have_http_status(404) + end + it "returns 404 on private projects for other users" do private_project = create(:empty_project, :private) create(:issue, project: private_project) diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb index 1941ca0d7d8..51021eec63c 100644 --- a/spec/requests/api/v3/issues_spec.rb +++ b/spec/requests/api/v3/issues_spec.rb @@ -439,6 +439,12 @@ describe API::V3::Issues, api: true do describe "GET /projects/:id/issues" do let(:base_url) { "/projects/#{project.id}" } + it 'returns 404 when project does not exist' do + get v3_api('/projects/1000/issues', non_member) + + expect(response).to have_http_status(404) + end + it "returns 404 on private projects for other users" do private_project = create(:empty_project, :private) create(:issue, project: private_project) diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 22115c6566d..d841bdaa292 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -30,6 +30,7 @@ describe Boards::Issues::ListService, services: true do let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) } let!(:closed_issue3) { create(:issue, :closed, project: project) } let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) } + let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) } before do project.team << [user, :developer] @@ -57,7 +58,7 @@ describe Boards::Issues::ListService, services: true do issues = described_class.new(project, user, params).execute - expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1] + expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1] end it 'returns opened issues that have label list applied when listing issues from a label list' do diff --git a/spec/services/create_branch_service_spec.rb b/spec/services/create_branch_service_spec.rb new file mode 100644 index 00000000000..3f548688c20 --- /dev/null +++ b/spec/services/create_branch_service_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe CreateBranchService, services: true do + let(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + describe '#execute' do + context 'when repository is empty' do + let(:project) { create(:project_empty_repo) } + + it 'creates master branch' do + service.execute('my-feature', 'master') + + expect(project.repository.branch_exists?('master')).to be_truthy + end + + it 'creates my-feature branch' do + service.execute('my-feature', 'master') + + expect(project.repository.branch_exists?('my-feature')).to be_truthy + end + end + end +end diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index 0475f38fe5e..7a1ac027310 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -138,7 +138,7 @@ describe Issuable::BulkUpdateService, services: true do let(:labels) { [bug, regression] } it 'updates the labels of all issues passed to the labels passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id))) + expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id))) end it 'does not update issues not passed in' do diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index ab6e8f537ba..e5917bb0b7a 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -120,6 +120,26 @@ describe Projects::ImportService, services: true do end end + context 'with blocked import_URL' do + it 'fails with localhost' do + project.import_url = 'https://localhost:9000/vim/vim.git' + + result = described_class.new(project, user).execute + + expect(result[:status]).to eq :error + expect(result[:message]).to end_with 'Blocked import URL.' + end + + it 'fails with port 25' do + project.import_url = "https://github.com:25/vim/vim.git" + + result = described_class.new(project, user).execute + + expect(result[:status]).to eq :error + expect(result[:message]).to end_with 'Blocked import URL.' + end + end + def stub_github_omniauth_provider provider = OpenStruct.new( 'name' => 'github', diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb index 4ce3b95aa87..e09c05ccf32 100644 --- a/spec/services/spam_service_spec.rb +++ b/spec/services/spam_service_spec.rb @@ -19,42 +19,67 @@ describe SpamService, services: true do let(:issue) { create(:issue, project: project) } let(:request) { double(:request, env: {}) } - context 'when indicated as spam by akismet' do - before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) } + context 'when spammable attributes have not changed' do + before do + issue.closed_at = Time.zone.now - it 'doesnt check as spam when request is missing' do - check_spam(issue, nil, false) - - expect(issue.spam).to be_falsey + allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) end - it 'checks as spam' do - check_spam(issue, request, false) - - expect(issue.spam).to be_truthy + it 'returns false' do + expect(check_spam(issue, request, false)).to be_falsey end - it 'creates a spam log' do + it 'does not create a spam log' do expect { check_spam(issue, request, false) } - .to change { SpamLog.count }.from(0).to(1) + .not_to change { SpamLog.count } end + end - it 'doesnt yield block' do - expect(check_spam(issue, request, false)) - .to eql(SpamLog.last) + context 'when spammable attributes have changed' do + before do + issue.description = 'SPAM!' end - end - context 'when not indicated as spam by akismet' do - before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) } + context 'when indicated as spam by akismet' do + before do + allow(AkismetService).to receive(:new).and_return(double(is_spam?: true)) + end - it 'returns false' do - expect(check_spam(issue, request, false)).to be_falsey + it 'doesnt check as spam when request is missing' do + check_spam(issue, nil, false) + + expect(issue.spam).to be_falsey + end + + it 'checks as spam' do + check_spam(issue, request, false) + + expect(issue.spam).to be_truthy + end + + it 'creates a spam log' do + expect { check_spam(issue, request, false) } + .to change { SpamLog.count }.from(0).to(1) + end + + it 'doesnt yield block' do + expect(check_spam(issue, request, false)) + .to eql(SpamLog.last) + end end - it 'does not create a spam log' do - expect { check_spam(issue, request, false) } - .not_to change { SpamLog.count } + context 'when not indicated as spam by akismet' do + before { allow(AkismetService).to receive(:new).and_return(double(is_spam?: false)) } + + it 'returns false' do + expect(check_spam(issue, request, false)).to be_falsey + end + + it 'does not create a spam log' do + expect { check_spam(issue, request, false) } + .not_to change { SpamLog.count } + end end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index db9f1231682..11037a4917b 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -5,6 +5,7 @@ describe SystemHooksService, services: true do let(:project) { create :project } let(:project_member) { create :project_member } let(:key) { create(:key, user: user) } + let(:deploy_key) { create(:key) } let(:group) { create(:group) } let(:group_member) { create(:group_member) } @@ -18,6 +19,8 @@ describe SystemHooksService, services: true do it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(key, :create)).to include(:username, :key, :id) } it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } + it { expect(event_data(deploy_key, :create)).to include(:key, :id) } + it { expect(event_data(deploy_key, :destroy)).to include(:key, :id) } it do project.old_path_with_namespace = 'renamed_from_path' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ceb3209331f..5ab8f0d981a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,7 +35,8 @@ RSpec.configure do |config| config.include Warden::Test::Helpers, type: :request config.include LoginHelpers, type: :feature config.include SearchHelpers, type: :feature - config.include WaitForAjax, type: :feature + config.include WaitForRequests, :js + config.include WaitForAjax, :js config.include StubConfiguration config.include EmailHelpers, type: :mailer config.include TestEnv diff --git a/spec/support/notify_shared_examples.rb b/spec/support/notify_shared_examples.rb index a3724b801b3..16a425f2ca2 100644 --- a/spec/support/notify_shared_examples.rb +++ b/spec/support/notify_shared_examples.rb @@ -27,24 +27,14 @@ shared_examples 'a multiple recipients email' do end shared_examples 'an email sent from GitLab' do - it 'is sent from GitLab' do + it 'has the characteristics of an email sent from GitLab' do sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(gitlab_sender_display_name) - expect(sender.address).to eq(gitlab_sender) - end - - it 'has a Reply-To address' do reply_to = subject.header[:reply_to].addresses - expect(reply_to).to eq([gitlab_sender_reply_to]) - end - - context 'when custom suffix for email subject is set' do - before do - stub_config_setting(email_subject_suffix: 'A Nice Suffix') - end - it 'ends the subject with the suffix' do - is_expected.to have_subject /\ \| A Nice Suffix$/ + aggregate_failures do + expect(sender.display_name).to eq(gitlab_sender_display_name) + expect(sender.address).to eq(gitlab_sender) + expect(reply_to).to eq([gitlab_sender_reply_to]) end end end @@ -56,43 +46,40 @@ shared_examples 'an email that contains a header with author username' do end shared_examples 'an email with X-GitLab headers containing project details' do - it 'has X-GitLab-Project* headers' do - is_expected.to have_header 'X-GitLab-Project', /#{project.name}/ - is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/ - is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/ + it 'has X-GitLab-Project headers' do + aggregate_failures do + is_expected.to have_header('X-GitLab-Project', /#{project.name}/) + is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) + is_expected.to have_header('X-GitLab-Project-Path', /#{project.path_with_namespace}/) + end end end shared_examples 'a new thread email with reply-by-email enabled' do - let(:regex) { /\A<reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ } - - it 'has a Message-ID header' do - is_expected.to have_header 'Message-ID', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>" - end + it 'has the characteristics of a threaded email' do + host = Gitlab.config.gitlab.host + route_key = "#{model.class.model_name.singular_route_key}_#{model.id}" - it 'has a References header' do - is_expected.to have_header 'References', regex + aggregate_failures do + is_expected.to have_header('Message-ID', "<#{route_key}@#{host}>") + is_expected.to have_header('References', /\A<reply\-.*@#{host}>\Z/ ) + end end end shared_examples 'a thread answer email with reply-by-email enabled' do include_examples 'an email with X-GitLab headers containing project details' - let(:regex) { /\A<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}> <reply\-(.*)@#{Gitlab.config.gitlab.host}>\Z/ } - - it 'has a Message-ID header' do - is_expected.to have_header 'Message-ID', /\A<(.*)@#{Gitlab.config.gitlab.host}>\Z/ - end - - it 'has a In-Reply-To header' do - is_expected.to have_header 'In-Reply-To', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>" - end - it 'has a References header' do - is_expected.to have_header 'References', regex - end + it 'has the characteristics of a threaded reply' do + host = Gitlab.config.gitlab.host + route_key = "#{model.class.model_name.singular_route_key}_#{model.id}" - it 'has a subject that begins with Re: ' do - is_expected.to have_subject /^Re: / + aggregate_failures do + is_expected.to have_header('Message-ID', /\A<.*@#{host}>\Z/) + is_expected.to have_header('In-Reply-To', "<#{route_key}@#{host}>") + is_expected.to have_header('References', /\A<#{route_key}@#{host}> <reply\-.*@#{host}>\Z/ ) + is_expected.to have_subject(/^Re: /) + end end end @@ -136,80 +123,77 @@ shared_examples 'an answer to an existing thread with reply-by-email enabled' do end end -shared_examples 'a new user email' do - it 'is sent to the new user' do - is_expected.to deliver_to new_user_address - end - - it 'has the correct subject' do - is_expected.to have_subject /^Account was created for you$/i - end - - it 'contains the new user\'s login name' do - is_expected.to have_body_text /#{new_user_address}/ - end -end - shared_examples 'it should have Gmail Actions links' do - it { is_expected.to have_body_text '<script type="application/ld+json">' } - it { is_expected.to have_body_text /ViewAction/ } + it do + aggregate_failures do + is_expected.to have_body_text('<script type="application/ld+json">') + is_expected.to have_body_text('ViewAction') + end + end end shared_examples 'it should not have Gmail Actions links' do - it { is_expected.not_to have_body_text '<script type="application/ld+json">' } - it { is_expected.not_to have_body_text /ViewAction/ } + it do + aggregate_failures do + is_expected.not_to have_body_text('<script type="application/ld+json">') + is_expected.not_to have_body_text('ViewAction') + end + end end shared_examples 'it should show Gmail Actions View Issue link' do it_behaves_like 'it should have Gmail Actions links' - it { is_expected.to have_body_text /View Issue/ } + it { is_expected.to have_body_text('View Issue') } end shared_examples 'it should show Gmail Actions View Merge request link' do it_behaves_like 'it should have Gmail Actions links' - it { is_expected.to have_body_text /View Merge request/ } + it { is_expected.to have_body_text('View Merge request') } end shared_examples 'it should show Gmail Actions View Commit link' do it_behaves_like 'it should have Gmail Actions links' - it { is_expected.to have_body_text /View Commit/ } + it { is_expected.to have_body_text('View Commit') } end shared_examples 'an unsubscribeable thread' do it_behaves_like 'an unsubscribeable thread with incoming address without %{key}' - it 'has a List-Unsubscribe header in the correct format' do - is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ - is_expected.to have_header 'List-Unsubscribe', /mailto/ - is_expected.to have_header 'List-Unsubscribe', /^<.+,.+>$/ + it 'has a List-Unsubscribe header in the correct format, and a body link' do + aggregate_failures do + is_expected.to have_header('List-Unsubscribe', /unsubscribe/) + is_expected.to have_header('List-Unsubscribe', /mailto/) + is_expected.to have_header('List-Unsubscribe', /^<.+,.+>$/) + is_expected.to have_body_text('unsubscribe') + end end - - it { is_expected.to have_body_text /unsubscribe/ } end shared_examples 'an unsubscribeable thread with incoming address without %{key}' do include_context 'reply-by-email is enabled with incoming address without %{key}' - it 'has a List-Unsubscribe header in the correct format' do - is_expected.to have_header 'List-Unsubscribe', /unsubscribe/ - is_expected.not_to have_header 'List-Unsubscribe', /mailto/ - is_expected.to have_header 'List-Unsubscribe', /^<[^,]+>$/ + it 'has a List-Unsubscribe header in the correct format, and a body link' do + aggregate_failures do + is_expected.to have_header('List-Unsubscribe', /unsubscribe/) + is_expected.not_to have_header('List-Unsubscribe', /mailto/) + is_expected.to have_header('List-Unsubscribe', /^<[^,]+>$/) + is_expected.to have_body_text('unsubscribe') + end end - - it { is_expected.to have_body_text /unsubscribe/ } end shared_examples 'a user cannot unsubscribe through footer link' do - it 'does not have a List-Unsubscribe header' do - is_expected.not_to have_header 'List-Unsubscribe', /unsubscribe/ + it 'does not have a List-Unsubscribe header or a body link' do + aggregate_failures do + is_expected.not_to have_header('List-Unsubscribe', /unsubscribe/) + is_expected.not_to have_body_text('unsubscribe') + end end - - it { is_expected.not_to have_body_text /unsubscribe/ } end shared_examples 'an email with a labels subscriptions link in its footer' do - it { is_expected.to have_body_text /label subscriptions/ } + it { is_expected.to have_body_text('label subscriptions') } end diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb index a52d8f37d14..4afdbd68304 100644 --- a/spec/support/prometheus_helpers.rb +++ b/spec/support/prometheus_helpers.rb @@ -1,10 +1,10 @@ module PrometheusHelpers def prometheus_memory_query(environment_slug) - %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024} + %{(sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})) /1024/1024} end def prometheus_cpu_query(environment_slug) - %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))} + %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}) * 100} end def prometheus_query_url(prometheus_query) diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 704922b6cf4..b902fe90707 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -324,5 +324,24 @@ RSpec.shared_examples 'slack or mattermost notifications' do it_behaves_like 'call Slack/Mattermost API' end end + + context 'only notify for the default branch' do + context 'when enabled' do + let(:pipeline) do + create(:ci_pipeline, project: project, status: 'failed', ref: 'not-the-default-branch') + end + + before do + chat_service.notify_only_default_branch = true + end + + it 'does not call the Slack/Mattermost API for pipeline events' do + data = Gitlab::DataBuilder::Pipeline.build(pipeline) + result = chat_service.execute(data) + + expect(result).to be_falsy + end + end + end end end diff --git a/spec/support/target_branch_helpers.rb b/spec/support/target_branch_helpers.rb new file mode 100644 index 00000000000..3ee8f0f657e --- /dev/null +++ b/spec/support/target_branch_helpers.rb @@ -0,0 +1,16 @@ +module TargetBranchHelpers + def select_branch(name) + first('button.js-target-branch').click + wait_for_ajax + all('a[data-group="Branches"]').find do |el| + el.text == name + end.click + end + + def create_new_branch(name) + first('button.js-target-branch').click + click_link 'Create new branch' + fill_in 'new_branch_name', with: name + click_button 'Create' + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index f1d226b6ae3..648b0380f18 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -37,9 +37,10 @@ module TestEnv 'conflict-too-large' => '39fa04f', 'deleted-image-test' => '6c17798', 'wip' => 'b9238ee', - 'csv' => '3dd0896' + 'csv' => '3dd0896', + 'v1.1.0' => 'b83d6e3' }.freeze - + # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily # need to keep all the branches in sync. # We currently only need a subset of the branches diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb new file mode 100644 index 00000000000..0bfa7f72ff8 --- /dev/null +++ b/spec/support/wait_for_requests.rb @@ -0,0 +1,32 @@ +module WaitForRequests + extend self + + # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests + def wait_for_requests_complete + Gitlab::Testing::RequestBlockerMiddleware.block_requests! + wait_for('pending AJAX requests complete') do + Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? + end + ensure + Gitlab::Testing::RequestBlockerMiddleware.allow_requests! + end + + # Waits until the passed block returns true + def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01) + wait_until = Time.now + max_wait_time.seconds + loop do + break if yield + if Time.now > wait_until + raise "Condition not met: #{condition_name}" + else + sleep(polling_interval) + end + end + end +end + +RSpec.configure do |config| + config.after(:each, :js) do + wait_for_requests_complete + end +end diff --git a/spec/support/wait_for_vue_resource.rb b/spec/support/wait_for_vue_resource.rb index 1029f84716f..4a4e2e16ee7 100644 --- a/spec/support/wait_for_vue_resource.rb +++ b/spec/support/wait_for_vue_resource.rb @@ -1,7 +1,7 @@ module WaitForVueResource def wait_for_vue_resource(spinner: true) Timeout.timeout(Capybara.default_max_wait_time) do - loop until page.evaluate_script('Vue.activeResources').zero? + loop until page.evaluate_script('window.activeVueResources').zero? end end end diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb new file mode 100644 index 00000000000..d95baddf546 --- /dev/null +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -0,0 +1,78 @@ +require 'rake_helper' + +describe 'gitlab:gitaly namespace rake task' do + before :all do + Rake.application.rake_require 'tasks/gitlab/gitaly' + end + + describe 'install' do + let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' } + let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s } + let(:tag) { "v#{File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp}" } + + context 'no dir given' do + it 'aborts and display a help message' do + # avoid writing task output to spec progress + allow($stderr).to receive :write + expect { run_rake_task('gitlab:gitaly:install') }.to raise_error /Please specify the directory where you want to install gitaly/ + end + end + + context 'when an underlying Git command fail' do + it 'aborts and display a help message' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).and_raise 'Git error' + + expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error' + end + end + + describe 'checkout or clone' do + before do + expect(Dir).to receive(:chdir).with(clone_path) + end + + it 'calls checkout_or_clone_tag with the right arguments' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path) + + run_rake_task('gitlab:gitaly:install', clone_path) + end + end + + describe 'gmake/make' do + before do + FileUtils.mkdir_p(clone_path) + expect(Dir).to receive(:chdir).with(clone_path).and_call_original + end + + context 'gmake is available' do + before do + expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) + allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + end + + it 'calls gmake in the gitaly directory' do + expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) + expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + + run_rake_task('gitlab:gitaly:install', clone_path) + end + end + + context 'gmake is not available' do + before do + expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) + allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + end + + it 'calls make in the gitaly directory' do + expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42]) + expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + + run_rake_task('gitlab:gitaly:install', clone_path) + end + end + end + end +end diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb index 6de66c3cf07..8a66a4aa047 100644 --- a/spec/tasks/gitlab/workhorse_rake_spec.rb +++ b/spec/tasks/gitlab/workhorse_rake_spec.rb @@ -9,9 +9,6 @@ describe 'gitlab:workhorse namespace rake task' do let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' } let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s } let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" } - before do - allow(ENV).to receive(:[]) - end context 'no dir given' do it 'aborts and display a help message' do diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb new file mode 100644 index 00000000000..19036c7677c --- /dev/null +++ b/spec/tasks/tokens_spec.rb @@ -0,0 +1,21 @@ +require 'rake_helper' + +describe 'tokens rake tasks' do + let!(:user) { create(:user) } + + before do + Rake.application.rake_require 'tasks/tokens' + end + + describe 'reset_all task' do + it 'invokes create_hooks task' do + expect { run_rake_task('tokens:reset_all_auth') }.to change { user.reload.authentication_token } + end + end + + describe 'reset_all_email task' do + it 'invokes create_hooks task' do + expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token } + end + end +end diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index c101f6f164d..e4aeaeca508 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -3,8 +3,9 @@ require 'spec_helper' describe 'projects/pipelines/show' do include Devise::Test::ControllerHelpers + let(:user) { create(:user) } let(:project) { create(:project) } - let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) } + let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, user: user) } before do controller.prepend_view_path('app/views/projects') @@ -21,6 +22,7 @@ describe 'projects/pipelines/show' do assign(:project, project) assign(:pipeline, pipeline) + assign(:commit, project.commit) allow(view).to receive(:can?).and_return(true) end @@ -31,6 +33,12 @@ describe 'projects/pipelines/show' do expect(rendered).to have_css('.js-pipeline-graph') expect(rendered).to have_css('.js-grouped-pipeline-dropdown') + # header + expect(rendered).to have_text("##{pipeline.id}") + expect(rendered).to have_css('time', text: pipeline.created_at.strftime("%b %d, %Y")) + expect(rendered).to have_selector(%Q(img[alt$="#{pipeline.user.name}'s avatar"])) + expect(rendered).to have_link(pipeline.user.name, href: user_path(pipeline.user)) + # stages expect(rendered).to have_text('Build') expect(rendered).to have_text('Test') diff --git a/tmp/sockets/private/.gitkeep b/tmp/sockets/private/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tmp/sockets/private/.gitkeep diff --git a/vendor/assets/javascripts/js.cookie.js b/vendor/assets/javascripts/js.cookie.js deleted file mode 100644 index 92dbba162c4..00000000000 --- a/vendor/assets/javascripts/js.cookie.js +++ /dev/null @@ -1,156 +0,0 @@ -/*! - * JavaScript Cookie v2.1.3 - * https://github.com/js-cookie/js-cookie - * - * Copyright 2006, 2015 Klaus Hartl & Fagner Brack - * Released under the MIT license - */ -;(function (factory) { - var registeredInModuleLoader = false; - if (typeof define === 'function' && define.amd) { - define(factory); - registeredInModuleLoader = true; - } - if (typeof exports === 'object') { - module.exports = factory(); - registeredInModuleLoader = true; - } - if (!registeredInModuleLoader) { - var OldCookies = window.Cookies; - var api = window.Cookies = factory(); - api.noConflict = function () { - window.Cookies = OldCookies; - return api; - }; - } -}(function () { - function extend () { - var i = 0; - var result = {}; - for (; i < arguments.length; i++) { - var attributes = arguments[ i ]; - for (var key in attributes) { - result[key] = attributes[key]; - } - } - return result; - } - - function init (converter) { - function api (key, value, attributes) { - var result; - if (typeof document === 'undefined') { - return; - } - - // Write - - if (arguments.length > 1) { - attributes = extend({ - path: '/' - }, api.defaults, attributes); - - if (typeof attributes.expires === 'number') { - var expires = new Date(); - expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); - attributes.expires = expires; - } - - try { - result = JSON.stringify(value); - if (/^[\{\[]/.test(result)) { - value = result; - } - } catch (e) {} - - if (!converter.write) { - value = encodeURIComponent(String(value)) - .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); - } else { - value = converter.write(value, key); - } - - key = encodeURIComponent(String(key)); - key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); - key = key.replace(/[\(\)]/g, escape); - - return (document.cookie = [ - key, '=', value, - attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE - attributes.path ? '; path=' + attributes.path : '', - attributes.domain ? '; domain=' + attributes.domain : '', - attributes.secure ? '; secure' : '' - ].join('')); - } - - // Read - - if (!key) { - result = {}; - } - - // To prevent the for loop in the first place assign an empty array - // in case there are no cookies at all. Also prevents odd result when - // calling "get()" - var cookies = document.cookie ? document.cookie.split('; ') : []; - var rdecode = /(%[0-9A-Z]{2})+/g; - var i = 0; - - for (; i < cookies.length; i++) { - var parts = cookies[i].split('='); - var cookie = parts.slice(1).join('='); - - if (cookie.charAt(0) === '"') { - cookie = cookie.slice(1, -1); - } - - try { - var name = parts[0].replace(rdecode, decodeURIComponent); - cookie = converter.read ? - converter.read(cookie, name) : converter(cookie, name) || - cookie.replace(rdecode, decodeURIComponent); - - if (this.json) { - try { - cookie = JSON.parse(cookie); - } catch (e) {} - } - - if (key === name) { - result = cookie; - break; - } - - if (!key) { - result[name] = cookie; - } - } catch (e) {} - } - - return result; - } - - api.set = api; - api.get = function (key) { - return api.call(api, key); - }; - api.getJSON = function () { - return api.apply({ - json: true - }, [].slice.call(arguments)); - }; - api.defaults = {}; - - api.remove = function (key, attributes) { - api(key, '', extend(attributes, { - expires: -1 - })); - }; - - api.withConverter = init; - - return api; - } - - return init(function () {}); -}));
\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2500ddc6f6b..f254668646c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4450,6 +4450,10 @@ verror@1.3.6: dependencies: extsprintf "1.0.2" +visibilityjs@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63" + vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" @@ -4464,9 +4468,9 @@ vue-resource@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d" -vue@^2.1.10: - version "2.1.10" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.1.10.tgz#c9235ca48c7925137be5807832ac4e3ac180427b" +vue@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.4.tgz#d0a3a050a80a12356d7950ae5a7b3131048209cc" watchpack@^1.2.0: version "1.2.1" |