diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fc0d2b71174..87d73fc0c52 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -49,11 +49,11 @@ stages: - gitlab-org .tests-metadata-state: &tests-metadata-state - services: [] + <<: *dedicated-runner variables: - SETUP_DB: "false" - USE_BUNDLE_INSTALL: "false" TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache" + before_script: + - source scripts/utils.sh artifacts: expire_in: 31d paths: @@ -80,6 +80,7 @@ stages: .rspec-metadata: &rspec-metadata <<: *dedicated-runner <<: *pull-cache + <<: *except-docs stage: test script: - JOB_NAME=( $CI_JOB_NAME ) @@ -109,16 +110,15 @@ stages: .rspec-metadata-pg: &rspec-metadata-pg <<: *rspec-metadata <<: *use-pg - <<: *except-docs .rspec-metadata-mysql: &rspec-metadata-mysql <<: *rspec-metadata <<: *use-mysql - <<: *except-docs .spinach-metadata: &spinach-metadata <<: *dedicated-runner <<: *pull-cache + <<: *except-docs stage: test script: - JOB_NAME=( $CI_JOB_NAME ) @@ -141,12 +141,10 @@ stages: .spinach-metadata-pg: &spinach-metadata-pg <<: *spinach-metadata <<: *use-pg - <<: *except-docs .spinach-metadata-mysql: &spinach-metadata-mysql <<: *spinach-metadata <<: *use-mysql - <<: *except-docs .only-canonical-masters: &only-canonical-masters only: @@ -157,12 +155,8 @@ stages: # Trigger a package build in omnibus-gitlab repository build-package: - image: ruby:2.3-alpine + image: ruby:2.4-alpine before_script: [] - services: [] - variables: - SETUP_DB: "false" - USE_BUNDLE_INSTALL: "false" stage: build cache: {} when: manual @@ -183,13 +177,9 @@ build-package: - apk add --update openssl - wget https://gitlab.com/gitlab-org/gitlab-ce/raw/master/scripts/trigger-build-docs - chmod 755 trigger-build-docs - services: [] cache: {} dependencies: [] - artifacts: {} variables: - SETUP_DB: "false" - USE_BUNDLE_INSTALL: "false" GIT_STRATEGY: none when: manual only: @@ -222,7 +212,6 @@ review-docs-cleanup: # Retrieve knapsack and rspec_flaky reports retrieve-tests-metadata: <<: *tests-metadata-state - <<: *dedicated-runner <<: *except-docs stage: prepare cache: @@ -240,7 +229,6 @@ retrieve-tests-metadata: update-tests-metadata: <<: *tests-metadata-state - <<: *dedicated-runner <<: *only-canonical-masters stage: post-test cache: @@ -305,69 +293,69 @@ setup-test-env: - public/assets - tmp/tests -rspec-pg 0 25: *rspec-metadata-pg -rspec-pg 1 25: *rspec-metadata-pg -rspec-pg 2 25: *rspec-metadata-pg -rspec-pg 3 25: *rspec-metadata-pg -rspec-pg 4 25: *rspec-metadata-pg -rspec-pg 5 25: *rspec-metadata-pg -rspec-pg 6 25: *rspec-metadata-pg -rspec-pg 7 25: *rspec-metadata-pg -rspec-pg 8 25: *rspec-metadata-pg -rspec-pg 9 25: *rspec-metadata-pg -rspec-pg 10 25: *rspec-metadata-pg -rspec-pg 11 25: *rspec-metadata-pg -rspec-pg 12 25: *rspec-metadata-pg -rspec-pg 13 25: *rspec-metadata-pg -rspec-pg 14 25: *rspec-metadata-pg -rspec-pg 15 25: *rspec-metadata-pg -rspec-pg 16 25: *rspec-metadata-pg -rspec-pg 17 25: *rspec-metadata-pg -rspec-pg 18 25: *rspec-metadata-pg -rspec-pg 19 25: *rspec-metadata-pg -rspec-pg 20 25: *rspec-metadata-pg -rspec-pg 21 25: *rspec-metadata-pg -rspec-pg 22 25: *rspec-metadata-pg -rspec-pg 23 25: *rspec-metadata-pg -rspec-pg 24 25: *rspec-metadata-pg +rspec-pg 0 26: *rspec-metadata-pg +rspec-pg 1 26: *rspec-metadata-pg +rspec-pg 2 26: *rspec-metadata-pg +rspec-pg 3 26: *rspec-metadata-pg +rspec-pg 4 26: *rspec-metadata-pg +rspec-pg 5 26: *rspec-metadata-pg +rspec-pg 6 26: *rspec-metadata-pg +rspec-pg 7 26: *rspec-metadata-pg +rspec-pg 8 26: *rspec-metadata-pg +rspec-pg 9 26: *rspec-metadata-pg +rspec-pg 10 26: *rspec-metadata-pg +rspec-pg 11 26: *rspec-metadata-pg +rspec-pg 12 26: *rspec-metadata-pg +rspec-pg 13 26: *rspec-metadata-pg +rspec-pg 14 26: *rspec-metadata-pg +rspec-pg 15 26: *rspec-metadata-pg +rspec-pg 16 26: *rspec-metadata-pg +rspec-pg 17 26: *rspec-metadata-pg +rspec-pg 18 26: *rspec-metadata-pg +rspec-pg 19 26: *rspec-metadata-pg +rspec-pg 20 26: *rspec-metadata-pg +rspec-pg 21 26: *rspec-metadata-pg +rspec-pg 22 26: *rspec-metadata-pg +rspec-pg 23 26: *rspec-metadata-pg +rspec-pg 24 26: *rspec-metadata-pg +rspec-pg 25 26: *rspec-metadata-pg -rspec-mysql 0 25: *rspec-metadata-mysql -rspec-mysql 1 25: *rspec-metadata-mysql -rspec-mysql 2 25: *rspec-metadata-mysql -rspec-mysql 3 25: *rspec-metadata-mysql -rspec-mysql 4 25: *rspec-metadata-mysql -rspec-mysql 5 25: *rspec-metadata-mysql -rspec-mysql 6 25: *rspec-metadata-mysql -rspec-mysql 7 25: *rspec-metadata-mysql -rspec-mysql 8 25: *rspec-metadata-mysql -rspec-mysql 9 25: *rspec-metadata-mysql -rspec-mysql 10 25: *rspec-metadata-mysql -rspec-mysql 11 25: *rspec-metadata-mysql -rspec-mysql 12 25: *rspec-metadata-mysql -rspec-mysql 13 25: *rspec-metadata-mysql -rspec-mysql 14 25: *rspec-metadata-mysql -rspec-mysql 15 25: *rspec-metadata-mysql -rspec-mysql 16 25: *rspec-metadata-mysql -rspec-mysql 17 25: *rspec-metadata-mysql -rspec-mysql 18 25: *rspec-metadata-mysql -rspec-mysql 19 25: *rspec-metadata-mysql -rspec-mysql 20 25: *rspec-metadata-mysql -rspec-mysql 21 25: *rspec-metadata-mysql -rspec-mysql 22 25: *rspec-metadata-mysql -rspec-mysql 23 25: *rspec-metadata-mysql -rspec-mysql 24 25: *rspec-metadata-mysql +rspec-mysql 0 26: *rspec-metadata-mysql +rspec-mysql 1 26: *rspec-metadata-mysql +rspec-mysql 2 26: *rspec-metadata-mysql +rspec-mysql 3 26: *rspec-metadata-mysql +rspec-mysql 4 26: *rspec-metadata-mysql +rspec-mysql 5 26: *rspec-metadata-mysql +rspec-mysql 6 26: *rspec-metadata-mysql +rspec-mysql 7 26: *rspec-metadata-mysql +rspec-mysql 8 26: *rspec-metadata-mysql +rspec-mysql 9 26: *rspec-metadata-mysql +rspec-mysql 10 26: *rspec-metadata-mysql +rspec-mysql 11 26: *rspec-metadata-mysql +rspec-mysql 12 26: *rspec-metadata-mysql +rspec-mysql 13 26: *rspec-metadata-mysql +rspec-mysql 14 26: *rspec-metadata-mysql +rspec-mysql 15 26: *rspec-metadata-mysql +rspec-mysql 16 26: *rspec-metadata-mysql +rspec-mysql 17 26: *rspec-metadata-mysql +rspec-mysql 18 26: *rspec-metadata-mysql +rspec-mysql 19 26: *rspec-metadata-mysql +rspec-mysql 20 26: *rspec-metadata-mysql +rspec-mysql 21 26: *rspec-metadata-mysql +rspec-mysql 22 26: *rspec-metadata-mysql +rspec-mysql 23 26: *rspec-metadata-mysql +rspec-mysql 24 26: *rspec-metadata-mysql +rspec-mysql 25 26: *rspec-metadata-mysql -spinach-pg 0 5: *spinach-metadata-pg -spinach-pg 1 5: *spinach-metadata-pg -spinach-pg 2 5: *spinach-metadata-pg -spinach-pg 3 5: *spinach-metadata-pg -spinach-pg 4 5: *spinach-metadata-pg +spinach-pg 0 4: *spinach-metadata-pg +spinach-pg 1 4: *spinach-metadata-pg +spinach-pg 2 4: *spinach-metadata-pg +spinach-pg 3 4: *spinach-metadata-pg -spinach-mysql 0 5: *spinach-metadata-mysql -spinach-mysql 1 5: *spinach-metadata-mysql -spinach-mysql 2 5: *spinach-metadata-mysql -spinach-mysql 3 5: *spinach-metadata-mysql -spinach-mysql 4 5: *spinach-metadata-mysql +spinach-mysql 0 4: *spinach-metadata-mysql +spinach-mysql 1 4: *spinach-metadata-mysql +spinach-mysql 2 4: *spinach-metadata-mysql +spinach-mysql 3 4: *spinach-metadata-mysql # Static analysis jobs .ruby-static-analysis: &ruby-static-analysis diff --git a/.rubocop.yml b/.rubocop.yml index dbeb1880d39..c427f219a0d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -624,7 +624,7 @@ Style/PredicateName: # branches, and conditions. Metrics/AbcSize: Enabled: true - Max: 55.25 + Max: 54.28 # This cop checks if the length of a block exceeds some maximum value. Metrics/BlockLength: @@ -665,7 +665,7 @@ Metrics/ParameterLists: # A complexity metric geared towards measuring complexity for a human reader. Metrics/PerceivedComplexity: Enabled: true - Max: 15 + Max: 14 # Lint ######################################################################## diff --git a/CHANGELOG.md b/CHANGELOG.md index efd32d44890..6bca9944bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,204 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.1.0 (2017-10-22) + +- [SECURITY] Use a timeout on certain git operations. !14872 +- [SECURITY] Move project repositories between namespaces when renaming users. +- [SECURITY] Prevent an open redirect on project pages. +- [SECURITY] Prevent a persistent XSS in user-provided markup. +- [REMOVED] Remove the ability to visit the issue edit form directly. !14523 +- [REMOVED] Remove animate.js and label animation. +- [FIXED] Perform prometheus data endpoint requests in parallel. !14003 +- [FIXED] Escape quotes in git username. !14020 (Brandon Everett) +- [FIXED] Fixed non-UTF-8 valid branch names from causing an error. !14090 +- [FIXED] Read import sources from setting at first initialization. !14141 (Visay Keo) +- [FIXED] Display full pre-receive and post-receive hook output in GitLab UI. !14222 (Robin Bobbitt) +- [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258 +- [FIXED] Fix the default branches sorting to actually be 'Last updated'. !14295 +- [FIXED] Fixes project denial of service via gitmodules using Extended ASCII. !14301 +- [FIXED] Fix the filesystem shard health check to check all configured shards. !14341 +- [FIXED] Compare email addresses case insensitively when verifying GPG signatures. !14376 (Tim Bishop) +- [FIXED] Allow the git circuit breaker to correctly handle missing repository storages. !14417 +- [FIXED] Fix `rake gitlab:incoming_email:check` and make it report the actual error. !14423 +- [FIXED] Does not check if an invariant hashed storage path exists on disk when renaming projects. !14428 +- [FIXED] Also reserve refs/replace after importing a project. !14436 +- [FIXED] Fix profile image orientation based on EXIF data gvieira37. !14461 (gvieira37) +- [FIXED] Move the deployment flag content to the left when deployment marker is near the end. !14514 +- [FIXED] Fix notes type created from import. This should fix some missing notes issues from imported projects. !14524 +- [FIXED] Fix bottom spacing for dropdowns that open upwards. !14535 +- [FIXED] Adjusts tag link to avoid underlining spaces. !14544 (Guilherme Vieira) +- [FIXED] Add missing space in Sidekiq memory killer log message. !14553 (Benjamin Drung) +- [FIXED] Ensure no exception is raised when Raven tries to get the current user in API context. !14580 +- [FIXED] Fix edit project service cancel button position. !14596 (Matt Coleman) +- [FIXED] Fix case sensitive email confirmation on signup. !14606 (robdel12) +- [FIXED] Whitelist authorized_keys.lock in the gitlab:check rake task. !14624 +- [FIXED] Allow merge in MR widget with no pipeline but using "Only allow merge requests to be merged if the pipeline succeeds". !14633 +- [FIXED] Fix navigation dropdown close animation on mobile screens. !14649 +- [FIXED] Fix the project import with issues and milestones. !14657 +- [FIXED] Use explicit boolean true attribute for show-disabled-button in Vue files. !14672 +- [FIXED] Make tabs on top scrollable on admin dashboard. !14685 (Takuya Noguchi) +- [FIXED] Fix broken Y-axis scaling in some Prometheus graphs. !14693 +- [FIXED] Search or compare LDAP DNs case-insensitively and ignore excess whitespace. !14697 +- [FIXED] Allow prometheus graphs to correctly handle NaN values. !14741 +- [FIXED] Don't show an "Unsubscribe" link in snippet comment notifications. !14764 +- [FIXED] Fixed duplicate notifications when added multiple labels on an issue. !14798 +- [FIXED] Fix alignment for indeterminate marker in dropdowns. !14809 +- [FIXED] Fix error when updating a forked project with deleted `ForkedProjectLink`. !14916 +- [FIXED] Correctly render asset path for locales with a region. !14924 +- [FIXED] Fix the external URLs generated for online view of HTML artifacts. !14977 +- [FIXED] Reschedule merge request diff background migrations to catch failures from 9.5 run. +- [FIXED] fix merge request widget status icon for failed CI. +- [FIXED] Fix the number representing the amount of commits related to a push event. +- [FIXED] Sync up hover and legend data across all graphs for the prometheus dashboard. +- [FIXED] Fixes mini pipeline graph in commit view. +- [FIXED] Fix comment deletion confirmation dialog typo. +- [FIXED] Fix project snippets breadcrumb link. +- [FIXED] Make usage ping scheduling more robust. +- [FIXED] Make "merge ongoing" check more consistent. +- [FIXED] Add 1000+ counters to job page. +- [FIXED] Fixed issue/merge request breadcrumb titles not having links. +- [FIXED] Fixed commit avatars being centered vertically. +- [FIXED] Tooltips in the commit info box now all face the same direction. (Jedidiah Broadbent) +- [FIXED] Fixed navbar title colors leaking out of the navbar. +- [FIXED] Fix bug that caused merge requests with diff notes imported from Bitbucket to raise errors. +- [FIXED] Correctly detect multiple issue URLs after 'Closes...' in MR descriptions. +- [FIXED] Set default scope on PATs that don't have one set to allow them to be revoked. +- [FIXED] Fix application setting to cache nil object. +- [FIXED] Fix image diff swipe handle offset to correctly align with the frame. +- [FIXED] Force non diff resolved discussion to display when collapse toggled. +- [FIXED] Fix resolved discussions not expanding on side by side view. +- [FIXED] Fixed the sidebar scrollbar overlapping links. +- [FIXED] Issue board tooltips are now the correct width when the column is collapsed. (Jedidiah Broadbent) +- [FIXED] Improve autodevops banner UX and render it only in project page. +- [FIXED] Fix typo in cycle analytics breaking time component. +- [FIXED] Force two up view to load by default for image diffs. +- [FIXED] Fixed milestone breadcrumb links. +- [FIXED] Fixed group sort dropdown defaulting to empty. +- [FIXED] Fixed notes not being scrolled to in merge requests. +- [FIXED] Adds Event polyfill for IE11. +- [FIXED] Update native unicode emojis to always render as normal text (previously could render italicized). (Branka Martinovic) +- [FIXED] Sort JobsController by id, not created_at. +- [FIXED] Fix revision and total size missing for Container Registry. +- [FIXED] Fixed milestone issuable assignee link URL. +- [FIXED] Fixed breadcrumbs container expanding in side-by-side diff view. +- [FIXED] Fixed merge request widget merged & closed date tooltip text. +- [FIXED] Prevent creating multiple ApplicationSetting instances. +- [FIXED] Fix username and ID not logging in production_json.log for Git activity. +- [FIXED] Make Redcarpet Markdown renderer thread-safe. +- [FIXED] Two factor auth messages in settings no longer overlap the button. (Jedidiah Broadbent) +- [FIXED] Made the "remember me" check boxes have consistent styles and alignment. (Jedidiah Broadbent) +- [FIXED] Prevent branches or tags from starting with invalid characters (e.g. -, .). +- [DEPRECATED] Removed two legacy config options. (Daniel Voogsgerd) +- [CHANGED] Show notes number more user-friendly in the graph. !13949 (Vladislav Kaverin) +- [CHANGED] Link SAML users to LDAP by email. !14216 +- [CHANGED] Display whether branch has been merged when deleting protected branch. !14220 +- [CHANGED] Make the labels in the Compare form less confusing. !14225 +- [CHANGED] Confirmation email shows link as text instead of human readable text. !14243 (bitsapien) +- [CHANGED] Return only group's members in user dropdowns on issuables list pages. !14249 +- [CHANGED] Added defaults for protected branches dropdowns on the repository settings. !14278 +- [CHANGED] Show confirmation modal before deleting account. !14360 +- [CHANGED] Allow creating merge requests across a fork network. !14422 +- [CHANGED] Re-arrange script HTML tags before template HTML tags in .vue files. !14671 +- [CHANGED] Create idea of read-only database. !14688 +- [CHANGED] Add active states to nav bar counters. +- [CHANGED] Add view replaced file link for image diffs. +- [CHANGED] Adjust tooltips to adhere to 8px grid and make them more readable. +- [CHANGED] breadcrumbs receives padding when double lined. +- [CHANGED] Allow developer role to admin milestones. +- [CHANGED] Stop using Sidekiq for updating Key#last_used_at. +- [CHANGED] Include GitLab full name in Slack messages. +- [ADDED] Expose last pipeline details in API response when getting a single commit. !13521 (Mehdi Lahmam (@mehlah)) +- [ADDED] Allow to use same periods for different housekeeping tasks (effectively skipping the lesser task). !13711 (cernvcs) +- [ADDED] Add GitLab-Pages version to Admin Dashboard. !14040 (travismiller) +- [ADDED] Commenting on image diffs. !14061 +- [ADDED] Script to migrate project's repositories to new Hashed Storage. !14067 +- [ADDED] Hide close MR button after merge without reloading page. !14122 (Jacopo Beschi @jacopo-beschi) +- [ADDED] Add Gitaly version to Admin Dashboard. !14313 (Jacopo Beschi @jacopo-beschi) +- [ADDED] Add 'closed_at' attribute to Issues API. !14316 (Vitaliy @blackst0ne Klachkov) +- [ADDED] Add tooltip for milestone due date to issue and merge request lists. !14318 (Vitaliy @blackst0ne Klachkov) +- [ADDED] Improve list of sorting options. !14320 (Vitaliy @blackst0ne Klachkov) +- [ADDED] Add client and call site metadata to Gitaly calls for better traceability. !14332 +- [ADDED] Strip gitlab-runner section markers in build trace HTML view. !14393 +- [ADDED] Add online view of HTML artifacts for public projects. !14399 +- [ADDED] Create Kubernetes cluster on GKE from k8s service. !14470 +- [ADDED] Add support for GPG subkeys in signature verification. !14517 +- [ADDED] Parse and store gitlab-runner timestamped section markers. !14551 +- [ADDED] Add "implements" to the default issue closing message regex. !14612 (Guilherme Vieira) +- [ADDED] Replace `tag: true` into `:tag` in the specs. !14653 (Jacopo Beschi @jacopo-beschi) +- [ADDED] Discussion lock for issues and merge requests. +- [ADDED] Add an API endpoint to determine the forks of a project. +- [ADDED] Add help text to runner edit: tags should be separated by commas. (Brendan O'Leary) +- [ADDED] Only copy old/new code when selecting left/right side of parallel diff. +- [ADDED] Expose avatar_url when requesting list of projects from API with simple=true. +- [ADDED] A confirmation email is now sent when adding a secondary email address. (digitalmoksha) +- [ADDED] Move Custom merge methods from EE. +- [ADDED] Makes @mentions links have a different styling for better separation. +- [ADDED] Added tabs to dashboard/projects to easily switch to personal projects. +- [OTHER] Extract AutocompleteController#users into finder. !13778 (Maxim Rydkin, Mayra Cabrera) +- [OTHER] Replace 'project/wiki.feature' spinach test with an rspec analog. !13856 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Expand docs for changing username or group path. !13914 +- [OTHER] Move `lib/ci` to `lib/gitlab/ci`. !14078 (Maxim Rydkin) +- [OTHER] Decrease Cyclomatic Complexity threshold to 13. !14152 (Maxim Rydkin) +- [OTHER] Decrease Perceived Complexity threshold to 15. !14160 (Maxim Rydkin) +- [OTHER] Replace project/group_links.feature spinach test with an rspec analog. !14169 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the project/milestone.feature spinach test with an rspec analog. !14171 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the profile/emails.feature spinach test with an rspec analog. !14172 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the project/team_management.feature spinach test with an rspec analog. !14173 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'project/merge_requests/accept.feature' spinach test with an rspec analog. !14176 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'project/builds/summary.feature' spinach test with an rspec analog. !14177 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Optimize the boards' issues fetching. !14198 +- [OTHER] Replace the 'project/merge_requests/revert.feature' spinach test with an rspec analog. !14201 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'project/issues/award_emoji.feature' spinach test with an rspec analog. !14202 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'profile/active_tab.feature' spinach test with an rspec analog. !14239 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'search.feature' spinach test with an rspec analog. !14248 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Load sidebar participants avatars only when visible. !14270 +- [OTHER] Adds gitlab features and components to usage ping data. !14305 +- [OTHER] Replace the 'project/archived.feature' spinach test with an rspec analog. !14322 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'project/commits/revert.feature' spinach test with an rspec analog. !14325 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'project/snippets.feature' spinach test with an rspec analog. !14326 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Add link to OpenID Connect documentation. !14368 (Markus Koller) +- [OTHER] Upgrade doorkeeper-openid_connect. !14372 (Markus Koller) +- [OTHER] Upgrade gitlab-markup gem. !14395 (Markus Koller) +- [OTHER] Index projects on repository storage. !14414 +- [OTHER] Replace the 'project/shortcuts.feature' spinach test with an rspec analog. !14431 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Replace the 'project/service.feature' spinach test with an rspec analog. !14432 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Improve GitHub import performance. !14445 +- [OTHER] Add basic sprintf implementation to JavaScript. !14506 +- [OTHER] Replace the 'project/merge_requests.feature' spinach test with an rspec analog. !14621 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Update GitLab Pages to v0.6.0. !14630 +- [OTHER] Add documentation to summarise project archiving. !14650 +- [OTHER] Remove 'Repo' prefix from API entites. !14694 (Vitaliy @blackst0ne Klachkov) +- [OTHER] Removes cycle analytics service and store from global namespace. +- [OTHER] Improves i18n for Auto Devops callout. +- [OTHER] Exports common_utils utility functions as modules. +- [OTHER] Use `simple=true` for projects API in Projects dropdown for better search performance. +- [OTHER] Change index on ci_builds to optimize Jobs Controller. +- [OTHER] Add index for merge_requests.merge_commit_sha. +- [OTHER] Add (partial) index on Labels.template. +- [OTHER] Cache issue and MR template names in Redis. +- [OTHER] changed dashed border button color to be darker. +- [OTHER] Speed up permission checks. +- [OTHER] Fix docs for lightweight tag creation via API. +- [OTHER] Clarify artifact download via the API only accepts branch or tag name for ref. +- [OTHER] Change recommended MySQL version to 5.6. +- [OTHER] Bump google-api-client Gem from 0.8.6 to 0.13.6. +- [OTHER] Detect when changelog entries are invalid. +- [OTHER] Use a UNION ALL for getting merge request notes. +- [OTHER] Remove an index on ci_builds meant to be only temporary. +- [OTHER] Remove a SQL query from the todos index page. +- Support custom attributes on users. !13038 (Markus Koller) +- made read-only APIs for public merge requests available without authentication. !13291 (haseebeqx) +- Hide read_registry scope when registry is disabled on instance. !13314 (Robin Bobbitt) +- creation of keys moved to services. !13331 (haseebeqx) +- Add username as GL_USERNAME in hooks. + +## 10.0.4 (2017-10-16) + +- [SECURITY] Move project repositories between namespaces when renaming users. +- [SECURITY] Prevent an open redirect on project pages. +- [SECURITY] Prevent a persistent XSS in user-provided markup. + ## 10.0.3 (2017-10-05) - [FIXED] find_user Users helper method no longer overrides find_user API helper method. !14418 @@ -212,6 +410,14 @@ entry. - Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi) - [BUGIFX] Improves subgroup creation permissions. !13418 +## 9.5.9 (2017-10-16) + +- [SECURITY] Move project repositories between namespaces when renaming users. +- [SECURITY] Prevent an open redirect on project pages. +- [SECURITY] Prevent a persistent XSS in user-provided markup. +- [FIXED] Allow using newlines in pipeline email service recipients. !14250 +- Escape user name in filtered search bar. + ## 9.5.8 (2017-10-04) - [FIXED] Fixed fork button being disabled for users who can fork to a group. @@ -457,6 +663,15 @@ entry. - Use a specialized class for querying events to improve performance. - Update build badges to be pipeline badges and display passing instead of success. +## 9.4.7 (2017-10-16) + +- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller) +- [SECURITY] Move project repositories between namespaces when renaming users. +- [SECURITY] Prevent an open redirect on project pages. +- [SECURITY] Prevent a persistent XSS in user-provided markup. +- [FIXED] Allow using newlines in pipeline email service recipients. !14250 +- Escape user name in filtered search bar. + ## 9.4.6 (2017-09-06) - [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 301092317fe..fbaaafa001b 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.46.0 +0.49.0 \ No newline at end of file diff --git a/Gemfile b/Gemfile index c1988aea803..f64cdca31c0 100644 --- a/Gemfile +++ b/Gemfile @@ -102,7 +102,7 @@ gem 'fog-google', '~> 0.5' gem 'fog-local', '~> 0.3' gem 'fog-openstack', '~> 0.1' gem 'fog-rackspace', '~> 0.1.1' -gem 'fog-aliyun', '~> 0.1.0' +gem 'fog-aliyun', '~> 0.2.0' # for Google storage gem 'google-api-client', '~> 0.13.6' @@ -281,7 +281,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~>0.7.0.beta14' + gem 'prometheus-client-mmap', '~>0.7.0.beta17' gem 'raindrops', '~> 0.18' end @@ -398,7 +398,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.41.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.45.0', require: 'gitaly' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index f5712da7508..601d5ca16e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -214,7 +214,7 @@ GEM flowdock (0.7.1) httparty (~> 0.7) multi_json - fog-aliyun (0.1.0) + fog-aliyun (0.2.0) fog-core (~> 1.27) fog-json (~> 1.0) ipaddress (~> 0.8) @@ -273,7 +273,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.41.0) + gitaly-proto (0.45.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -324,7 +324,9 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.4.0.2) + google-protobuf (3.4.1.1) + googleapis-common-protos-types (1.0.0) + google-protobuf (~> 3.0) googleauth (0.5.3) faraday (~> 0.12) jwt (~> 1.4) @@ -351,8 +353,9 @@ GEM rake grape_logging (1.7.0) grape - grpc (1.6.0) + grpc (1.6.6) google-protobuf (~> 3.1) + googleapis-common-protos-types (~> 1.0.0) googleauth (~> 0.5.1) haml (4.0.7) tilt @@ -620,7 +623,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.7.0.beta14) + prometheus-client-mmap (0.7.0.beta17) mmap2 (~> 2.2, >= 2.2.7) pry (0.10.4) coderay (~> 1.1.0) @@ -1012,7 +1015,7 @@ DEPENDENCIES flay (~> 2.8.0) flipper (~> 0.10.2) flipper-active_record (~> 0.10.2) - fog-aliyun (~> 0.1.0) + fog-aliyun (~> 0.2.0) fog-aws (~> 1.4) fog-core (~> 1.44) fog-google (~> 0.5) @@ -1027,7 +1030,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.41.0) + gitaly-proto (~> 0.45.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) @@ -1103,7 +1106,7 @@ DEPENDENCIES pg (~> 0.18.2) poltergeist (~> 1.9.0) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.7.0.beta14) + prometheus-client-mmap (~> 0.7.0.beta17) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 1efb2a35f6d..5cf9fee1a14 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -1,35 +1,3 @@ # GitLab Maintenance Policy -GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: -`(Major).(Minor).(Patch)` in a [pragmatic way]. - -- **Major version**: Whenever there is something significant or any backwards - incompatible changes are introduced to the public API. -- **Minor version**: When new, backwards compatible functionality is introduced - to the public API or a minor feature is introduced, or when a set of smaller - features is rolled out. -- **Patch number**: When backwards compatible bug fixes are introduced that fix - incorrect behavior. - -The current stable release will receive security patches and bug fixes -(eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable -release where the minor version is increased numerically by increments of one -(eg. `8.9 -> 8.10`). - -Our current policy is to support one stable release at any given time, but for -medium-level security issues, we may consider [backporting to the previous two -monthly releases][rel-sec]. - -We encourage everyone to run the latest stable release to ensure that you can -easily upgrade to the most secure and feature-rich GitLab experience. In order -to make sure you can easily run the most recent stable release, we are working -hard to keep the update process simple and reliable. - -More information about the release procedures can be found in our -[release-tools documentation][rel]. You may also want to read our -[Responsible Disclosure Policy][disclosure]. - -[rel-sec]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/security.md#backporting -[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/ -[disclosure]: https://about.gitlab.com/disclosure/ -[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e +See [doc/policy/maintenance.md](doc/policy/maintenance.md) diff --git a/VERSION b/VERSION index 22dd5aa0686..19eac09041d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.1.0-pre +10.2.0-pre diff --git a/app/assets/images/auth_buttons/signin_with_google.png b/app/assets/images/auth_buttons/signin_with_google.png index b1327b4f7b4..f27bb243304 100644 Binary files a/app/assets/images/auth_buttons/signin_with_google.png and b/app/assets/images/auth_buttons/signin_with_google.png differ diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 38d1effc77c..242b3e2b990 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -15,6 +15,7 @@ const Api = { issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', usersPath: '/api/:version/users.json', commitPath: '/api/:version/projects/:id/repository/commits', + branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath) @@ -123,6 +124,19 @@ const Api = { }); }, + branchSingle(id, branch) { + const url = Api.buildUrl(Api.branchSinglePath) + .replace(':id', id) + .replace(':branch', branch); + + return this.wrapAjaxCall({ + url, + type: 'GET', + contentType: 'application/json; charset=utf-8', + dataType: 'json', + }); + }, + // Return text for a specific license licenseText(key, data, callback) { const url = Api.buildUrl(Api.licensePath) diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index ddd1fea3aca..0d590a9dbc4 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,6 +1,5 @@ /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ -/* global Dropzone */ - +import Dropzone from 'dropzone'; import '../lib/utils/url_utility'; import { HIDDEN_CLASS } from '../lib/utils/constants'; import csrf from '../lib/utils/csrf'; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 7f3afefc9cc..c1f902a785a 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -9,6 +9,7 @@ import Flash from '../../flash'; import eventHub from '../../sidebar/event_hub'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import Assignees from '../../sidebar/components/assignees/assignees'; +import DueDateSelectors from '../../due_date_select'; import './sidebar/remove_issue'; const Store = gl.issueBoards.BoardsStore; @@ -113,7 +114,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ mounted () { new IssuableContext(this.currentUser); new MilestoneSelect(); - new gl.DueDateSelectors(); + new DueDateSelectors(); new LabelsSelect(); new Sidebar(); gl.Subscription.bindAll('.subscription'); diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index 38eea38f949..97e80afa3f8 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -7,7 +7,7 @@ class BoardService { this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, { issues: { method: 'GET', - url: `${gon.relative_url_root}/boards/${boardId}/issues.json`, + url: `${gon.relative_url_root}/-/boards/${boardId}/issues.json`, } }); this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, { @@ -16,7 +16,7 @@ class BoardService { url: `${listsEndpoint}/generate.json` } }); - this.issue = Vue.resource(`${gon.relative_url_root}/boards/${boardId}/issues{/id}`, {}); + this.issue = Vue.resource(`${gon.relative_url_root}/-/boards/${boardId}/issues{/id}`, {}); this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, { bulkUpdate: { method: 'POST', diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js index f73e489e7b2..ff88083a4b4 100644 --- a/app/assets/javascripts/broadcast_message.js +++ b/app/assets/javascripts/broadcast_message.js @@ -1,33 +1,28 @@ -/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */ +export default function initBroadcastMessagesForm() { + $('input#broadcast_message_color').on('input', function onMessageColorInput() { + const previewColor = $(this).val(); + $('div.broadcast-message-preview').css('background-color', previewColor); + }); -$(function() { - var previewPath; - $('input#broadcast_message_color').on('input', function() { - var previewColor; - previewColor = $(this).val(); - return $('div.broadcast-message-preview').css('background-color', previewColor); + $('input#broadcast_message_font').on('input', function onMessageFontInput() { + const previewColor = $(this).val(); + $('div.broadcast-message-preview').css('color', previewColor); }); - $('input#broadcast_message_font').on('input', function() { - var previewColor; - previewColor = $(this).val(); - return $('div.broadcast-message-preview').css('color', previewColor); - }); - previewPath = $('textarea#broadcast_message_message').data('preview-path'); - return $('textarea#broadcast_message_message').on('input', function() { - var message; - message = $(this).val(); + + const previewPath = $('textarea#broadcast_message_message').data('preview-path'); + + $('textarea#broadcast_message_message').on('input', _.debounce(function onMessageInput() { + const message = $(this).val(); if (message === '') { - return $('.js-broadcast-message-preview').text("Your message here"); + $('.js-broadcast-message-preview').text('Your message here'); } else { - return $.ajax({ + $.ajax({ url: previewPath, - type: "POST", + type: 'POST', data: { - broadcast_message: { - message: message - } - } + broadcast_message: { message }, + }, }); } - }); -}); + }, 250)); +} diff --git a/app/assets/javascripts/clusters.js b/app/assets/javascripts/clusters.js index 50dbeb06362..180aa30e98c 100644 --- a/app/assets/javascripts/clusters.js +++ b/app/assets/javascripts/clusters.js @@ -3,7 +3,8 @@ import Visibility from 'visibilityjs'; import axios from 'axios'; import Poll from './lib/utils/poll'; import { s__ } from './locale'; -import './flash'; +import initSettingsPanels from './settings_panels'; +import Flash from './flash'; /** * Cluster page has 2 separate parts: @@ -24,6 +25,8 @@ class ClusterService { export default class Clusters { constructor() { + initSettingsPanels(); + const dataset = document.querySelector('.js-edit-cluster-form').dataset; this.state = { diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 0661087a1ba..e9a0dbaa59d 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -25,6 +25,11 @@ type: String, required: true, }, + viewType: { + type: String, + required: false, + default: 'child', + }, }, mixins: [ pipelinesMixin, @@ -110,6 +115,7 @@ :pipelines="state.pipelines" :update-graph-dropdown="updateGraphDropdown" :auto-devops-help-path="autoDevopsHelpPath" + :view-type="viewType" /> diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 6c78662baa7..c8874e48c09 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -1,5 +1,3 @@ -/* eslint-disable class-methods-use-this */ - import './lib/utils/url_utility'; import FilesCommentButton from './files_comment_button'; import SingleFileDiff from './single_file_diff'; @@ -8,7 +6,7 @@ import imageDiffHelper from './image_diff/helpers/index'; const UNFOLD_COUNT = 20; let isBound = false; -class Diff { +export default class Diff { constructor() { const $diffFile = $('.files .diff-file'); @@ -104,7 +102,7 @@ class Diff { } this.highlightSelectedLine(); } - + // eslint-disable-next-line class-methods-use-this handleParallelLineDown(e) { const line = $(e.currentTarget); const table = line.closest('table'); @@ -116,11 +114,11 @@ class Diff { table.addClass(`${lineClass}-selected`); } } - + // eslint-disable-next-line class-methods-use-this diffViewType() { return $('.inline-parallel-buttons a.active').data('view-type'); } - + // eslint-disable-next-line class-methods-use-this lineNumbers(line) { const children = line.find('.diff-line-num').toArray(); if (children.length !== 2) { @@ -128,7 +126,7 @@ class Diff { } return children.map(elm => parseInt($(elm).data('linenumber'), 10) || 0); } - + // eslint-disable-next-line class-methods-use-this highlightSelectedLine() { const hash = gl.utils.getLocationHash(); const $diffFiles = $('.diff-file'); @@ -141,6 +139,3 @@ class Diff { } } } - -window.gl = window.gl || {}; -window.gl.Diff = Diff; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 0a653d7fefc..748b1993c56 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,8 +1,6 @@ /* 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 ProjectSelect */ -/* global ShortcutsNavigation */ /* global IssuableIndex */ -/* global ShortcutsIssuable */ /* global Milestone */ /* global IssuableForm */ /* global LabelsSelect */ @@ -10,7 +8,8 @@ /* global NewBranchForm */ /* global NotificationsForm */ /* global NotificationsDropdown */ -/* global GroupAvatar */ +import groupAvatar from './group_avatar'; +import GroupLabelSubscription from './group_label_subscription'; /* global LineHighlighter */ import BuildArtifacts from './build_artifacts'; import CILintEditor from './ci_lint_editor'; @@ -31,10 +30,7 @@ import CILintEditor from './ci_lint_editor'; /* global ProjectImport */ import Labels from './labels'; import LabelManager from './label_manager'; -/* global Shortcuts */ -/* global ShortcutsFindFile */ /* global Sidebar */ -/* global ShortcutsWiki */ import CommitsList from './commits'; import Issue from './issue'; @@ -70,6 +66,7 @@ import initSettingsPanels from './settings_panels'; import initExperimentalFlags from './experimental_flags'; import OAuthRememberMe from './oauth_remember_me'; import PerformanceBar from './performance_bar'; +import initBroadcastMessagesForm from './broadcast_message'; import initNotes from './init_notes'; import initLegacyFilters from './init_legacy_filters'; import initIssuableSidebar from './init_issuable_sidebar'; @@ -77,12 +74,21 @@ import initProjectVisibilitySelector from './project_visibility'; import GpgBadges from './gpg_badges'; import UserFeatureHelper from './helpers/user_feature_helper'; import initChangesDropdown from './init_changes_dropdown'; +import NewGroupChild from './groups/new_group_child'; import AbuseReports from './abuse_reports'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import AjaxLoadingSpinner from './ajax_loading_spinner'; import GlFieldErrors from './gl_field_errors'; import GLForm from './gl_form'; +import Shortcuts from './shortcuts'; +import ShortcutsNavigation from './shortcuts_navigation'; +import ShortcutsFindFile from './shortcuts_find_file'; +import ShortcutsIssuable from './shortcuts_issuable'; import U2FAuthenticate from './u2f/authenticate'; +import Members from './members'; +import memberExpirationDate from './member_expiration_date'; +import DueDateSelectors from './due_date_select'; +import Diff from './diff'; (function() { var Dispatcher; @@ -166,9 +172,6 @@ import U2FAuthenticate from './u2f/authenticate'; const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); filteredSearchManager.setup(); } - if (page === 'projects:merge_requests:index') { - new UserCallout({ setCalloutPerProject: true }); - } const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; IssuableIndex.init(pagePrefix); @@ -232,11 +235,11 @@ import U2FAuthenticate from './u2f/authenticate'; case 'groups:milestones:edit': case 'groups:milestones:update': new ZenMode(); - new gl.DueDateSelectors(); + new DueDateSelectors(); new GLForm($('.milestone-form'), true); break; case 'projects:compare:show': - new gl.Diff(); + new Diff(); initChangesDropdown(); break; case 'projects:branches:new': @@ -272,7 +275,7 @@ import U2FAuthenticate from './u2f/authenticate'; } case 'projects:merge_requests:creations:diffs': case 'projects:merge_requests:edit': - new gl.Diff(); + new Diff(); shortcut_handler = new ShortcutsNavigation(); new GLForm($('.merge-request-form'), true); new IssuableForm($('.merge-request-form')); @@ -306,7 +309,7 @@ import U2FAuthenticate from './u2f/authenticate'; new GLForm($('.release-form'), true); break; case 'projects:merge_requests:show': - new gl.Diff(); + new Diff(); shortcut_handler = new ShortcutsIssuable(true); new ZenMode(); @@ -322,7 +325,7 @@ import U2FAuthenticate from './u2f/authenticate'; new gl.Activities(); break; case 'projects:commit:show': - new gl.Diff(); + new Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); new MiniPipelineGraph({ @@ -350,7 +353,10 @@ import U2FAuthenticate from './u2f/authenticate'; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); - new UserCallout({ setCalloutPerProject: true }); + new UserCallout({ + setCalloutPerProject: true, + className: 'js-autodevops-banner', + }); if ($('#tree-slider').length) new TreeView(); if ($('.blob-viewer').length) new BlobViewer(); @@ -370,9 +376,6 @@ import U2FAuthenticate from './u2f/authenticate'; case 'projects:pipelines:new': new NewBranchForm($('.js-new-pipeline-form')); break; - case 'projects:pipelines:index': - new UserCallout({ setCalloutPerProject: true }); - break; case 'projects:pipelines:builds': case 'projects:pipelines:failures': case 'projects:pipelines:show': @@ -393,21 +396,26 @@ import U2FAuthenticate from './u2f/authenticate'; new gl.Activities(); break; case 'groups:show': + const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); new NotificationsDropdown(); new ProjectsList(); + + if (newGroupChildWrapper) { + new NewGroupChild(newGroupChildWrapper); + } break; case 'groups:group_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); + memberExpirationDate(); + new Members(); new UsersSelect(); break; case 'projects:project_members:index': - new gl.MemberExpirationDate('.js-access-expiration-date-groups'); + memberExpirationDate('.js-access-expiration-date-groups'); new GroupsSelect(); - new gl.MemberExpirationDate(); - new gl.Members(); + memberExpirationDate(); + new Members(); new UsersSelect(); break; case 'groups:new': @@ -416,11 +424,11 @@ import U2FAuthenticate from './u2f/authenticate'; case 'admin:groups:create': BindInOut.initAll(); new Group(); - new GroupAvatar(); + groupAvatar(); break; case 'groups:edit': case 'admin:groups:edit': - new GroupAvatar(); + groupAvatar(); break; case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); @@ -430,7 +438,6 @@ import U2FAuthenticate from './u2f/authenticate'; new TreeView(); new BlobViewer(); new NewCommitForm($('.js-create-dir-form')); - new UserCallout({ setCalloutPerProject: true }); $('#tree-slider').waitForImages(function() { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); @@ -468,7 +475,7 @@ import U2FAuthenticate from './u2f/authenticate'; const $el = $(el); if ($el.find('.dropdown-group-label').length) { - new gl.GroupLabelSubscription($el); + new GroupLabelSubscription($el); } else { new gl.ProjectLabelSubscription($el); } @@ -528,7 +535,7 @@ import U2FAuthenticate from './u2f/authenticate'; break; case 'profiles:personal_access_tokens:index': case 'admin:impersonation_tokens:index': - new gl.DueDateSelectors(); + new DueDateSelectors(); break; case 'projects:clusters:show': import(/* webpackChunkName: "clusters" */ './clusters') @@ -553,6 +560,9 @@ import U2FAuthenticate from './u2f/authenticate'; case 'admin': new Admin(); switch (path[1]) { + case 'broadcast_messages': + initBroadcastMessagesForm(); + break; case 'cohorts': new UsagePing(); break; diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index bd45da8c422..7a17adcd44e 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -1,308 +1,276 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */ -/* global Dropzone */ +import Dropzone from 'dropzone'; import _ from 'underscore'; import './preview_markdown'; import csrf from './lib/utils/csrf'; -window.DropzoneInput = (function() { - function DropzoneInput(form) { - const divHover = '
'; - const iconPaperclip = ''; - const $attachButton = form.find('.button-attach-file'); - const $attachingFileMessage = form.find('.attaching-file-message'); - const $cancelButton = form.find('.button-cancel-uploading-files'); - const $retryLink = form.find('.retry-uploading-link'); - const $uploadProgress = form.find('.uploading-progress'); - const $uploadingErrorContainer = form.find('.uploading-error-container'); - const $uploadingErrorMessage = form.find('.uploading-error-message'); - const $uploadingProgressContainer = form.find('.uploading-progress-container'); - const uploadsPath = window.uploads_path || null; - const maxFileSize = gon.max_file_size || 10; - const formTextarea = form.find('.js-gfm-input'); - let handlePaste; - let pasteText; - let addFileToForm; - let updateAttachingMessage; - let isImage; - let getFilename; - let uploadFile; +export default function dropzoneInput(form) { + const divHover = '
'; + const iconPaperclip = ''; + const $attachButton = form.find('.button-attach-file'); + const $attachingFileMessage = form.find('.attaching-file-message'); + const $cancelButton = form.find('.button-cancel-uploading-files'); + const $retryLink = form.find('.retry-uploading-link'); + const $uploadProgress = form.find('.uploading-progress'); + const $uploadingErrorContainer = form.find('.uploading-error-container'); + const $uploadingErrorMessage = form.find('.uploading-error-message'); + const $uploadingProgressContainer = form.find('.uploading-progress-container'); + const uploadsPath = window.uploads_path || null; + const maxFileSize = gon.max_file_size || 10; + const formTextarea = form.find('.js-gfm-input'); + let handlePaste; + let pasteText; + let addFileToForm; + let updateAttachingMessage; + let isImage; + let getFilename; + let uploadFile; - formTextarea.wrap('
'); - formTextarea.on('paste', (function(_this) { - return function(event) { - return handlePaste(event); - }; - })(this)); + formTextarea.wrap('
'); + formTextarea.on('paste', event => handlePaste(event)); - // Add dropzone area to the form. - const $mdArea = formTextarea.closest('.md-area'); - form.setupMarkdownPreview(); - const $formDropzone = form.find('.div-dropzone'); - $formDropzone.parent().addClass('div-dropzone-wrapper'); - $formDropzone.append(divHover); - $formDropzone.find('.div-dropzone-hover').append(iconPaperclip); + // Add dropzone area to the form. + const $mdArea = formTextarea.closest('.md-area'); + form.setupMarkdownPreview(); + const $formDropzone = form.find('.div-dropzone'); + $formDropzone.parent().addClass('div-dropzone-wrapper'); + $formDropzone.append(divHover); + $formDropzone.find('.div-dropzone-hover').append(iconPaperclip); - if (!uploadsPath) return; + if (!uploadsPath) return; - const dropzone = $formDropzone.dropzone({ - url: uploadsPath, - dictDefaultMessage: '', - clickable: true, - paramName: 'file', - maxFilesize: maxFileSize, - uploadMultiple: false, - headers: csrf.headers, - previewContainer: false, - processing: function() { - return $('.div-dropzone-alert').alert('close'); - }, - dragover: function() { - $mdArea.addClass('is-dropzone-hover'); - form.find('.div-dropzone-hover').css('opacity', 0.7); - }, - dragleave: function() { - $mdArea.removeClass('is-dropzone-hover'); - form.find('.div-dropzone-hover').css('opacity', 0); - }, - drop: function() { - $mdArea.removeClass('is-dropzone-hover'); - form.find('.div-dropzone-hover').css('opacity', 0); - formTextarea.focus(); - }, - success: function(header, response) { - const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length; - const shouldPad = processingFileCount >= 1; + const dropzone = $formDropzone.dropzone({ + url: uploadsPath, + dictDefaultMessage: '', + clickable: true, + paramName: 'file', + maxFilesize: maxFileSize, + uploadMultiple: false, + headers: csrf.headers, + previewContainer: false, + processing: () => $('.div-dropzone-alert').alert('close'), + dragover: () => { + $mdArea.addClass('is-dropzone-hover'); + form.find('.div-dropzone-hover').css('opacity', 0.7); + }, + dragleave: () => { + $mdArea.removeClass('is-dropzone-hover'); + form.find('.div-dropzone-hover').css('opacity', 0); + }, + drop: () => { + $mdArea.removeClass('is-dropzone-hover'); + form.find('.div-dropzone-hover').css('opacity', 0); + formTextarea.focus(); + }, + success(header, response) { + const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length; + const shouldPad = processingFileCount >= 1; - pasteText(response.link.markdown, shouldPad); - // Show 'Attach a file' link only when all files have been uploaded. - if (!processingFileCount) $attachButton.removeClass('hide'); - addFileToForm(response.link.url); - }, - error: function(file, errorMessage = 'Attaching the file failed.', xhr) { - // If 'error' event is fired by dropzone, the second parameter is error message. - // If the 'errorMessage' parameter is empty, the default error message is set. - // If the 'error' event is fired by backend (xhr) error response, the third parameter is - // xhr object (xhr.responseText is error message). - // On error we hide the 'Attach' and 'Cancel' buttons - // and show an error. + pasteText(response.link.markdown, shouldPad); + // Show 'Attach a file' link only when all files have been uploaded. + if (!processingFileCount) $attachButton.removeClass('hide'); + addFileToForm(response.link.url); + }, + error: (file, errorMessage = 'Attaching the file failed.', xhr) => { + // If 'error' event is fired by dropzone, the second parameter is error message. + // If the 'errorMessage' parameter is empty, the default error message is set. + // If the 'error' event is fired by backend (xhr) error response, the third parameter is + // xhr object (xhr.responseText is error message). + // On error we hide the 'Attach' and 'Cancel' buttons + // and show an error. - // If there's xhr error message, let's show it instead of dropzone's one. - const message = xhr ? xhr.responseText : errorMessage; + // If there's xhr error message, let's show it instead of dropzone's one. + const message = xhr ? xhr.responseText : errorMessage; - $uploadingErrorContainer.removeClass('hide'); - $uploadingErrorMessage.html(message); - $attachButton.addClass('hide'); - $cancelButton.addClass('hide'); - }, - totaluploadprogress: function(totalUploadProgress) { - updateAttachingMessage(this.files, $attachingFileMessage); - $uploadProgress.text(Math.round(totalUploadProgress) + '%'); - }, - sending: function(file) { - // DOM elements already exist. - // Instead of dynamically generating them, - // we just either hide or show them. - $attachButton.addClass('hide'); - $uploadingErrorContainer.addClass('hide'); - $uploadingProgressContainer.removeClass('hide'); - $cancelButton.removeClass('hide'); - }, - removedfile: function() { - $attachButton.removeClass('hide'); - $cancelButton.addClass('hide'); - $uploadingProgressContainer.addClass('hide'); - $uploadingErrorContainer.addClass('hide'); - }, - queuecomplete: function() { - $('.dz-preview').remove(); - $('.markdown-area').trigger('input'); - - $uploadingProgressContainer.addClass('hide'); - $cancelButton.addClass('hide'); - } - }); - - const child = $(dropzone[0]).children('textarea'); - - // removeAllFiles(true) stops uploading files (if any) - // and remove them from dropzone files queue. - $cancelButton.on('click', (e) => { - const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone'); - - e.preventDefault(); - e.stopPropagation(); - Dropzone.forElement(target).removeAllFiles(true); - }); - - // If 'error' event is fired, we store a failed files, - // clear dropzone files queue, change status of failed files to undefined, - // and add that files to the dropzone files queue again. - // addFile() adds file to dropzone files queue and upload it. - $retryLink.on('click', (e) => { - const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone')); - const failedFiles = dropzoneInstance.files; - - e.preventDefault(); - - // 'true' parameter of removeAllFiles() cancels uploading of files that are being uploaded at the moment. - dropzoneInstance.removeAllFiles(true); - - failedFiles.map((failedFile, i) => { - const file = failedFile; - - if (file.status === Dropzone.ERROR) { - file.status = undefined; - file.accepted = undefined; - } - - return dropzoneInstance.addFile(file); - }); - }); - - handlePaste = function(event) { - var filename, image, pasteEvent, text; - pasteEvent = event.originalEvent; - if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) { - image = isImage(pasteEvent); - if (image) { - event.preventDefault(); - filename = getFilename(pasteEvent) || 'image.png'; - text = `{{${filename}}}`; - pasteText(text); - return uploadFile(image.getAsFile(), filename); - } - } - }; - - isImage = function(data) { - var i, item; - i = 0; - while (i < data.clipboardData.items.length) { - item = data.clipboardData.items[i]; - if (item.type.indexOf('image') !== -1) { - return item; - } - i += 1; - } - return false; - }; - - pasteText = function(text, shouldPad) { - var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; - var formattedText = text; - if (shouldPad) formattedText += "\n\n"; - const textarea = child.get(0); - caretStart = textarea.selectionStart; - caretEnd = textarea.selectionEnd; - textEnd = $(child).val().length; - beforeSelection = $(child).val().substring(0, caretStart); - afterSelection = $(child).val().substring(caretEnd, textEnd); - $(child).val(beforeSelection + formattedText + afterSelection); - textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); - textarea.style.height = `${textarea.scrollHeight}px`; - formTextarea.get(0).dispatchEvent(new Event('input')); - return formTextarea.trigger('input'); - }; - - addFileToForm = function(path) { - $(form).append(''); - }; - - getFilename = function(e) { - var value; - if (window.clipboardData && window.clipboardData.getData) { - value = window.clipboardData.getData('Text'); - } else if (e.clipboardData && e.clipboardData.getData) { - value = e.clipboardData.getData('text/plain'); - } - value = value.split("\r"); - return value[0]; - }; - - const showSpinner = function(e) { - return $uploadingProgressContainer.removeClass('hide'); - }; - - const closeSpinner = function() { - return $uploadingProgressContainer.addClass('hide'); - }; - - const showError = function(message) { $uploadingErrorContainer.removeClass('hide'); $uploadingErrorMessage.html(message); - }; + $attachButton.addClass('hide'); + $cancelButton.addClass('hide'); + }, + totaluploadprogress(totalUploadProgress) { + updateAttachingMessage(this.files, $attachingFileMessage); + $uploadProgress.text(`${Math.round(totalUploadProgress)}%`); + }, + sending: () => { + // DOM elements already exist. + // Instead of dynamically generating them, + // we just either hide or show them. + $attachButton.addClass('hide'); + $uploadingErrorContainer.addClass('hide'); + $uploadingProgressContainer.removeClass('hide'); + $cancelButton.removeClass('hide'); + }, + removedfile: () => { + $attachButton.removeClass('hide'); + $cancelButton.addClass('hide'); + $uploadingProgressContainer.addClass('hide'); + $uploadingErrorContainer.addClass('hide'); + }, + queuecomplete: () => { + $('.dz-preview').remove(); + $('.markdown-area').trigger('input'); - const closeAlertMessage = function() { - return form.find('.div-dropzone-alert').alert('close'); - }; + $uploadingProgressContainer.addClass('hide'); + $cancelButton.addClass('hide'); + }, + }); - const insertToTextArea = function(filename, url) { - const $child = $(child); - $child.val(function(index, val) { - return val.replace(`{{${filename}}}`, url); - }); + const child = $(dropzone[0]).children('textarea'); - $child.trigger('change'); - }; + // removeAllFiles(true) stops uploading files (if any) + // and remove them from dropzone files queue. + $cancelButton.on('click', (e) => { + const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone'); - const appendToTextArea = function(url) { - return $(child).val(function(index, val) { - return val + url + "\n"; - }); - }; + e.preventDefault(); + e.stopPropagation(); + Dropzone.forElement(target).removeAllFiles(true); + }); - uploadFile = function(item, filename) { - var formData; - formData = new FormData(); - formData.append('file', item, filename); - return $.ajax({ - url: uploadsPath, - type: 'POST', - data: formData, - dataType: 'json', - processData: false, - contentType: false, - headers: csrf.headers, - beforeSend: function() { - showSpinner(); - return closeAlertMessage(); - }, - success: function(e, textStatus, response) { - return insertToTextArea(filename, response.responseJSON.link.markdown); - }, - error: function(response) { - return showError(response.responseJSON.message); - }, - complete: function() { - return closeSpinner(); - } - }); - }; + // If 'error' event is fired, we store a failed files, + // clear dropzone files queue, change status of failed files to undefined, + // and add that files to the dropzone files queue again. + // addFile() adds file to dropzone files queue and upload it. + $retryLink.on('click', (e) => { + const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone')); + const failedFiles = dropzoneInstance.files; - updateAttachingMessage = (files, messageContainer) => { - let attachingMessage; - const filesCount = files.filter(function(file) { - return file.status === 'uploading' || - file.status === 'queued'; - }).length; + e.preventDefault(); - // Dinamycally change uploading files text depending on files number in - // dropzone files queue. - if (filesCount > 1) { - attachingMessage = 'Attaching ' + filesCount + ' files -'; - } else { - attachingMessage = 'Attaching a file -'; + // 'true' parameter of removeAllFiles() cancels + // uploading of files that are being uploaded at the moment. + dropzoneInstance.removeAllFiles(true); + + failedFiles.map((failedFile) => { + const file = failedFile; + + if (file.status === Dropzone.ERROR) { + file.status = undefined; + file.accepted = undefined; } - messageContainer.text(attachingMessage); - }; - - form.find('.markdown-selector').click(function(e) { - e.preventDefault(); - $(this).closest('.gfm-form').find('.div-dropzone').click(); - formTextarea.focus(); + return dropzoneInstance.addFile(file); }); - } + }); + // eslint-disable-next-line consistent-return + handlePaste = (event) => { + const pasteEvent = event.originalEvent; + if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) { + const image = isImage(pasteEvent); + if (image) { + event.preventDefault(); + const filename = getFilename(pasteEvent) || 'image.png'; + const text = `{{${filename}}}`; + pasteText(text); + return uploadFile(image.getAsFile(), filename); + } + } + }; - return DropzoneInput; -})(); + isImage = (data) => { + let i = 0; + while (i < data.clipboardData.items.length) { + const item = data.clipboardData.items[i]; + if (item.type.indexOf('image') !== -1) { + return item; + } + i += 1; + } + return false; + }; + + pasteText = (text, shouldPad) => { + let formattedText = text; + if (shouldPad) { + formattedText += '\n\n'; + } + const textarea = child.get(0); + const caretStart = textarea.selectionStart; + const caretEnd = textarea.selectionEnd; + const textEnd = $(child).val().length; + const beforeSelection = $(child).val().substring(0, caretStart); + const afterSelection = $(child).val().substring(caretEnd, textEnd); + $(child).val(beforeSelection + formattedText + afterSelection); + textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); + textarea.style.height = `${textarea.scrollHeight}px`; + formTextarea.get(0).dispatchEvent(new Event('input')); + return formTextarea.trigger('input'); + }; + + addFileToForm = (path) => { + $(form).append(``); + }; + + getFilename = (e) => { + let value; + if (window.clipboardData && window.clipboardData.getData) { + value = window.clipboardData.getData('Text'); + } else if (e.clipboardData && e.clipboardData.getData) { + value = e.clipboardData.getData('text/plain'); + } + value = value.split('\r'); + return value[0]; + }; + + const showSpinner = () => $uploadingProgressContainer.removeClass('hide'); + + const closeSpinner = () => $uploadingProgressContainer.addClass('hide'); + + const showError = (message) => { + $uploadingErrorContainer.removeClass('hide'); + $uploadingErrorMessage.html(message); + }; + + const closeAlertMessage = () => form.find('.div-dropzone-alert').alert('close'); + + const insertToTextArea = (filename, url) => { + const $child = $(child); + $child.val((index, val) => val.replace(`{{${filename}}}`, url)); + + $child.trigger('change'); + }; + + uploadFile = (item, filename) => { + const formData = new FormData(); + formData.append('file', item, filename); + return $.ajax({ + url: uploadsPath, + type: 'POST', + data: formData, + dataType: 'json', + processData: false, + contentType: false, + headers: csrf.headers, + beforeSend: () => { + showSpinner(); + return closeAlertMessage(); + }, + success: (e, text, response) => { + const md = response.responseJSON.link.markdown; + insertToTextArea(filename, md); + }, + error: response => showError(response.responseJSON.message), + complete: () => closeSpinner(), + }); + }; + + updateAttachingMessage = (files, messageContainer) => { + let attachingMessage; + const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length; + + // Dinamycally change uploading files text depending on files number in + // dropzone files queue. + if (filesCount > 1) { + attachingMessage = `Attaching ${filesCount} files -`; + } else { + attachingMessage = 'Attaching a file -'; + } + + messageContainer.text(attachingMessage); + }; + + form.find('.markdown-selector').click(function onMarkdownClick(e) { + e.preventDefault(); + $(this).closest('.gfm-form').find('.div-dropzone').click(); + formTextarea.focus(); + }); +} diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index ee71728184f..ada985913bb 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -1,8 +1,7 @@ -/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */ /* global dateFormat */ import Pikaday from 'pikaday'; -import DateFix from './lib/utils/datefix'; +import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; class DueDateSelect { constructor({ $dropdown, $loading } = {}) { @@ -17,8 +16,8 @@ class DueDateSelect { this.$value = $block.find('.value'); this.$valueContent = $block.find('.value-content'); this.$sidebarValue = $('.js-due-date-sidebar-value', $block); - this.fieldName = $dropdown.data('field-name'), - this.abilityName = $dropdown.data('ability-name'), + this.fieldName = $dropdown.data('field-name'); + this.abilityName = $dropdown.data('ability-name'); this.issueUpdateURL = $dropdown.data('issue-update'); this.rawSelectedDate = null; @@ -39,20 +38,20 @@ class DueDateSelect { hidden: () => { this.$selectbox.hide(); this.$value.css('display', ''); - } + }, }); } initDatePicker() { const $dueDateInput = $(`input[name='${this.fieldName}']`); - const dateFix = DateFix.dashedFix($dueDateInput.val()); const calendar = new Pikaday({ field: $dueDateInput.get(0), theme: 'gitlab-theme', format: 'yyyy-mm-dd', + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), onSelect: (dateText) => { - const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd'); - $dueDateInput.val(formattedDate); + $dueDateInput.val(calendar.toString(dateText)); if (this.$dropdown.hasClass('js-issue-boards-due-date')) { gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val(); @@ -60,10 +59,10 @@ class DueDateSelect { } else { this.saveDueDate(true); } - } + }, }); - calendar.setDate(dateFix); + calendar.setDate(parsePikadayDate($dueDateInput.val())); this.$datePicker.append(calendar.el); this.$datePicker.data('pikaday', calendar); } @@ -79,8 +78,8 @@ class DueDateSelect { gl.issueBoards.BoardsStore.detail.issue.dueDate = ''; this.updateIssueBoardIssue(); } else { - $("input[name='" + this.fieldName + "']").val(''); - return this.saveDueDate(false); + $(`input[name='${this.fieldName}']`).val(''); + this.saveDueDate(false); } }); } @@ -111,7 +110,7 @@ class DueDateSelect { this.datePayload = datePayload; } - updateIssueBoardIssue () { + updateIssueBoardIssue() { this.$loading.fadeIn(); this.$dropdown.trigger('loading.gl.dropdown'); this.$selectbox.hide(); @@ -149,8 +148,8 @@ class DueDateSelect { return selectedDateValue.length ? $('.js-remove-due-date-holder').removeClass('hidden') : $('.js-remove-due-date-holder').addClass('hidden'); - } - }).done((data) => { + }, + }).done(() => { if (isDropdown) { this.$dropdown.trigger('loaded.gl.dropdown'); this.$dropdown.dropdown('toggle'); @@ -160,27 +159,28 @@ class DueDateSelect { } } -class DueDateSelectors { +export default class DueDateSelectors { constructor() { this.initMilestoneDatePicker(); this.initIssuableSelect(); } - + // eslint-disable-next-line class-methods-use-this initMilestoneDatePicker() { - $('.datepicker').each(function() { + $('.datepicker').each(function initPikadayMilestone() { const $datePicker = $(this); - const dateFix = DateFix.dashedFix($datePicker.val()); const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', format: 'yyyy-mm-dd', container: $datePicker.parent().get(0), + parse: dateString => parsePikadayDate(dateString), + toString: date => pikadayToString(date), onSelect(dateText) { - $datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); - } + $datePicker.val(calendar.toString(dateText)); + }, }); - calendar.setDate(dateFix); + calendar.setDate(parsePikadayDate($datePicker.val())); $datePicker.data('pikaday', calendar); }); @@ -191,19 +191,17 @@ class DueDateSelectors { calendar.setDate(null); }); } - + // eslint-disable-next-line class-methods-use-this initIssuableSelect() { const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); $('.js-due-date-select').each((i, dropdown) => { const $dropdown = $(dropdown); + // eslint-disable-next-line no-new new DueDateSelect({ $dropdown, - $loading + $loading, }); }); } } - -window.gl = window.gl || {}; -window.gl.DueDateSelectors = DueDateSelectors; diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index a00d29a845a..90020344748 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -1,6 +1,3 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */ -/* global notes */ - /* Developer beware! Do not add logic to showButton or hideButton * that will force a reflow. Doing so will create a signficant performance * bottleneck for pages with large diffs. For a comprehensive list of what @@ -20,8 +17,10 @@ const DIFF_EXPANDED_CLASS = 'diff-expanded'; export default { init($diffFile) { - /* Caching is used only when the following members are *true*. This is because there are likely to be - * differently configured versions of diffs in the same session. However if these values are true, they + /* Caching is used only when the following members are *true*. + * This is because there are likely to be + * differently configured versions of diffs in the same session. + * However if these values are true, they * will be true in all cases */ if (!this.userCanCreateNote) { diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js index 6d516a253bb..9e91f72b2ea 100644 --- a/app/assets/javascripts/filterable_list.js +++ b/app/assets/javascripts/filterable_list.js @@ -6,10 +6,11 @@ import _ from 'underscore'; */ export default class FilterableList { - constructor(form, filter, holder) { + constructor(form, filter, holder, filterInputField = 'filter_groups') { this.filterForm = form; this.listFilterElement = filter; this.listHolderElement = holder; + this.filterInputField = filterInputField; this.isBusy = false; } @@ -32,10 +33,10 @@ export default class FilterableList { onFilterInput() { const $form = $(this.filterForm); const queryData = {}; - const filterGroupsParam = $form.find('[name="filter_groups"]').val(); + const filterGroupsParam = $form.find(`[name="${this.filterInputField}"]`).val(); if (filterGroupsParam) { - queryData.filter_groups = filterGroupsParam; + queryData[this.filterInputField] = filterGroupsParam; } this.filterResults(queryData); diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index dd24fc44d2a..d2f92929b8a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -123,8 +123,8 @@ class FilteredSearchVisualTokens { /* eslint-disable no-param-reassign */ tokenValueContainer.dataset.originalValue = tokenValue; tokenValueElement.innerHTML = ` - ${user.name}'s avatar - ${user.name} + + ${_.escape(user.name)} `; /* eslint-enable no-param-reassign */ }) diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index bc5cd818e1c..67261c1c9b4 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -40,6 +40,10 @@ const createFlashEl = (message, type, isInContentWrapper = false) => ` `; +const removeFlashClickListener = (flashEl, fadeTransition) => { + flashEl.parentNode.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); +}; + /* * Flash banner supports different types of Flash configurations * along with ability to provide actionConfig which can be used to show @@ -70,7 +74,7 @@ const createFlash = function createFlash( flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper); const flashEl = flashContainer.querySelector(`.flash-${type}`); - flashEl.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); + removeFlashClickListener(flashEl, fadeTransition); if (actionConfig) { flashEl.innerHTML += createAction(actionConfig); @@ -90,5 +94,6 @@ export { createFlashEl, createAction, hideFlash, + removeFlashClickListener, }; window.Flash = createFlash; diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index 48d0c12143a..48cd43d3348 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -1,7 +1,7 @@ -/* global DropzoneInput */ /* global autosize */ import GfmAutoComplete from './gfm_auto_complete'; +import dropzoneInput from './dropzone_input'; export default class GLForm { constructor(form, enableGFM = false) { @@ -41,7 +41,7 @@ export default class GLForm { mergeRequests: this.enableGFM, labels: this.enableGFM, }); - new DropzoneInput(this.form); // eslint-disable-line no-new + dropzoneInput(this.form); autosize(this.textarea); } // form and textarea event listeners diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js index f03b47b1c1d..2168ff3a8ba 100644 --- a/app/assets/javascripts/group_avatar.js +++ b/app/assets/javascripts/group_avatar.js @@ -1,19 +1,12 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */ - -window.GroupAvatar = (function() { - function GroupAvatar() { - $('.js-choose-group-avatar-button').on("click", function() { - var form; - form = $(this).closest("form"); - return form.find(".js-group-avatar-input").click(); - }); - $('.js-group-avatar-input').on("change", function() { - var filename, form; - form = $(this).closest("form"); - filename = $(this).val().replace(/^.*[\\\/]/, ''); - return form.find(".js-avatar-filename").text(filename); - }); - } - - return GroupAvatar; -})(); +export default function groupAvatar() { + $('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() { + const form = $(this).closest('form'); + return form.find('.js-group-avatar-input').click(); + }); + $('.js-group-avatar-input').on('change', function onChangeAvatarInput() { + const form = $(this).closest('form'); + // eslint-disable-next-line no-useless-escape + const filename = $(this).val().replace(/^.*[\\\/]/, ''); + return form.find('.js-avatar-filename').text(filename); + }); +} diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js index 7dc9ce898e8..befaebb635e 100644 --- a/app/assets/javascripts/group_label_subscription.js +++ b/app/assets/javascripts/group_label_subscription.js @@ -1,6 +1,4 @@ -/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */ - -class GroupLabelSubscription { +export default class GroupLabelSubscription { constructor(container) { const $container = $(container); this.$dropdown = $container.find('.dropdown'); @@ -18,7 +16,7 @@ class GroupLabelSubscription { $.ajax({ type: 'POST', - url: url + url, }).done(() => { this.toggleSubscriptionButtons(); this.$unsubscribeButtons.removeAttr('data-url'); @@ -35,7 +33,7 @@ class GroupLabelSubscription { $.ajax({ type: 'POST', - url: url + url, }).done(() => { this.toggleSubscriptionButtons(); }); @@ -47,6 +45,3 @@ class GroupLabelSubscription { this.$unsubscribeButtons.toggleClass('hidden'); } } - -window.gl = window.gl || {}; -window.gl.GroupLabelSubscription = GroupLabelSubscription; diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue new file mode 100644 index 00000000000..2c0b6ab4ea8 --- /dev/null +++ b/app/assets/javascripts/groups/components/app.vue @@ -0,0 +1,194 @@ + + + diff --git a/app/assets/javascripts/groups/components/group_folder.vue b/app/assets/javascripts/groups/components/group_folder.vue index 7cc6c4b0359..e60221fa08d 100644 --- a/app/assets/javascripts/groups/components/group_folder.vue +++ b/app/assets/javascripts/groups/components/group_folder.vue @@ -1,15 +1,27 @@ @@ -20,8 +32,20 @@ export default { v-for="(group, index) in groups" :key="index" :group="group" - :base-group="baseGroup" - :collection="groups" + :parent-group="parentGroup" /> +
  • + +