diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000000..ee4c391da30 --- /dev/null +++ b/.babelrc @@ -0,0 +1,21 @@ +{ + "presets": [ + ["latest", { "es2015": { "modules": false } }], + "stage-2" + ], + "env": { + "coverage": { + "plugins": [ + ["istanbul", { + "exclude": [ + "app/assets/javascripts/droplab/**/*", + "spec/javascripts/**/*" + ] + }], + ["transform-define", { + "process.env.BABEL_ENV": "coverage" + }] + ] + } + } +} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 080d8cd6c7f..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 @@ -277,8 +276,11 @@ rake karma: stage: test <<: *use-db <<: *dedicated-runner + variables: + BABEL_ENV: "coverage" script: - bundle exec rake karma + coverage: '/^Statements *: (\d+\.\d+%)/' artifacts: name: coverage-javascript expire_in: 31d @@ -310,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 @@ -331,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 @@ -343,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 @@ -389,9 +393,11 @@ trigger_docs: cache: {} artifacts: {} script: - - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/1794617/trigger/builds" + - "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)" + - if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi only: - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee # Notify slack in the end notify:slack: 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 ) +- 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 diff --git a/Gemfile b/Gemfile index 6af27ce0f3e..38158387642 100644 --- a/Gemfile +++ b/Gemfile @@ -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 734911baf3f..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) @@ -304,7 +304,7 @@ GEM multi_json (~> 1.10) retriable (~> 1.4) signet (~> 0.6) - google-protobuf (3.2.0) + google-protobuf (3.2.0.2) googleauth (0.5.1) faraday (~> 0.9) jwt (~> 1.4) @@ -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 @@ - + \ 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 = $("
").html(errorMessage).text(); - $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show(); - 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; - }); - } +export default class BlobFileDropzone { + constructor(form, method) { + const formDropzone = form.find('.dropzone'); + Dropzone.autoDiscover = false; - return BlobFileDropzone; - })(); -}).call(window); + 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); + }); + 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 = $('
').html(errorMessage).text(); + $('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show(); + this.removeFile(file); + }, + }); + + 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); }; }; +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'; - 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 - }); +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); - }; + this.$toggleButton.show(); - 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(); - } - }; + return this.editor.focus(); + } - EditBlob.prototype.initSoftWrap = function() { - this.isSoftWrapped = false; - this.$toggleButton = $('.soft-wrap-toggle'); - this.$toggleButton.on('click', this.toggleSoftWrap.bind(this)); - }; + initSoftWrap() { + this.isSoftWrapped = false; + this.$toggleButton = $('.soft-wrap-toggle'); + this.$toggleButton.on('click', () => this.toggleSoftWrap()); + } - 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); + 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 9538f5b69e9..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; @@ -30,7 +31,7 @@ if (this.activeTab === 'selected') { obj.title = 'You haven\'t selected any issues yet'; obj.content = ` - Go back to All issues and select some issues + Go back to Open issues and select some issues to add to your board. `; } @@ -59,7 +60,7 @@ class="btn btn-default" @click="changeTab('all')" v-if="activeTab === 'selected'"> - All issues + Open issues
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"> + v-if="!loading && showList && !filterLoading">
+ v-if="loading || filterLoading">
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 e8cb43f3503..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; @@ -23,7 +24,7 @@ href="#" role="button" @click.prevent="changeTab('all')"> - All issues + Open issues {{ issuesCount }} 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: ` -
+
-
-

- No pipelines to show -

-
+ -
+ +
{ 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 db1a2848d8d..d1a662459e1 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,4 +1,3 @@ -import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make this a bundle /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* global UsernameValidator */ /* global ActiveTabMemoizer */ @@ -42,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; @@ -329,8 +328,6 @@ const UserCallout = require('./user_callout'); case 'ci:lints:show': new gl.CILintEditor(); break; - case 'projects:environments:metrics': - new PrometheusGraph(); case 'users:show': new UserCallout(); break; diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index fdbb4644971..db10b383913 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -132,7 +132,7 @@ class DueDateSelect { const selectedDateValue = this.datePayload[this.abilityName].due_date; const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value'; - this.$loading.fadeIn(); + this.$loading.removeClass('hidden').fadeIn(); if (isDropdown) { this.$dropdown.trigger('loading.gl.dropdown'); 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"> diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js index 8679b7d106f..d58eeeebf81 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js @@ -38,6 +38,7 @@ gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true); } + this.resetFilters(); this.dismissDropdown(); this.dispatchInputEvent(); } @@ -107,7 +108,7 @@ const hook = this.getCurrentHook(); if (hook) { - const data = hook.list.data; + const data = hook.list.data || []; const results = data.map((o) => { const updated = o; updated.droplab_hidden = false; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index bc046a5bb8a..36090b49651 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/issue.js b/app/assets/javascripts/issue.js index ef4029a8623..47e675f537e 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -2,6 +2,7 @@ /* global Flash */ require('./flash'); +require('~/lib/utils/text_utility'); require('vendor/jquery.waitforimages'); require('./task_list'); @@ -50,20 +51,21 @@ class Issue { success: function(data, textStatus, jqXHR) { if ('id' in data) { $(document).trigger('issuable:change'); - const currentTotal = Number($('.issue_counter').text()); + let total = Number($('.issue_counter').text().replace(/[^\d]/, '')); if (isClose) { $('a.btn-close').addClass('hidden'); $('a.btn-reopen').removeClass('hidden'); $('div.status-box-closed').removeClass('hidden'); $('div.status-box-open').addClass('hidden'); - $('.issue_counter').text(currentTotal - 1); + total -= 1; } else { $('a.btn-reopen').addClass('hidden'); $('a.btn-close').removeClass('hidden'); $('div.status-box-closed').addClass('hidden'); $('div.status-box-open').removeClass('hidden'); - $('.issue_counter').text(currentTotal + 1); + total += 1; } + $('.issue_counter').text(gl.text.addDelimiter(total)); } else { new Flash(issueFailMessage, 'alert'); } diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index c648a0f076c..443fb3e0ca9 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -76,7 +76,7 @@ if (!selected.length) { data[abilityName].label_ids = ['']; } - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); return $.ajax({ type: 'PUT', 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 5918182bece..bdc5d913e86 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 %> Deployed to - + <%- name %> <%- deployed_at %> - + View on <%- external_url_formatted %> diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 02ff6f5682c..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) { @@ -159,7 +160,7 @@ } $dropdown.trigger('loading.gl.dropdown'); - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { @@ -171,7 +172,7 @@ data = {}; data[abilityName] = {}; data[abilityName].milestone_id = selected != null ? selected : null; - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); return $.ajax({ type: 'PUT', diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js new file mode 100644 index 00000000000..b3ce9310417 --- /dev/null +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -0,0 +1,6 @@ +import PrometheusGraph from './prometheus_graph'; + +document.addEventListener('DOMContentLoaded', function onLoad() { + document.removeEventListener('DOMContentLoaded', onLoad, false); + return new PrometheusGraph(); +}, false); diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js index 71eb746edac..844a0785bc9 100644 --- a/app/assets/javascripts/monitoring/prometheus_graph.js +++ b/app/assets/javascripts/monitoring/prometheus_graph.js @@ -1,11 +1,10 @@ -/* eslint-disable no-new */ +/* eslint-disable no-new*/ /* global Flash */ import d3 from 'd3'; -import _ from 'underscore'; import statusCodes from '~/lib/utils/http_status'; -import '~/lib/utils/common_utils'; -import '~/flash'; +import '../lib/utils/common_utils'; +import '../flash'; const prometheusGraphsContainer = '.prometheus-graph'; const metricsEndpoint = 'metrics.json'; @@ -31,22 +30,21 @@ class PrometheusGraph { } createGraph() { - const self = this; - _.each(this.data, (value, key) => { - if (value.length > 0 && (key === 'cpu_values' || key === 'memory_values')) { - self.plotValues(value, key); + Object.keys(this.data).forEach((key) => { + const value = this.data[key]; + if (value.length > 0) { + this.plotValues(value, key); } }); } init() { - const self = this; this.getData().then((metricsResponse) => { - if (metricsResponse === {}) { + if (Object.keys(metricsResponse).length === 0) { new Flash('Empty metrics', 'alert'); } else { - self.transformData(metricsResponse); - self.createGraph(); + this.transformData(metricsResponse); + this.createGraph(); } }); } @@ -182,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); @@ -190,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) { @@ -265,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)', }, }; @@ -321,12 +319,14 @@ class PrometheusGraph { transformData(metricsResponse) { const metricTypes = {}; - _.each(metricsResponse.metrics, (value, key) => { - const metricValues = value[0].values; - metricTypes[key] = _.map(metricValues, metric => ({ - time: new Date(metric[0] * 1000), - value: metric[1], - })); + Object.keys(metricsResponse.metrics).forEach((key) => { + if (key === 'cpu_values' || key === 'memory_values') { + const metricValues = (metricsResponse.metrics[key])[0]; + metricTypes[key] = metricValues.values.map(metric => ({ + time: new Date(metric[0] * 1000), + value: metric[1], + })); + } }); this.data = metricTypes; } 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 = `
`; -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 c7a57b47834..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; @@ -53,7 +54,7 @@ $loading = $block.find('.block-loading').fadeOut(); var updateIssueBoardsIssue = function () { - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) .then(function () { $loading.fadeOut(); @@ -90,7 +91,7 @@ data = {}; data[abilityName] = {}; data[abilityName].assignee_id = selected != null ? selected : null; - $loading.fadeIn(); + $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); return $.ajax({ type: 'PUT', 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: ` +
+
+
+ ${pipelinesEmptyStateSVG} +
+
+ +
+
+

Build with confidence

+

+ Continous Integration can help catch bugs by running your tests automatically, + while Continuous Deployment can help you deliver code to your product environment. +

+ + Get started with Pipelines + +
+
+
+ `, +}; 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: ` +
+
+
+ ${pipelinesErrorStateSVG} +
+
+ +
+
+

The API failed to fetch the pipelines.

+
+
+
+ `, +}; 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: ` + + `, +}; 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: ` + + `, +}; 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: ` - + `, })); 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: ` -
-
- +
+ +
+ + +
-
-

- No pipelines to show -

-
+
-
- -
+
+