diff --git a/.flayignore b/.flayignore index acac0ce14c91ade3bd981f6e3c4bcf0774de5151..d6bd96e393d39a7aabe8c2521c6af6ce49a3fc98 100644 --- a/.flayignore +++ b/.flayignore @@ -1,8 +1,15 @@ *.erb lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/diff/position_tracer.rb +app/controllers/projects/approver_groups_controller.rb +app/controllers/projects/approvers_controller.rb +app/controllers/projects/protected_branches/merge_access_levels_controller.rb +app/controllers/projects/protected_branches/push_access_levels_controller.rb +app/controllers/projects/protected_tags/create_access_levels_controller.rb app/policies/project_policy.rb app/models/concerns/relative_positioning.rb app/workers/stuck_merge_jobs_worker.rb lib/gitlab/redis/*.rb lib/gitlab/gitaly_client/operation_service.rb +app/models/project_services/packagist_service.rb +lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb diff --git a/.gitignore b/.gitignore index 4933575332b428cea2ba15854db5a0d835584c75..1478686b31ebddd0baccd19e6e898dcfc7cd49a6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ eslint-report.html /backups/* /config/aws.yml /config/database.yml +/config/database_geo.yml /config/gitlab.yml /config/gitlab_ci.yml /config/initializers/rack_attack.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 19540e4331ec2678c2b398cd70e1e810acd52c3a..ad7c335e08c7772b99fcc2e6dd75e21ce69326f5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git variables: MYSQL_ALLOW_EMPTY_PASSWORD: "1" + ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200" RAILS_ENV: "test" NODE_ENV: "test" SIMPLECOV: "true" @@ -27,6 +28,8 @@ variables: KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json + # This hack is needed to make ES not that memory hungry + ES_JAVA_OPTS: "-Xms256m -Xmx256m" before_script: - bundle --version @@ -63,11 +66,19 @@ stages: services: - postgres:9.2 - redis:alpine + - docker.elastic.co/elasticsearch/elasticsearch:5.5.2 + +.use-pg-9-6-no-elasticsearch: &use-pg-9-6-no-elasticsearch + services: + - postgres:9.6 + - redis:alpine .use-mysql: &use-mysql services: - mysql:latest - redis:alpine + - docker.elastic.co/elasticsearch/elasticsearch:5.5.2 + # Skip all jobs except the ones that begin with 'docs/'. # Used for commits including ONLY documentation changes. @@ -110,6 +121,17 @@ stages: <<: *rspec-metadata <<: *use-pg +.rspec-geo-pg-9-6: &rspec-metadata-pg-geo + <<: *rspec-metadata + <<: *use-pg-9-6-no-elasticsearch + stage: test + script: + - export NO_KNAPSACK=1 + - export CACHE_CLASSES=true + - source scripts/prepare_postgres_fdw.sh + - scripts/gitaly-test-spawn + - bundle exec rspec --color --format documentation --tag geo spec/ + .rspec-metadata-mysql: &rspec-metadata-mysql <<: *rspec-metadata <<: *use-mysql @@ -294,6 +316,8 @@ setup-test-env: - public/assets - tmp/tests +rspec-pg geo: *rspec-metadata-pg-geo + rspec-pg 0 26: *rspec-metadata-pg rspec-pg 1 26: *rspec-metadata-pg rspec-pg 2 26: *rspec-metadata-pg @@ -416,6 +440,7 @@ ee_compat_check: - master - tags - /^[\d-]+-stable(-ee)?/ + - /^security-/ - branches@gitlab-org/gitlab-ee - branches@gitlab/gitlab-ee retry: 0 @@ -452,7 +477,7 @@ db:migrate:reset-mysql: SETUP_DB: "false" CREATE_DB_USER: "true" script: - - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0 + - git fetch https://gitlab.com/gitlab-org/gitlab-ee.git v9.3.0-ee - git checkout -f FETCH_HEAD - bundle install $BUNDLE_INSTALL_FLAGS - cp config/gitlab.yml.example config/gitlab.yml @@ -518,7 +543,7 @@ db:check-schema-pg: <<: *db-migrate-reset <<: *use-pg script: - - source scripts/schema_changed.sh + - sh scripts/schema_changed.sh # Frontend-related jobs gitlab:assets:compile: diff --git a/.license_encryption_key.pub b/.license_encryption_key.pub new file mode 100644 index 0000000000000000000000000000000000000000..68f241b9741696e3c2e49ba5c9a1b0726fc2f727 --- /dev/null +++ b/.license_encryption_key.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Hxv3MkkZbMrKtIs6np9 +ccP4OwGBkNhIvhPjcQP48hbbascv5RqsOquQGrYSD2ZrE/kbkRdkIcoHEeTZLif+ +bDKFZFI7o5x0H92o9/GSvxHJhQ8mkmvwxD7lssGShwZEm8WG+U7BZqUV/gGmCDqe +9W8H8Fq2B0ck8IXjbQ4Zz+JlyV/NHZTZcs69plFiLKh4N6GYVftOVwSomh0bbypP +OB9WnLC7RC9a2LRrhtf8sqa2rRFmtyMMfgFFzLMzS+w+1K4+QLnWP1gKQVzaFnzk +pnwKPrqbGFYbRztIVEWbs8jPYlLkGb8ME4C84YVtQgbQcbyisU/VW3wUGkhT+J0k +xwIDAQAB +-----END PUBLIC KEY----- diff --git a/.rubocop.yml b/.rubocop.yml index c427f219a0df71f8fa29744138ae307703671a22..3230ac414e266b792b0eddc589e93aa46a7942f7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -22,6 +22,7 @@ AllCops: - 'node_modules/**/*' - 'db/*' - 'db/fixtures/**/*' + - 'db/geo/*' - 'tmp/**/*' - 'bin/**/*' - 'generator_templates/**/*' @@ -465,6 +466,7 @@ Style/MutableConstant: Exclude: - 'db/migrate/**/*' - 'db/post_migrate/**/*' + - 'db/geo/migrate/**/*' # Favor unless over if for negative conditions (or control flow or). Style/NegatedIf: @@ -1078,6 +1080,9 @@ RSpec/EmptyExampleGroup: Enabled: true CustomIncludeMethods: - run_permission_checks + - run_group_permission_checks + - it_should_email! + - it_should_not_email! # Checks for long example. RSpec/ExampleLength: @@ -1197,6 +1202,9 @@ GitlabSecurity/DeepMunge: GitlabSecurity/JsonSerialization: Enabled: false +Gitlab/HTTParty: + Enabled: true + GitlabSecurity/PublicSend: Enabled: true Exclude: @@ -1224,3 +1232,6 @@ GitlabSecurity/SystemCommandInjection: Exclude: - 'lib/**/*.rake' - 'spec/**/*' + +Gitlab/HTTParty: + Enabled: true diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md new file mode 100644 index 0000000000000000000000000000000000000000..c6ad576e482cfb6513e011a14b4cb554f9aa7566 --- /dev/null +++ b/CHANGELOG-EE.md @@ -0,0 +1,2146 @@ +Please view this file on the master branch, on stable branches it's out of date. + +## 10.3.9 (2018-03-16) + +- No changes. + +## 10.3.8 (2018-03-01) + +### Security (2 changes) + +- Project can no longer be shared between groups when both member and group locks are active. +- Prevent new push rules from using non-RE2 regexes. + +### Fixed (1 change) + +- Fix LDAP group sync no longer configurable for regular users. + + +## 10.3.7 (2018-02-05) + +### Security (1 change) + +- Restrict LDAP API to admins only. + +### Fixed (1 change) + +- Fix JavaScript bundle running on Cluster update/destroy pages. + + +## 10.3.6 (2018-01-22) + +### Fixed (3 changes) + +- Geo - Fix repository clean up when selective replication changes with hashed storage enabled. !4059 +- Fix JavaScript bundle running on Cluster update/destroy pages. !4112 +- Fix export to CSV if a filter with multiple labels is used. + + +## 10.3.5 (2018-01-18) + +- No changes. + +## 10.3.4 (2018-01-10) + +### Security (2 changes) + +- Fix LDAP external user/group bug on first sign in. +- Deny persisting milestones from outside project/group scope on boards. + + +## 10.3.3 (2018-01-02) + +- No changes. + +## 10.3.2 (2017-12-28) + +- No changes. + +## 10.3.1 (2017-12-27) + +### Changed (1 change) + +- Geo: Show sync percent on bar graph and count within tooltips. !3794 + + +## 10.3.0 (2017-12-22) + +### Removed (2 changes) + +- Remove the full-scan option from the Geo log cursor. !3412 +- Remove Geo SSH repo sync support. !3553 + +### Fixed (14 changes) + +- Hide Approvals section when Merge Request Widget is showing the empty state. !3376 +- Fix error when entering an invalid url to push to or pull from a remote repository. !3389 +- Update gitlab.yml.example to match the default settings for Geo sync workers. !3488 +- Remove duplicate read-only flash message on admin pages. !3495 +- Strip leading & trailing whitespaces in CI/CD secret variable's environment scope. !3563 +- Fix Advanced Search Syntax documentation. !3571 +- Fix Git message when pushing to Geo secondary. !3616 +- Fix a bug in the Geo metrics update service. !3623 +- Fix validation of environment scope for Ci::Variable. !3641 +- Fix an exception in Geo scheduler workers. !3740 +- Fix Merge Request Widget Approvals responsiveness on mobile. +- Geo - Does not sync repositories on unhealthy shards in non-backfill conditions. +- Record EE Ultimate usage pings correctly. +- Fix board filter for predefined milestones. + +### Changed (4 changes) + +- Improve Geo logging of repository errors. !3402 +- ProtectedBranches API allows individual users and group to be specified. !3516 +- EE Protected Branches API access levels include user_id/group_id where relevant. !3535 +- Enhancements for Geo admin screen. !3545 + +### Performance (1 change) + +- Geo - Improve performance when calculating the node status. !3595 + +### Added (20 changes) + +- Show SAST results in MR widget. !3207 +- Add option for projects to only mirror protected branches. !3326 +- Add option to remote mirrors to only push protected branches. !3350 +- Add warning when Geo is configured insecurely. !3368 +- Added enpoint that triggers the pull mirroring process. !3453 +- Add performance metrics to the merge request widget. !3507 +- Geo: replicate Attachments migration to Hashed Storage in secondary node. !3544 +- View, add, and edit weight on Issue from the Issue Board contextual sidebar. !3566 +- Decrease scheduling delay and add rate limiting to push mirror. !3575 +- Allow admins to disable mirroring. !3586 +- Support multiple Kubernetes cluster per project. !3603 +- Geo: Increase parallelism by scheduling project repositories by shard. !3606 +- Geo: rake task to refresh foreign table schema (FDW support). !3626 +- Support mentioning epics. +- Handle outdated replicas in the DB load balancer. +- Add geo:set_secondary_as_primary rake task. +- Transfer job archives to object storage after creation. +- Geo - Show GitLab version for each node in the Geo status page. +- Add epic information to issue sidebar. +- Add system notes for issue - epic association. + +### Other (3 changes) + +- Add fade mask to the bottom of the boards selector dropdown list if it can be scrolled down. !3384 +- Document how to set up GitLab Geo for HA. !3468 +- Add border for epic edit button. + + +## 10.2.3 (2017-11-30) + +### Fixed (5 changes) + +- Fix viewing default push rules on a Geo secondary. !3559 +- Disable autocomplete for epics. +- Fix epic fullscreen editing. +- Fix tasklist for epics. +- Fix Geo wiki sync error not increasing retry count. + + +## 10.2.2 (2017-11-23) + +### Fixed (6 changes) + +- Fix in-progress repository syncs counting as failed. !3424 +- Don't user issuable_sort cookie for epics collection. +- Enable scoped boards for Early Adopters. +- Account shared runner minutes to top-level namespace. +- Geo - Ensure that LFS object deletions are communicated to the secondary. +- Disable file attachments for epics. + +### Other (1 change) + +- Document a failure mode for large repositories in Geo. !3500 + + +## 10.2.1 (2017-11-22) + +- No changes. + +## 10.2.0 (2017-11-22) + +### Fixed (17 changes) + +- Geo - Does not move projects backed by hashed storage when handling renamed events. !3066 +- Geo: Don't sync disabled project wikis. !3109 +- Reconfigure the Geo tracking database pool size when running as Sidekiq. !3181 +- Geo - Ensures that leases were returned. !3241 +- Fix (un)approver names not being shown in plaintext emails. !3266 +- Add post-migration to drain all Geo related redis queues. !3289 +- Prevent the Geo log cursor from running on primary nodes. !3411 +- Reduce the number of Elasticsearch client instances that are created. !3432 +- Fix generated clone URLs for wikis on Geo secondaries. !3448 +- Remove duplicate delete button in epic. +- Fix: Failed to rebase MR from forked repo. +- Fix: Geo API bug. Statistic is not collected when prometheus is disabled. +- Geo - Ensure that repository deletions in a primary node are correctly deleted in a secondary node. +- Geo: Fix handling of nil values on advanced section in admin screen. +- Redirect to existing group boards using old URL if there is no subgroup called 'boards'. +- Geo - Allow Sidekiq to retry failed jobs to rename project repositories. +- Geo: Ensure database is connected before attempting to check for secondary status. + +### Changed (4 changes) + +- Add project actions in Audit events. !3160 +- Add group actions in Audit events. !3176 +- Geo: Don't retry repositories or files until everything has been backfilled. !3182 +- Improve Codeclimate UI. + +### Performance (1 change) + +- Reduce the quiet times between scheduler runs on Geo secondaries. !3185 + +### Added (20 changes, 1 of them is from the community) + +- Add new push rule to enforce that only the author of a commit can push to the repository. !3086 +- Make the maximum capacity of Geo backfill operations configurable. !3107 +- Mirrors can now hard fail, keeping them from being retried until a project admin takes action. !3117 +- View/edit epic at group level. !3126 +- Add worker to prune the Geo Event Log. !3172 +- julian7 Add required_groups option to SAML config, to restrict access to GitLab to specific SAML groups. !3223 (Balazs Nagy) +- Geo: Expire and resync attachments from renamed projects in secondary nodes when using legacy storage. !3259 +- On Secondary read-only Geo Nodes now a flash banner is shown on all pages. !3260 +- Make GeoLogCursor Highly Available. !3305 +- Allow Geo repository sync over HTTPS. !3341 +- Allow persisting board configuration in order to automatically filter issues. +- Improve error handling. +- Add epics list and add epics to nav sidebar. +- Introduce EEU lincese with epics as the first feature. +- Add ability to create new epics. +- Add sidebar for epic. +- Add delete epic button. +- Allow admins to globally disable all remote mirrors from application settings page. +- Add support for logging Prometheus metrics for Geo. +- Use PostgreSQL FDW for Geo downloads. + +### Other (2 changes, 1 of them is from the community) + +- Suppress MergeableSelector warning candidates in EE-only files. !3225 (Takuya Noguchi) +- Enhance the documentation for gitlab-ctl replicate-geo-database. !3268 + + +## 10.1.4 (2017-11-14) + +- No changes. + +## 10.1.3 (2017-11-10) + +- [FIXED] Fix: Failed to rebase MR from forked repo. + +## 10.1.2 (2017-11-08) + +- [SECURITY] Fix vulnerability that could allow any user of a Geo instance to clone any repository on the secondary instance. +- [SECURITY] Geo JSON web tokens now expire after two minutes to reduce risk of compromise. + +## 10.1.1 (2017-10-31) + +- [FIXED] Fix LDAP group sync for nested groups e.g. when base has uppercase or extraneous spaces. !3217 +- [FIXED] Geo: read-only safeguards was not working on Secondary node. !3227 +- [FIXED] fix height of rebase and approve buttons. +- [FIXED] Move group boards routes under - and remove "boards" from reserved paths. + +## 10.1.0 (2017-10-22) + +- [SECURITY] Prevent Related Issues from leaking confidential issues. !541 +- [FIXED] Geo - Selective replication allows admins to select any groups. !2779 +- [FIXED] Fix CSV export when filtering issues by multiple labels. !2852 +- [FIXED] Impersonation no longer gets stuck on password change. !2904 +- [FIXED] Mirroring to remote repository no longer fails after a force push. !2919 +- [FIXED] Fix a merge request validation error on forked projects. !2932 +- [FIXED] Fix an error reporting some failures in the elasticsearch indexer. !2998 +- [FIXED] Fix a Geo node validation, preventing admins from locking themselves out. !3040 +- [FIXED] Find stuck scheduled import jobs and also mark them as failed. !3055 +- [FIXED] Fix removing the username from the git repository URL for pull mirroring. !3060 +- [FIXED] Prevent failed file syncs from stalling Geo backfill. !3101 +- [FIXED] Fix reading the status of a secondary Geo node from the primary. !3140 +- [FIXED] Always allow the default branch as a branch name. !3154 +- [FIXED] Show errors when rebase onto target branch fails in the UI. +- [FIXED] Fix base link for issues on group boards. +- [FIXED] Don't create todos for old issue assignees. +- [FIXED] Geo: Fix attachments/avatars saving to the wrong directory. +- [FIXED] Save Geo files to a temporary file and rename after success. +- [FIXED] Fix personal snippets not downloading in Geo secondaries. +- [FIXED] Geo: Limit the huge cross-database pluck for LFS objects and attachments. +- [CHANGED] Schedule repository synchronization when processing events on a Geo secondary node. !2838 +- [CHANGED] Create idea of read-only database and add method to check for it. !2954 +- [CHANGED] Remove the backoff delay from Geo repository sync. !3009 +- [CHANGED] Improves visibility of deploy boards. +- [CHANGED] Improve performance of rebasing by using worktree. +- [ADDED] Add suport for CI/CD pipeline policy management. !2986 +- [ADDED] Add LDAP synchronization based on filter for GitLab groups. +- [OTHER] Add Geo rake task descriptions. !2925 +- [OTHER] Improve logging output for several Geo background workers. !2961 +- [OTHER] Add partial index on push_rules.is_sample. +- Add new push rule to reject unsigned commits. !2913 + +## 10.0.5 (2017-11-03) + +- [FIXED] Find stuck scheduled import jobs and also mark them as failed. !3055 +- [FIXED] Fix removing the username from the git repository URL for pull mirroring. !3060 +- [FIXED] Fix base link for issues on group boards. +- [FIXED] Move group boards routes under - and remove "boards" from reserved paths. +- [FIXED] Geo: Fix attachments/avatars saving to the wrong directory. + +## 10.0.4 (2017-10-16) + +- [SECURITY] Prevent Related Issues from leaking confidential issues. !541 +- [SECURITY] Escape user name in filtered search bar. + +## 10.0.3 (2017-10-05) + +- [FIXED] Rewrite Geo database rake tasks so they operate on the correct database. !3052 +- [FIXED] Show group tab if member lock is enabled. +- [FIXED] File uploaders do not perform hard check, only soft check. +- [FIXED] Only show Turn on Service Desk button when user has permissions. +- [FIXED] Fix EE delta size check handling with annotated tags. + +## 10.0.2 (2017-09-27) + +- [FIXED] Send valid project path as name for Jira dev panel. +- [FIXED] Fix delta size check to handle commit or nil objects. + +## 10.0.1 (2017-09-23) + +- No changes. + +## 10.0.0 (2017-09-22) + +- [SECURITY] Check if LDAP users are in external groups on login. !2720 +- [FIXED] Fix typo for `required` attribute. !2659 +- [FIXED] Fix global code search when using negation queries. !2709 +- [FIXED] Fixes activation of project mirror when new project is created. !2756 +- [FIXED] Geo - Whitelist LFS requests to download objects on a secondary node. !2758 +- [FIXED] Fix Geo::RepositorySyncWorker so attempts to sync all projects if some are failing. !2796 +- [FIXED] Fix unsetting credentials data for pull mirrors. !2810 +- [FIXED] Geo: Gracefully catch incorrect db key on primary. !2819 +- [FIXED] Fix a regression breaking projects with an empty import URL. !2824 +- [FIXED] Fix a 500 error in the SSH host keys lookup action. !2827 +- [FIXED] Handle Geo DB replication lag as 24h/day & 7d/week. !2833 +- [FIXED] Geo - Add a unique index on project_id to the Geo project_registry table. !2850 +- [FIXED] Improve Geo repository sync performance for larger databases. !2887 +- [FIXED] Ensure #route_setting is available before calling it. !2908 +- [FIXED] Fix searching by assignee in the service desk. !2969 +- [FIXED] Fix approvals before merge error while importing projects. +- [FIXED] Fix the gap in approvals in merge request widget. +- [FIXED] Fix branch name regex not saving in /admin/push_rule config. +- [FIXED] Fix merges not working when project is not licensed for squash. +- [CHANGED] Add Time estimate and Time spend fields in csv export. !2627 (g3dinua, LockiStrike) +- [CHANGED] Improve copy so users will set up SSH from DB for Geo. !2644 +- [CHANGED] Support `codequality` job name for Code Quality feature. !2704 +- [CHANGED] Support Elasticsearch v5.1 - v5.5. !2751 +- [CHANGED] Geo primary nodes no longer require SSH keys. !2861 +- [CHANGED] Show Geo event log and cursor data in node status page. +- [CHANGED] Use a logger for the artifacts migration rake task. +- [ADDED] LFS files can be stored in remote object storage such as S3. !2760 +- [ADDED] Add LDAP sync endpoint to Groups API. !2785 +- [ADDED] Geo - Log a repository created event when a project is created. !2807 +- [ADDED] Show geo.log in the Admin area. !2845 +- [ADDED] Commits integration with Jira development panel. +- [OTHER] Add missing indexes to geo_event_log table. !2836 +- [OTHER] Geo - Ignore S3-backed LFS objects on secondary nodes. !2889 +- Fix a bug searching private projects with Elasticsearch as an admin or auditor. !2613 +- Don't put the password in the SSH remote if using public-key authentication. !2837 +- Support handling of rename events in Geo Log Cursor. +- Update delete board button text color to red and fix hover color. +- Search for issues with multiple assignees. +- Fix: When MR approvals are disabled, but approvers were previously assigned, all approvers receive a notification on every MR. +- Add group issue boards. +- Ports style changes fixed in a conflict in ce to ee upstream to master for new projects page. + +## 9.5.9 (2017-10-16) + +- [SECURITY] Prevent Related Issues from leaking confidential issues. +- Escape user name in filtered search bar. + +## 9.5.8 (2017-10-04) + +- [FIXED] Fix EE delta size check handling with annotated tags. +- [FIXED] Fix delta size check to handle commit or nil objects. + +## 9.5.7 (2017-10-03) + +- No changes. + +## 9.5.6 (2017-09-29) + +- [FIXED] Show group tab if member lock is enabled. + +## 9.5.5 (2017-09-18) + +- [FIXED] Fixes activation of project mirror when new project is created. !2756 +- [FIXED] Geo - Whitelist LFS requests to download objects on a secondary node. !2758 +- [FIXED] Fix unsetting credentials data for pull mirrors. !2810 +- [FIXED] Fix a regression breaking projects with an empty import URL. !2824 +- [FIXED] Fix a 500 error in the SSH host keys lookup action. !2827 +- [FIXED] Ensure #route_setting is available before calling it. !2908 +- [FIXED] Fix branch name regex not saving in /admin/push_rule config. +- [FIXED] Fix the gap in approvals in merge request widget. +- [FIXED] Fix merges not working when project is not licensed for squash. +- Don't put the password in the SSH remote if using public-key authentication. !2837 + +## 9.5.4 (2017-09-06) + +- [FIXED] Validate branch name push rule when pushing branch without commits. !2685 + +## 9.5.3 (2017-09-03) + +- [FIXED] Check if table exists before loading the current license. !2783 +- [FIXED] Extend early adopters feature set. + +## 9.5.2 (2017-08-28) + +- [FIXED] Fix LDAP backwards-compatibility when using "method" or when "verify_certificates" is not defined. !2690 +- [FIXED] Geo - Count projects where wiki sync failed in node status page. + +## 9.5.1 (2017-08-23) + +- [FIXED] Fix url for object store artifacts. +- [CHANGED] Ensure all database queries are routed through the database load balancer when load balancing is enabled +. !2707 + +## 9.5.0 (2017-08-22) + +- [FIXED] Fix Copy to Clipboard for SSH Public Key on Pull Repository settings. !2692 +- [FIXED] Enable mirror repository button. +- [FIXED] Create system notes only if issue was successfully related. +- [FIXED] Fix issue boards focus button not being visible to guest users. +- Namespace license checks Audit Events & Admin Audit Log. !2326 +- Namespace license checks for Repository Mirrors. !2328 +- Automatically link kerberos users to LDAP people. !2405 +- Implement SSH public-key support for repository mirroring. !2423 +- Shows project names for commits in elasticsearch global search. !2434 +- Add admin application setting to allow group owners to manage LDAP. !2529 +- Geo - Selectively choose which namespaces to replicate in DR. !2533 +- Support variables on Trigger API for Cross-project pipeline. !2557 +- Allow excluding sidekiq queues from execution in sidekiq-cluster. !2571 +- Ensure artifacts are moved locally within the filesystem to prevent timeouts. !2572 +- Audit failed login events. !2587 +- Spread load across all nodes in an elasticsearch cluster. !2625 +- Improves handling of stuck imports. !2628 +- Improves handling of the mirror threshold. !2671 +- Allow artifacts access with job_token parameter or CI_JOB_TOKEN header. +- Add initial Groups/Billing and Profile/Billing routing and template. +- Fix rebase from fork when upstream has protected branches. +- Present Related Issues add badge only when user can manage related issues (previously when user could edit issue). +- clean up merge request widget UI. +- Make contextual sidebar collapsible. +- Fix accessing individual files on Object Storage. +- Fix rebase button when merge request is created from a fork. +- Skip oAuth authorization for trusted applications. + +## 9.4.7 (2017-10-16) + +- [SECURITY] Prevent Related Issues from leaking confidential issues. +- Fix when pushing without a branch name. !2879 +- Escape user name in filtered search bar. + +## 9.4.6 (2017-09-06) + +- [FIXED] Validate branch name push rule when pushing branch without commits. !2685 + +## 9.4.5 (2017-08-14) + +- Ensure artifacts are moved locally within the filesystem to prevent timeouts. !2572 +- Fix rebase from fork when upstream has protected branches. +- Present Related Issues add badge only when user can manage related issues (previously when user could edit issue). +- Fix accessing individual files on Object Storage. + +## 9.4.4 (2017-08-09) + +- No changes. + +## 9.4.3 (2017-07-31) + +- Present Related Issues widget for logged-out users when available. + +## 9.4.2 (2017-07-28) + +- Adds lower bound to pull mirror scheduling feature. !2366 +- Add warning and option toggle when rebuilding authorized_keys. !2508 +- Fix CSS for mini graph with downstream pipeline. +- Renamed board to boards in new project sidebar. +- Fix Rebasing not working with Merge Requests. +- Fixed issue boards focus mode when new navigation is turned on. + +## 9.4.1 (2017-07-25) + +- Cleans up mirror capacity in project destroy service if project is a scheduled mirror. !2445 +- Fixes unscoping of imposed capacity limit by find_each method on Mirror scheduler. !2460 +- Remove text underline from suggested approvers. + +## 9.4.0 (2017-07-22) + +- GeoLogCursor is part of a new experimental Geo replication system. !1988 +- Add explicit licensing for Elasticsearch. !2108 +- Add namespace license checks for Service Desk (EEP). !2109 +- Add environment scope to secret variables to specify environments. !2112 +- Namespace license checks for exporting issues (EES). !2164 +- Retry Elasticsearch queries on failure. !2181 +- Introduce namespace license checks for rebase before merge. !2200 +- Geo: fix removal of repositories from disk on secondary nodes. !2210 +- Add license checks for brundown charts. !2219 +- Add namespace license checks for squash before merge. !2249 +- Namespace license checks for fast-forward merge (EES). !2272 +- Empty repository mirror no longer creates master branch with README automatically. !2276 +- Introduce namespace licensing for issue weights (EES). !2291 +- Add namespace license checks for Contribution Analytics. !2302 +- Add license checks for focus mode on the issue board. !2303 +- Add license checks for issue boards with milestones. !2315 +- Add license checks for multiple issue boards. !2317 +- Geo: Fix clone instructions in a secondary node for SSH protocol. !2319 +- Namespace license checks Issue & MR template. !2321 +- Introduce namespace license checks for merge request approvers (EES). !2324 +- Introduce namespace license checks for Push Rules (EES). !2335 +- Geo: Implement alternative to geo_{primary|secondary}_role in gitlab.yml. !2352 +- Geo: Added extra SystemCheck checks. !2354 +- Implement progressive elasticsearch indexing for project mirrors. !2393 +- Fix undefined method quote when database load balancing is used. !2430 +- Improve the performance of the project list API. !12679 +- fix approver placeholder icon in ie11. +- Add public API for listing, creating and deleting Related Issues. +- All artifacts are now browsable. +- Escape symbols in exported CSV columns to prevent command execution in Microsoft Excel. +- Geo - Fix RepositorySyncService when cannot obtain a lease to sync a repository. +- Prevent mirror user to be assigned to users other than the current one. +- Geo - Makes the projects synchronization faster on secondaries nodes. +- Only show the LDAP sync banner on first login. +- Enable service desk be default. +- Fix creation of push rules via POST API. +- Fix Geo middleware to work properly with multiple requests. +- [GitLab.com only] Add Slack applicationq service. +- Speed up checking for approvers when approvers are specified on the MR. +- Allows manually adding bi-directional relationships between issues in the issue page (EES feature). +- Add Geo repository renamed event log. +- Merge states to allow realtime with deploy boards. +- Fix 500 error when approvals are enabled and editing an MR conflicts with another edit. +- add toggle for overriding approvers per MR. +- Add optional sha param when approving a merge request through the API. +- Allow updating shared_runners_minutes_limit on admin Namespace API. +- Allow to Store Artifacts on Object Storage. +- Adding support for AWS ec2 instance profile credentials with elasticsearch. (Matt Gresko) +- Fixed edit issue boards milestone action buttons not sticking to bottom of dropdown. +- Respect the external user setting in Elasticsearch. + +## 9.3.11 (2017-09-06) + +- [FIXED] Validate branch name push rule when pushing branch without commits. !2685 +- Prevent mirror user to be assigned to users other than the current one. + +## 9.3.10 (2017-08-09) + +- No changes. + +## 9.3.9 (2017-07-20) + +- No changes. + +## 9.3.8 (2017-07-19) + +- Escape symbols in exported CSV columns to prevent command execution in Microsoft Excel. +- Prevent mirror user to be assigned to users other than the current one. + +## 9.3.7 (2017-07-18) + +- No changes. + +## 9.3.6 (2017-07-12) + +- Geo: Fix clone instructions in a secondary node for SSH protocol. !2319 +- Implement progressive elasticsearch indexing for project mirrors. !2393 + +## 9.3.5 (2017-07-05) + +- Make admin mirror application setting Gitlab.com exclusive. !2307 +- Make Geo::RepositorySyncService force create a repo. + +## 9.3.4 (2017-07-03) + +- Update gitlab-shell to 5.1.1 to fix Post Recieve errors + +## 9.3.3 (2017-06-30) + +- Add metrics to both remote and non remote mirroring. !2118 +- Forces import worker with mirror to insert mirror in front of queue. !2231 +- Fix locked and stale SSH keys file from 9.3.0 upgrade. !2240 +- Fix crash in LDAP sync when user was removed. !2289 +- allow rebase for unapproved merge requests. +- Geo - Fix path_with_namespace for instances of Geo::DeletedProject. + +## 9.3.2 (2017-06-27) + +- Fix GitLab check: Problem with Elastic Search. !2278 + +## 9.3.1 (2017-06-26) + +- Geo: fix removal of repositories from disk on secondary nodes. !2210 +- Fix Geo middleware to work properly with multiple requests. + +## 9.3.0 (2017-06-22) + +- Per user/group access levels for Protected Tags. !1629 +- Add a user's memberships when logging in through LDAP. !1819 +- Add server-wide Audit Log admin screen. !1852 +- Move pull mirroring to adaptive scheduling. !1853 +- Create a push rule to check the branch name. !1896 (Riccardo Padovani) +- Add shared_runners_minutes_limit to groups and users API. !1942 +- Compare codeclimate artifacts on the merge request page. !1984 +- Lookup users by email in LDAP if lookup by DN fails during sync. !2003 +- Update mirror_user for project when mirror_user is deleted. !2013 (Athar Hameed) +- Geo: persist clone url prefix in the database. !2015 +- Geo: prevent Gitlab::Git::Repository::NoRepository from stucking replication. !2115 +- Geo: fixed Dynamic Backoff strategy that was not being used by workers. !2128 +- [Elasticsearch] Improve code search for camel case. +- Fixed header being over issue boards when in focus mode. +- Fix: Approvals not reset if changing target branch. +- Fix bug where files over 2 GB would not be saved in Geo tracking DB. +- Add primary node clone URL to Geo secondary 'How to work faster with Geo' popover. +- Fix broken time sync leeway with Geo. +- Gracefully handle case when Geo secondary does not have the right db_key_base. +- Use the current node configuration to populate suggested new URL for Geo node. +- Check if a merge request is approved when merging from API or slash command. +- Add closed_at field to issue CSV export. +- Geo - Properly set tracking database connection and cron jobs on secondary nodes. +- Add push events to Geo event log. +- fix Rebase being disabled for unapproved MRs. +- Fix approvers dropdown when creating a merge request from a fork. +- Add relation between Pipelines. +- Allow to Trigger Pipeline using CI Job Token. +- Allow to view Personal pipelines quota. +- Geo - Use GeoNode#clone_url_prefix for the Geo::RepositorySyncService. +- Elasticsearch searches through the project description. +- Fix: /unassign by default unassigns everyone. Implement /reassign command. +- Speed up checking for approvers remaining. + +## 9.2.10 (2017-08-09) + +- No changes. + +## 9.2.9 (2017-07-20) + +- No changes. + +## 9.2.8 (2017-07-19) + +- Escape symbols in exported CSV columns to prevent command execution in Microsoft Excel. +- Prevent mirror user to be assigned to users other than the current one. + +## 9.2.7 (2017-06-21) + +- Geo: fixed Dynamic Backoff strategy that was not being used by workers. !2128 +- fix Rebase being disabled for unapproved MRs. + +## 9.2.6 (2017-06-16) + +- Geo: backported fix from 9.3 for big repository sync issues. !2000 +- Geo - Properly set tracking database connection and cron jobs on secondary nodes. +- Fix approvers dropdown when creating a merge request from a fork. +- Fixed header being over issue boards when in focus mode. +- Fix bug where files over 2 GB would not be saved in Geo tracking DB. + +## 9.2.5 (2017-06-07) + +- No changes. + +## 9.2.4 (2017-06-02) + +- No changes. +- No changes. + +## 9.2.3 (2017-05-31) + +- No changes. +- No changes. +- Respect the external user setting in Elasticsearch. + +## 9.2.2 (2017-05-25) + +- No changes. + +## 9.2.1 (2017-05-23) + +- No changes. + +## 9.2.0 (2017-05-22) + +- Stop using sidekiq cron for push mirrors. !1616 +- Inline RSS button with Export Issues button for mobile. !1637 +- Highlight Contribution Analytics tab under groups when active, remove sub-nav items. !1677 +- Uses etag polling for deployboards. !1713 +- Support more elasticsearch versions. !1716 +- Support advanced search queries using elasticsearch. !1770 +- Remove superfluous wording on push rules. !1811 +- Geo - Fix signing out from secondary node when "Remember me" option is checked. !1903 +- Add global wiki search using Elasticsearch. +- Remove warning about protecting Service Desk email from form. +- Geo: Resync repositories that have been updated recently. +- Respect project features when searching alternative branches with elasticsearch enabled. +- Backfill projects where the last attempt to backfill failed. +- Fix MR approvals sentence when all approvers need to approve the MR. +- Fix for XSS in project mirror errors caused by Hamlit filter usage. +- Feature availability check using feature list AND license addons. +- Disable mirror workers for Geo secondaries. + +## 9.1.10 (2017-08-09) + +- No changes. + +## 9.1.9 (2017-07-20) + +- No changes. + +## 9.1.8 (2017-07-19) + +- Escape symbols in exported CSV columns to prevent command execution in Microsoft Excel. +- Prevent mirror user to be assigned to users other than the current one. + +## 9.1.7 (2017-06-07) + +- No changes. + +## 9.1.6 (2017-06-02) + +- No changes. + +## 9.1.5 (2017-05-31) + +- Respect the external user setting in Elasticsearch. + +## 9.1.4 (2017-05-12) + +- Remove warning about protecting Service Desk email from form. +- Backfill projects where the last attempt to backfill failed. + +## 9.1.3 (2017-05-05) + +- No changes. +- No changes. +- No changes. +- Respect project features when searching alternative branches with elasticsearch enabled. +- Fix for XSS in project mirror errors caused by Hamlit filter usage. + +## 9.1.2 (2017-05-01) + +- No changes. +- No changes. +- No changes. +- Fix commit search on some elasticsearch indexes. !1745 +- Fix emailing issues to projects when Service Desk is enabled. +- Fix bug where Geo secondary Sidekiq cron jobs would not be activated if settings changed. + +## 9.1.1 (2017-04-26) + +- No changes. + +## 9.1.0 (2017-04-22) + +- Fix rake gitlab:env:info elasticsearch datum. !1422 +- Fix 500 errors caused by elasticsearch results referencing garbage-collected commits. !1430 +- Adds timeout option to push mirrors. !1439 +- elasticsearch: Add support for an experimental repository indexer. !1483 +- Update color palette to a more harmonious and consistent one. !1500 +- Cache Gitlab::Geo queries. !1507 +- Add Service Desk feature. !1508 +- Fix pre-receive hooks when using Git 2.11 or later. !1525 +- Geo: Add support to sync avatars and attachments. !1562 +- Fix Elasticsearch not working when URL ends with a forward slash. !1566 +- Allow admins to perform global searches with Elasticsearch. !1578 +- Periodically persists users activity to users.last_activity_on. !1597 +- Removes duplicate count of LFS objects from repository_and_lfs_size method. !1599 +- Fix searching notes and snippets as an auditor. !1674 +- Fix searching for notes with elasticsearch when a user is a member of many projects. !1675 +- Fix type declarations for spend/estimate values. +- Speed up suggested approvers on MR creation. +- Fix squashing MRs when the repository contains a ref named HEAD. +- Fix approver count reset when editing assignee or labels. +- Geo: handle git failures on GeoRepositoryFetchWorker. +- Give each elasticsearch worker its own sidekiq queue. +- Fixes broken link to pipeline quota. +- Prevent filtering issues by multiple Milestones or Authors. +- Fix 500 error when selecting a mirror user. +- Add index to approvals.merge_request_id. +- Added mock data for Deployboard. +- Add uuid to usage ping. +- Expose board project and milestone on boards API. +- Fix active user count to ignore internal users. +- Add warning when burndown data is not accurate. +- Check if incoming emails and email key are available for service desk. +- Add burndown chart to milestones. +- Make deployboard to be visible by default. +- Add a Rake task to make the current node the primary Geo node. +- Return 404 instead of a 500 error on API status endpoint if Geo tracking DB is not enabled. +- Remove N+1 queries for Groups::AnalyticsController. +- Show user cohorts data when usage ping is enabled. +- Visualise Canary Deployments. + +## 9.0.13 (2017-08-09) + +- No changes. + +## 9.0.12 (2017-07-20) + +- No changes. + +## 9.0.11 (2017-07-19) + +- Escape symbols in exported CSV columns to prevent command execution in Microsoft Excel. +- Prevent mirror user to be assigned to users other than the current one. + +## 9.0.10 (2017-06-07) + +- No changes. + +## 9.0.9 (2017-06-02) + +- No changes. + +## 9.0.8 (2017-05-31) + +- Respect the external user setting in Elasticsearch. + +## 9.0.7 (2017-05-05) + +- Respect project features when searching alternative branches with elasticsearch enabled. +- Fix for XSS in project mirror errors caused by Hamlit filter usage. + +## 9.0.6 (2017-04-21) + +- Cache Gitlab::Geo queries. !1507 +- Fix searching for notes with elasticsearch when a user is a member of many projects. !1675 +- Fix 500 error when selecting a mirror user. +- Fix active user count to ignore internal users. + +## 9.0.5 (2017-04-10) + +- Return 404 instead of a 500 error on API status endpoint if Geo tracking DB is not enabled. + +## 9.0.4 (2017-04-05) + +- No changes. + +## 9.0.3 (2017-04-05) + +- Allow to edit pipelines quota for user. +- Fixed label resetting when sorting by weight. (James Clark) +- Fixed issue boards milestone toggle text not updating when filtering. +- Fixed mirror user dropdown not displaying. + +## 9.0.2 (2017-03-29) + +- No changes. + +## 9.0.1 (2017-03-28) + +- No changes. + +## 9.0.0 (2017-03-22) + +- Geo: Replicate repository creation in Geo secondary node. !952 +- Make approval system notes lowercase. !1125 +- Issues can be exported as CSV, via email. !1126 +- Try to update mirrors again after 15 minutes if the previous update failed. !1183 +- Adds abitlity to render deploy boards in the frontend side. !1233 +- Add filtered search to MR page. !1243 +- Update project list API returns with approvals_before_merge attribute. !1245 (Geoff Webster) +- Catch Net::LDAP::DN exceptions in EE::Gitlab::LDAP::Group. !1260 +- API: Use `post ":id/#{type}/:subscribable_id/subscribe"` to subscribe and `post ":id/#{type}/:subscribable_id/unsubscribe"` to unsubscribe from a resource. !1274 (Robert Schilling) +- API: Remove deprecated fields Notes#upvotes and Notes#downvotes. !1275 (Robert Schilling) +- Deploy board backend. !1278 +- API: Remove the ProjectGitHook API. !1301 (Robert Schilling) +- Expose elasticsearch client params for AWS signing and HTTPS. !1305 (Matt Gresko) +- Fix LDAP DN case-mismatch bug in LDAP group sync. !1337 +- Remove es6 file extension from JavaScript files. !1344 (winniehell) +- Geo: Don't load dependent models when fetching an existing GeoNode from the database. !1348 +- Parallelise the gitlab:elastic:index_database Rake task. !1361 +- Robustify reading attributes for elasticsearch. !1365 +- Introduce one additional thread into bin/elastic_repo_indexer. !1372 +- Show hook errors for fast-forward merges. !1375 +- Allow all parameters of group webhooks to be set through the UI. !1376 +- Fix Elasticsearch queries when a group_id is specified. !1423 +- Check the right index mapping based on Rails environment for rake gitlab:elastic:add_feature_visiblity_levels_to_project. !1473 +- Fix issues with another milestone that has a matching list label could not be added to a board. +- Only admins or group owners can set LDAP overrides. +- Add support for load balancing database queries. +- Only replace non-approval mr-widget-footer on getMergeStatus. +- Remove repository_storage from V4 "/application/settings" settings API. +- Added headers to protected branches access dropdowns. +- Remove support for Git Annex. +- Repositioned multiple issue boards selector. +- Added back weight in issue rows on issue list. +- Add basic support for GitLab Geo file transfers over HTTP. +- Added weight slash command. +- Set deployment status invalid when the environments does not match a k8s label. +- Combined deploy keys, push rules, protect branches and mirror repository settings options into a single one called Repository. +- Rebase - fix commiter email & name. +- Adds a EE specific dev favicon. +- Elastic security fix: Respect feature visibility level. +- Update Elasticsearch to 5.1. +- [Elasticsearch] More efficient search. +- Get Geo secondaries nodes statuses over AJAX. + +## 8.17.8 (2017-08-09) + +- No changes. + +## 8.17.7 (2017-07-19) + +- Prevent mirror user to be assigned to users other than the current one. + +## 8.17.6 (2017-05-05) + +- Respect project features when searching alternative branches with elasticsearch enabled. + +## 8.17.5 (2017-04-05) + +- No changes. + +## 8.17.4 (2017-03-19) + +- Elastic security fix: Respect feature visibility level. + +## 8.17.3 (2017-03-07) + +- No changes. + +## 8.17.2 (2017-03-01) + +- No changes. + +## 8.17.1 (2017-02-28) + +- Fix admin email notification recipient group select list. +- Add repository_storage field back to projects API for admin users. +- Don't try to update a project's external service caches on a secondary Geo node. +- Fixed merge request state not updating when approvals feature is active. +- Improve error messages when squashing fails. + +## 8.17.0 (2017-02-22) + +- Read-only "auditor" user role. !998 +- Also reset approvals on push when merge request is closed. !1051 +- Copy commit SHA to clipboard. !1066 +- Pull EE specific Gitlab::Auth code in to its own module. !1112 +- Geo: Added `gitlab:geo:check` and improved `gitlab:envinfo` rake tasks. !1120 +- Geo: send the new event type with the backfill function. !1157 +- Re-add removed params from projects and issues V3 API. !1209 +- Add configurable minimum mirror sync time in admin section. !1217 +- Move RepositoryUpdateRemoteMirrorWorker jobs to project_mirror Sidekiq queue. !1234 +- Change Builds word to Pipelines in Mirror settings page. +- Fix bundle tag in anaytics page. +- Support v4 API for GitLab Geo endpoints. +- Fixed merge request environment link not displaying. +- Reduce queries needed to check if node is a primary or secondary Geo node. +- Allow squashing merge requests into a single commit. + +## 8.16.9 (2017-04-05) + +- No changes. + +## 8.16.8 (2017-03-19) + +- No changes. +- No changes. +- No changes. +- Elastic security fix: Respect feature visibility level. + +## 8.16.7 (2017-02-27) + +- Fixed merge request state not updating when approvals feature is active. + +## 8.16.6 (2017-02-17) + +- Geo: send the new event type with the backfill function. !1157 +- Move RepositoryUpdateRemoteMirrorWorker jobs to project_mirror Sidekiq queue. !1234 +- Fixed merge request environment link not displaying. +- Reduce queries needed to check if node is a primary or secondary Geo node. +- Read true-up info from license and validate it. !1159 + +## 8.16.5 (2017-02-14) + +- No changes. + +## 8.16.4 (2017-02-02) + +- Disable all merge acceptance buttons pending MR approval. + +## 8.16.3 (2017-01-27) + +- Fix sidekiq cluster mishandling of queue names. !1117 + +## 8.16.2 (2017-01-25) + +- Track Mattermost usage in usage ping. !1071 +- Fix count of required approvals displayed on MR edit form. !1082 +- Fix updating approvals count when editing an MR. !1106 +- Don't try to show assignee in approved_merge_request_email if there's no assignee. + +## 8.16.1 (2017-01-23) + +- No changes. + +## 8.16.0 (2017-01-22) + +- Allow to limit shared runners minutes quota for group. !965 +- About GitLab link in sidebar that links to help page. !1008 +- Prevent 500 error when uploading/entering a blank license. !1016 +- Add more push rules to the API. !1022 (Robert Schilling) +- Expose issue weight in the API. !1023 (Robert Schilling) +- Copy to clipboard. !1048 + +## 8.15.8 (2017-03-19) + +- No changes. +- No changes. +- Elastic security fix: Respect feature visibility level. + +## 8.15.7 (2017-02-15) + +- No changes. + +## 8.15.6 (2017-02-14) + +- No changes. + +## 8.15.5 (2017-01-20) + +- No changes. + +## 8.15.4 (2017-01-09) + +- No changes. + +## 8.15.3 (2017-01-06) + +- Disable LDAP permission override in project members edit list. +- Perform only one fetch per push on Geo secondary nodes. + +## 8.15.2 (2016-12-27) + +- No changes. +- Fix ES search for non-default branches. + +## 8.15.1 (2016-12-23) + +- Fix 404/500 error while navigating to the 'show/destroy' pages. !993 + +## 8.15.0 (2016-12-22) + +- Adds a check ensure only active, ie. non-blocked users can be emailed from the admin panel. +- Add user activities API. +- Add milestone total weight to the milestone summary. +- Allow master/owner to change permission levels when LDAP group sync is enabled. !822 +- Geo: Improve project view UI to teach users how to clone from a secondary Geo node and push to a primary. !905 +- Technical debt follow-up from restricting pushes / merges by group. !927 +- Geo: Enables nodes to be removed even without proper license. !978 +- Update validates_hostname to 1.0.6 to fix a bug in parsing hexadecimal-looking domain names. !982 + +## 8.14.10 (2017-02-15) + +- No changes. + +## 8.14.9 (2017-02-14) + +- No changes. + +## 8.14.8 (2017-01-25) + +- No changes. + +## 8.14.7 (2017-01-21) + +- No changes. + +## 8.14.6 (2017-01-10) + +- No changes. + +## 8.14.5 (2016-12-14) + +- Add milestone total weight to the milestone summary. + +## 8.14.4 (2016-12-08) + +- No changes. + +## 8.14.3 (2016-12-02) + +- No changes. + +## 8.14.2 (2016-12-01) + +- No changes. + +## 8.14.1 (2016-11-28) + +- Fix: MergeRequestSerializer breaks on MergeRequest#rebase_dir_path when source_project doesn't exist anymore. + +## 8.14.0 (2016-11-22) + +- Added Backfill service for Geo. !861 +- Fix for autosuggested approvers(https://gitlab.com/gitlab-org/gitlab-ee/issues/1273). +- Gracefully recover from previously failed rebase. +- Disable retries for remote mirror update worker. !848 +- Fix Approvals API documentation. +- Add ability to set approvals_before_merge for project through the API. +- gitlab:check rake task checks ES version according to requirements +- Convert ASCII-8BIT LDAP DNs to UTF-8 to avoid unnecessary user deletions +- [Fix] Only owner can see "Projects" button in group edit menu + +## 8.13.12 (2017-01-21) + +- No changes. + +## 8.13.11 (2017-01-10) + +- No changes. + +## 8.13.10 (2016-12-14) + +- No changes. + +## 8.13.9 (2016-12-08) + +- No changes. + +## 8.13.8 (2016-12-02) + +- No changes. + +## 8.13.7 (2016-11-28) + +- No changes. + +## 8.13.6 (2016-11-17) + +- Disable retries for remote mirror update worker. !848 +- Fixed cache clearing on secondary Geo nodes. !869 +- Geo: fix a problem that prevented git cloning from secondary node. !873 + +## 8.13.5 (2016-11-08) + +- No changes + +## 8.13.4 (2016-11-07) + +- Weight dropdown in issue filter form does not stay selected. !826 + +## 8.13.3 (2016-11-02) + +- No changes + +## 8.13.2 (2016-10-31) + +- Don't pass a current user to Member#add_user in LDAP group sync. !830 + +## 8.13.1 (2016-10-25) + +- Hide multiple board actions if user doesnt have permissions. !816 +- Fix Elasticsearch::Transport::Transport::Errors::BadRequest when ES is enabled. !818 + +## 8.13.0 (2016-10-22) + +- Cache the last usage data to avoid unicorn timeouts +- Add user activity table and service to query for active users +- Fix 500 error updating mirror URLs for projects +- Restrict protected branch access to specific groups !645 +- Fix validations related to mirroring settings form. !773 +- Add multiple issue boards. !782 +- Fix Git access panel for Wikis when Kerberos authentication is enabled (Borja Aparicio) +- Decrease maximum time that GitLab waits for a mirror to finish !791 (Borja Aparicio) +- User groups (that can be assigned as approvers) +- Fix a search for non-default branches when ES is enabled +- Re-organized the Sidekiq queues for EE specific workers + +## 8.12.12 (2016-12-08) + +- No changes. + +## 8.12.11 (2016-12-02) + +- No changes. + +## 8.12.10 (2016-11-28) + +- No changes. + +## 8.12.9 (2016-11-07) + +- No changes + +## 8.12.8 (2016-11-02) + +- No changes + +## 8.12.7 + + - No EE-specific changes + +## 8.12.6 + + - No EE-specific changes + +## 8.12.5 + + - No EE-specific changes + +## 8.12.4 + + - [ES] Indexer works with smaller batches of repositories to not exceed NOFILE limit. !774 + +## 8.12.3 + + - Fix prevent_secrets checkbox on admin view + +## 8.12.2 + + - Fix bug when protecting a branch due to missing url paramenter in request !760 + - Ignore unknown project ID in RepositoryUpdateMirrorWorker + +## 8.12.1 + + - Prevent secrets to be pushed to the repository + - Prevent secrets to be pushed to the repository + +## 8.12.0 (2016-09-22) + + - Include more data in EE usage ping + - Reduce UPDATE queries when moving between import states on projects + - [ES] Instrument Elasticsearch::Git::Repository + - Request only the LDAP attributes we need + - Add 'Sync now' to group members page !704 + - Add repository size limits and enforce them !740 + - [ES] Instrument other Gitlab::Elastic classes + - [ES] Fix: Elasticsearch does not find partial matches in project names + - Faster Active Directory group membership resolution !719 + - [ES] Global code search + - [ES] Improve logging + - Fix projects with remote mirrors asynchronously destruction + +## 8.11.11 (2016-11-07) + +- No changes + +## 8.11.10 (2016-11-02) + +- No changes + +## 8.11.9 + + - No EE-specific changes + +## 8.11.8 + + - No EE-specific changes + +## 8.11.7 + + - Refactor Protected Branches dropdown. !687 + - Fix mirrored projects allowing empty import urls. !700 + +## 8.11.6 + + - Exclude blocked users from potential MR approvers. + +## 8.11.5 + + - API: Restore backward-compatibility for POST /projects/:id/members when membership is locked + +## 8.11.4 + + - No EE-specific changes + +## 8.11.3 + + - [ES] Add logging to indexer + - Fix missing EE-specific service parameters for Jenkins CI + - Set the correct `GL_PROTOCOL` when rebasing !691 + - [ES] Elasticsearch workers checks ES settings before running + +## 8.11.2 + + - Additional documentation on protected branches for EE + - Change slash commands docs location + +## 8.11.1 + + - Pulled due to packaging error. + +## 8.11.0 (2016-08-22) + + - Allow projects to be moved between repository storages + - Add rake task to remove old repository copies from repositories moved to another storage + - Performance improvement of push rules + - Temporary fix for #825 - LDAP sync converts access requests to members. !655 + - Optimize commit and diff changes access check to reduce git operations + - Allow syncing a group against all providers at once + - Change LdapGroupSyncWorker to use new LDAP group sync classes + - Allow LDAP `sync_ssh_keys` setting to be set to `true` + - Removed unused GitLab GEO database index + - Restrict protected branch access to specific users !581 + - Enable monitoring for ES classes + - [Elastic] Improve code search + - [Elastic] Significant improvement of global search performance + - [Fix] Push rules check existing commits in some cases + - [ES] Limit amount of retries for sidekiq jobs + - Fix Projects::UpdateMirrorService to allow tags pointing to blob objects + +## 8.10.12 + + - No EE-specific changes + +## 8.10.11 + + - No EE-specific changes + +## 8.10.10 + + - No EE-specific changes + +## 8.10.9 + + - Exclude blocked users from potential MR approvers. + +## 8.10.8 + + - No EE-specific changes + +## 8.10.7 + + - No EE-specific changes + +## 8.10.6 + + - Fix race condition with UpdateMirrorWorker Lease. !641 + +## 8.10.5 + + - Used cached value of project count in `Elastic::RepositoriesSearch` to reduce DB load. !637 + +## 8.10.4 + + - Fix available users in userselect dropdown when there is more than one userselect on the page. !604 (Rik de Groot) + - Fix updating skipped approvers in search list on removal. !604 (Rik de Groot) + +## 8.10.3 + + - Fix regression in Git Annex permission check. !599 + - [Elastic] Fix commit search for some URLs. !605 + - [Elastic][Fix] Commit search breaks for some URLs on gitlab-ce project + +## 8.10.2 + + - Fix pagination on search result page when ES search is enabled. !592 + - Decouple an ES index update from `RepositoryUpdateMirrorWorker`. !593 + - Fix broken `user_allowed?` check in Git Annex push. !597 + +## 8.10.1 + + - No EE-specific changes + +## 8.10.0 (2016-07-22) + + - Add EE license usage ping !557 + - Rename Git Hooks to Push Rules + - Fix EE keys fingerprint add index migration if came from CE + - Add todos for MR approvers !547 + - Replace LDAP group sync exclusive lease with state machine + - Prevent the author of an MR from being on the approvers list + - Isolate EE LDAP library code in EE module (Part 1) !511 + - Make Elasticsearch indexer run as an async task + - Fix of removing wiki data from index when project is deleted + - Ticket-based Kerberos authentication (SPNEGO) + - [Elastic] Suppress ActiveRecord::RecordNotFound error in ElasticIndexWorker + +## 8.9.10 + + - No EE-specific changes + +## 8.9.9 + + - No EE-specific changes + +## 8.9.8 + + - No EE-specific changes + +## 8.9.7 + + - No EE-specific changes + +## 8.9.6 + + - Avoid adding index for key fingerprint if it already exists. !539 + +## 8.9.5 + + - Fix of quoted text in lock tooltip. !518 + +## 8.9.4 + + - Improve how File Lock feature works with nested items. !497 + +## 8.9.3 + + - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !502 + - Fix creating MRs on forks of deleted projects. !503 + - Roll back Grack::Auth to fix Git HTTP SPNEGO. !504 + +## 8.9.2 + + - [Elastic] Fix visibility of snippets when searching. + +## 8.9.1 + + - Improve Geo documentation. !431 + - Fix remote mirror stuck on started issue. !491 + - Fix MR creation from forks where target project has approvals enabled. !496 + - Fix MR edit where target project has approvals enabled. !496 + - Fix vertical alignment of git-hooks page. !499 + +## 8.9.0 (2016-06-22) + + - Fix JenkinsService test button + - Fix nil user handling in UpdateMirrorService + - Allow overriding the number of approvers for a merge request + - Allow LDAP to mark users as external based on their group membership. !432 + - Instrument instance methods of Gitlab::InsecureKeyFingerprint class + - Add API endpoint for Merge Request Approvals !449 + - Send notification email when merge request is approved + - Distribute RepositoryUpdateMirror jobs in time and add exclusive lease on them by project_id + - [Elastic] Move ES settings to application settings + - Always allow merging a merge request whenever fast-forward is possible. !454 + - Disable mirror flag for projects without import_url + - UpdateMirror service return an error status when no mirror + - Don't reset approvals when rebasing an MR from the UI + - Show flash notice when Git Hooks are updated successfully + - Remove explicit Gitlab::Metrics.action assignments, are already automatic. + - [Elastic] Project members with guest role can't access confidential issues + - Ability to lock file or folder in the repository + - Fix: Git hooks don't fire when committing from the UI + +## 8.8.9 + + - No EE-specific changes + +## 8.8.8 + + - No EE-specific changes + +## 8.8.7 + + - No EE-specific changes + +## 8.8.6 + + - [Elastic] Fix visibility of snippets when searching. + +## 8.8.5 + + - Make sure OAuth routes that we generate for Geo matches with the ones in Rails routes !444 + +## 8.8.4 + + - Remove license overusage message + +## 8.8.3 + + - Add standard web hook headers to Jenkins CI post. !374 + - Gracefully handle malformed DNs in LDAP group sync. !392 + - Reduce load on DB for license upgrade check. !421 + - Make it clear the license overusage message is visible only to admins. !423 + - Fix Git hook validations for fast-forward merges. !427 + - [Elastic] In search results, only show notes on confidential issues that the user has access to. + +## 8.8.2 + + - Fix repository mirror updates for new imports stuck in started + - [Elastic] Search through the filenames. !409 + - Fix repository mirror updates for new imports stuck in "started" state. !416 + +## 8.8.1 + + - No EE-specific changes + +## 8.8.0 (2016-05-22) + + - [Elastic] Database indexer prints its status + - [Elastic][Fix] Database indexer skips projects with invalid HEAD reference + - Fix skipping pages when restoring backups + - Add EE license via API !400 + - [Elastic] More efficient snippets search + - [Elastic] Add rake task for removing all indexes + - [Elastic] Add rake task for clearing indexing status + - [Elastic] Improve code search + - [Elastic] Fix encoding issues during indexing + - Warn admin if current active count exceeds license + - [Elastic] Search through the filenames + - Set KRB5 as default clone protocol when Kerberos is enabled and user is logged in (Borja Aparicio) + - Add support for Admin Groups to SAML + - Reduce emails-on-push HTML size by using a simple monospace font + - API requests to /internal/authorized_keys are now tagged properly + - Geo: Single Sign Out support !380 + +## 8.7.9 + + - No EE-specific changes + +## 8.7.8 + + - [Elastic] Fix visibility of snippets when searching. + +## 8.7.7 + + - No EE-specific changes + +## 8.7.6 + + - Bump GitLab Pages to 0.2.4 to fix Content-Type for predefined 404 + +## 8.7.5 + + - No EE-specific changes + +## 8.7.4 + + - Delete ProjectImportData record only if Project is not a mirror !370 + - Fixed typo in GitLab GEO license check alert !379 + - Fix LDAP access level spillover bug !499 + +## 8.7.3 + + - No EE-specific changes + +## 8.7.2 + + - Fix MR notifications for slack and hipchat when approvals are fullfiled. !325 + - GitLab Geo: Merge requests on Secondary should not check mergeable status + +## 8.7.1 + + - No EE-specific changes + +## 8.7.0 (2016-04-22) + + - Update GitLab Pages to 0.2.1: support user-defined 404 pages + - Refactor group sync to pull access level logic to its own class. !306 + - [Elastic] Stabilize database indexer if database is inconsistent + - Add ability to sync to remote mirrors. !249 + - GitLab Geo: Many replication improvements and fixes !354 + +## 8.6.9 + + - No EE-specific changes + +## 8.6.8 + + - No EE-specific changes + +## 8.6.7 + + - No EE-specific changes + +## 8.6.6 + + - Concat AD group recursive member results with regular member results. !333 + - Fix LDAP group sync regression for groups with member value `uid=`. !335 + - Don't attempt to include too large diffs in e-mail-on-push messages (Stan Hu). !338 + +## 8.6.5 + + - No EE-specific changes + +## 8.6.4 + + - No EE-specific changes + +## 8.6.3 + + - Fix other cases where git hooks would fail due to old commits. !310 + - Exit ElasticIndexerWorker's job happily if record cannot be found. !311 + - Fix "Reload with full diff" button not working (Stan Hu). !313 + +## 8.6.2 + + - Fix old commits triggering git hooks on new branches branched off another branch. !281 + - Fix issue with deleted user in audit event (Stan Hu). !284 + - Mark pending todos as done when approving a merge request. !292 + - GitLab Geo: Display Attachments from Primary node. !302 + +## 8.6.1 + + - Only rename the `light_logo` column in the `appearances` table if its not there yet. !290 + - Fix diffs in text part of email-on-push messages (Stan Hu). !293 + - Fix an issue with methods not accessible in some controllers. !295 + - Ensure Projects::ApproversController inherits from Projects::ApplicationController. !296 + +## 8.6.0 (2016-03-22) + + - Handle duplicate appearances table creation issue with upgrade from CE to EE + - Add confidential issues + - Improve weight filter for issues + - Update settings and documentation for per-install LDAP sync time + - Fire merge request webhooks when a merge request is approved + - Add full diff highlighting to Email on push + - Clear "stuck" mirror updates before periodically updating all mirrors + - LDAP: Don't render Linked LDAP groups forms when LDAP is disabled + - [Elastic] Add elastic checker to gitlab:check + - [Elastic] Added UPDATE_INDEX option to rake task + - [Elastic] Removing repository and wiki index after removing project + - [Elastic] Update index on push to wiki + - [Elastic] Use subprocesses for ElasticSearch index jobs + - [Elastic] More accurate as_indexed_json (More stable database indexer) + - [Elastic] Fix: Don't index newly created system messages and awards + - [Elastic] Fixed exception on branch removing + - [Elastic] Fix bin/elastic_repo_indexer to follow config + - GitLab Geo: OAuth authentication + - GitLab Geo: Wiki synchronization + - GitLab Geo: ReadOnly Middleware improvements + - GitLab Geo: SSH Keys synchronization + - Allow SSL verification to be configurable when importing GitHub projects + - Disable git-hooks for git annex commits + +## 8.5.13 + + - No EE-specific changes + +## 8.5.12 + + - No EE-specific changes + +## 8.5.11 + + - Fix vulnerability that made it possible to enumerate private projects belonging to group + +## 8.5.10 + + - No EE-specific changes + +## 8.5.9 + + - No EE-specific changes + +## 8.5.8 + + - GitLab Geo: Documentation + +## 8.5.7 + + - No EE-specific changes + +## 8.5.6 + + - No EE-specific changes + +## 8.5.5 + + - GitLab Geo: Repository synchronization between primary and secondary nodes + - Add documentation for GitLab Pages + - Fix importing projects from GitHub Enterprise Edition + - Fix syntax error in init file + - Only show group member roles if explicitly requested + - GitLab Geo: Improve GeoNodes Admin screen + - GitLab Geo: Avoid locking yourself out when adding a GeoNode + +## 8.5.4 + + - [Elastic][Security] Notes exposure + +## 8.5.3 + + - Prevent LDAP from downgrading a group's last owner + - Update gitlab-elastic-search gem to 0.0.11 + +## 8.5.2 + + - Update LDAP groups asynchronously + - Fix an issue when weight text was displayed in Issuable collapsed sidebar +## 8.5.2 + + - Fix importing projects from GitHub Enterprise Edition. + +## 8.5.1 + + - Fix adding pages domain to projects in groups + +## 8.5.0 (2016-02-22) + + - Fix Elasticsearch blob results linking to the wrong reference ID (Stan Hu) + - Show warning when mirror repository default branch could not be updated because it has diverged from upstream. + - More reliable wiki indexer + - GitLab Pages gets support for custom domain and custom certificate + - Fix of Elastic indexer. It should not trigger record validation for projects + - Fix of Elastic indexer. Stabilze indexer when serialized data is corrupted + - [Elastic] Don't index unnecessary data into elastic + +## 8.4.11 + + - No EE-specific changes + +## 8.4.10 + + - No EE-specific changes + +## 8.4.9 + + - Fix vulnerability that made it possible to enumerate private projects belonging to group + +## 8.4.8 + + - No EE-specific changes + +## 8.4.7 + + - No EE-specific changes + +## 8.4.6 + + - No EE-specific changes + +## 8.4.5 + + - Update LDAP groups asynchronously + +## 8.4.4 + + - Re-introduce "Send email to users" link in Admin area + - Fix category values for Jenkins and JenkinsDeprecated services + - Fix Elasticsearch indexing for newly added snippets + - Make Elasticsearch indexer more stable + - Update gitlab-elasticsearch-git to 0.0.10 which contain a few important fixes + +## 8.4.3 + + - Elasticsearch: fix partial blob indexing on push + - Elasticsearch: added advanced indexer for repositories + - Fix Mirror User dropdown + +## 8.4.2 + + - Elasticsearch indexer performance improvements + - Don't redirect away from Mirror Repository settings when repo is empty + - Fix updating of branches in mirrored repository + - Fix a 500 error preventing LDAP users with 2FA enabled from logging in + - Rake task gitlab:elastic:index_repositories handles errors and shows progress + - Partial indexing of repo on push (indexing changes only) + +## 8.4.1 + + - No EE-specific changes + +## 8.4.0 (2016-01-22) + + - Add ability to create a note for user by admin + - Fix "Commit was rejected by git hook", when max_file_size was set null in project's Git hooks + - Fix "Approvals are not reset after a new push is made if the request is coming from a fork" + - Fix "User is not automatically removed from suggested approvers list if user is deleted" + - Add option to enforce a semi-linear history by only allowing merge requests to be merged that have been rebased + - Add option to trigger builds when branches or tags are updated from a mirrored upstream repository + - Ability to use Elasticsearch as a search engine + +## 8.3.10 + + - No EE-specific changes + +## 8.3.9 + + - No EE-specific changes + +## 8.3.8 + + - Fix vulnerability that made it possible to enumerate private projects belonging to group + +## 8.3.7 + + - No EE-specific changes + +## 8.3.6 + + - No EE-specific changes + +## 8.3.5 + + - No EE-specific changes + +## 8.3.4 + + - No EE-specific changes + +## 8.3.3 + + - Fix undefined method call in Jenkins integration service + +## 8.3.2 + + - No EE-specific changes + +## 8.3.1 + + - Rename "Group Statistics" to "Contribution Analytics" + +## 8.3.0 (2015-12-22) + + - License information can now be retrieved via the API + - Show Kerberos clone url when Kerberos enabled and url different than HTTP url (Borja Aparicio) + - Fix bug with negative approvals required + - Add group contribution analytics page + - Add GitLab Pages + - Add group contribution statistics page + - Automatically import Kerberos identities from Active Directory when Kerberos is enabled (Alex Lossent) + - Canonicalization of Kerberos identities to always include realm (Alex Lossent) + +## 8.2.6 + + - No EE-specific changes + +## 8.2.5 + + - No EE-specific changes + +## 8.2.4 + + - No EE-specific changes + +## 8.2.3 + + - No EE-specific changes + +## 8.2.2 + + - Fix 404 in redirection after removing a project (Stan Hu) + - Ensure cached application settings are refreshed at startup (Stan Hu) + - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) + - Fix: Raw private snippets access workflow + - Prevent "413 Request entity too large" errors when pushing large files with LFS + - Ensure GitLab fires custom update hooks after commit via UI + +## 8.2.1 + + - Forcefully update builds that didn't want to update with state machine + - Fix: saving GitLabCiService as Admin Template + +## 8.2.0 (2015-11-22) + + - Invalidate stored jira password if the endpoint URL is changed + - Fix: Page is not reloaded periodically to check if rebase is finished + - When someone as marked as a required approver for a merge request, an email should be sent + - Allow configuring the Jira API path (Alex Lossent) + - Fix "Rebase onto master" + - Ensure a comment is properly recorded in JIRA when a merge request is accepted + - Allow groups to appear in the `Share with group` share if the group owner allows it + - Add option to mirror an upstream repository. + +## 8.1.4 + + - Fix bug in JIRA integration which prevented merge requests from being accepted when using issue closing pattern + +## 8.1.3 + + - Fix "Rebase onto master" + +## 8.1.2 + + - Prevent a 500 error related to the JIRA external issue tracker service + +## 8.1.1 + + - Removed, see 8.1.2 + +## 8.1.0 (2015-10-22) + + - Add documentation for "Share project with group" API call + - Added an issues template (Hannes Rosenögger) + - Add documentation for "Share project with group" API call + - Ability to disable 'Share with Group' feature (via UI and API) + +## 8.0.6 + + - No EE-specific changes + +## 8.0.5 + + - "Multi-project" and "Treat unstable builds as passing" parameters for + the Jenkins CI service are now correctly persisted. + - Correct the build URL when "Multi-project" is enabled for the Jenkins CI + service. + +## 8.0.4 + + - Fix multi-project setup for Jenkins + +## 8.0.3 + + - No EE-specific changes + +## 8.0.2 + + - No EE-specific changes + +## 8.0.1 + + - Correct gem dependency versions + - Re-add the "Help Text" feature that was inadvertently removed + +## 8.0.0 (2015-09-22) + + - Fix navigation issue when viewing Group Settings pages + - Guests and Reporters can approve merge request as well + - Add fast-forward merge option in project settings + - Separate rebase & fast-forward merge features + +## 7.14.3 + + - No changes + +## 7.14.2 + + - Fix the rebase before merge feature + +## 7.14.1 + + - Fix sign in form when just Kerberos is enabled + +## 7.14.0 (2015-08-22) + + - Disable adding, updating and removing members from a group that is synced with LDAP + - Don't send "Added to group" notifications when group is LDAP synched + - Fix importing projects from GitHub Enterprise Edition. + - Automatic approver suggestions (based on an authority of the code) + - Add support for Jenkins unstable status + - Automatic approver suggestions (based on an authority of the code) + - Support Kerberos ticket-based authentication for Git HTTP access + +## 7.13.3 + + - Merge community edition changes for version 7.13.3 + - Improved validation for an approver + - Don't resend admin email to everyone if one delivery fails + - Added migration for removing of invalid approvers + +## 7.13.2 + + - Fix group web hook + - Don't resend admin email to everyone if one delivery fails + +## 7.13.1 + + - Merge community edition changes for version 7.13.1 + - Fix: "Rebase before merge" doesn't work when source branch is in the same project + +## 7.13.0 (2015-07-22) + + - Fix git hook validation on initial push to master branch. + - Reset approvals on push + - Fix 500 error when the source project of an MR is deleted + - Ability to define merge request approvers + +## 7.12.2 + + - Fixed the alignment of project settings icons + +## 7.12.1 + + - No changes specific to EE + +## 7.12.0 (2015-06-22) + + - Fix error when viewing merge request with a commit that includes "Closes #". + - Enhance LDAP group synchronization to check also for member attributes that only contain "uid=" + - Enhance LDAP group synchronization to check also for submember attributes + - Prevent LDAP group sync from removing a group's last owner + - Add Git hook to validate maximum file size. + - Project setting: approve merge request by N users before accept + - Support automatic branch jobs created by Jenkins in CI Status + - Add API support for adding and removing LDAP group links + +## 7.11.4 + + - no changes specific to EE + +## 7.11.3 + + - Fixed an issue with git annex + +## 7.11.2 + + - Fixed license upload and verification mechanism + +## 7.11.0 (2015-05-22) + + - Skip git hooks commit validation when pushing new tag. + - Add Two-factor authentication (2FA) for LDAP logins + +## 7.10.1 + + - Check if comment exists in Jira before sending a reference + +## 7.10.0 (2015-04-22) + + - Improve UI for next pages: Group LDAP sync, Project git hooks, Project share with groups, Admin -> Appearance settigns + - Default git hooks for new projects + - Fix LDAP group links page by using new group members route. + - Skip email confirmation when updated via LDAP. + +## 7.9.0 (2015-03-22) + + - Strip prefixes and suffixes from synced SSH keys: + `SSHKey:ssh-rsa keykeykey` and `ssh-rsa keykeykey (SSH key)` will now work + - Check if LDAP admin group exists before querying for user membership + - Use one custom header logo for all GitLab themes in appearance settings + - Escape wildcards when searching LDAP by group name. + - Group level Web Hooks + - Don't allow project to be shared with the group it is already in. + +## 7.8.0 (2015-02-22) + + - Improved Jira issue closing integration + - Improved message logging for Jira integration + - Added option of referencing JIRA issues from GitLab + - Update Sidetiq to 0.6.3 + - Added Github Enterprise importer + - When project has MR rebase enabled, MR will have rebase checkbox selected by default + - Minor UI fixes for sidebar navigation + - Manage large binaries with git annex + +## 7.7.0 (2015-01-22) + + - Added custom header logo support (Drew Blessing) + - Fixed preview appearance bug + - Improve performance for selectboxes: project share page, admin email users page + +## 7.6.2 + + - Fix failing migrations for MySQL, LDAP + +## 7.6.1 + + - No changes + +## 7.6.0 (2014-12-22) + + - Added Audit events related to membership changes for groups and projects + - Added option to attempt a rebase before merging merge request + - Dont show LDAP groups settings if LDAP disabled + - Added member lock for groups to disallow membership additions on project level + - Rebase on merge request. Introduced merge request option to rebase before merging + - Better message for failed pushes because of git hooks + - Kerberos support for web interface and git HTTP + +## 7.5.3 + + - Only set up Sidetiq from a Sidekiq server process (fixes Redis::InheritedError) + +## 7.5.0 (2014-11-22) + + - Added an ability to check each author commit's email by regex + - Added an ability to restrict commit authors to existing Gitlab users + - Add an option for automatic daily LDAP user sync + - Added git hook for preventing tag removal to API + - Added git hook for setting commit message regex to API + - Added an ability to block commits with certain filenames by regex expression + - Improved a jenkins parser + +## 7.4.4 + + - Fix broken ldap migration + +## 7.4.0 (2014-10-22) + + - Support for multiple LDAP servers + - Skip AD specific LDAP checks + - Do not show ldap users in dropdowns for groups with enabled ldap-sync + - Update the JIRA integration documentation + - Reset the homepage to show the GitLab logo by deleting the custom logo. + +## 7.3.0 (2014-09-22) + + - Add an option to change the LDAP sync time from default 1 hour + - User will receive an email when unsubscribed from admin notifications + - Show group sharing members on /my/project/team + - Improve explanation of the LDAP permission reset + - Fix some navigation issues + - Added support for multiple LDAP groups per Gitlab group + +## 7.2.0 (2014-08-22) + + - Improve Redmine integration + - Better logging for the JIRA issue closing service + - Administrators can now send email to all users through the admin interface + - JIRA issue transition ID is now customizable + - LDAP group settings are now visible in admin group show page and group members page + +## 7.1.0 (2014-07-22) + + - Synchronize LDAP-enabled GitLab administrators with an LDAP group (Marvin Frick, sponsored by SinnerSchrader) + - Synchronize SSH keys with LDAP (Oleg Girko (Jolla) and Marvin Frick (SinnerSchrader)) + - Support Jenkins jobs with multiple modules (Marvin Frick, sponsored by SinnerSchrader) + +## 7.0.0 (2014-06-22) + + - Fix: empty brand images are displayed as empty image_tag on login page (Marvin Frick, sponsored by SinnerSchrader) + +## 6.9.4 + + - Fix bug in JIRA Issue closing triggered by commit messages + - Fix JIRA issue reference bug + +## 6.9.3 + + - Fix check CI status only when CI service is enabled(Daniel Aquino) + +## 6.9.2 + + - Merge community edition changes for version 6.9.2 + +## 6.9.1 + + - Merge community edition changes for version 6.9.1 + +## 6.9.0 (2014-05-22) + + - Add support for closing Jira tickets with commits and MR + - Template for Merge Request description can be added in project settings + - Jenkins CI service + - Fix LDAP email upper case bug + +## 6.8.0 (2014-04-22) + + - Customise sign-in page with custom text and logo + +## 6.7.1 + + - Handle LDAP errors in Adapter#dn_matches_filter? + +## 6.7.0 (2014-03-22) + + - Improve LDAP sign-in speed by reusing connections + - Add support for Active Directory nested LDAP groups + - Git hooks: Commit message regex + - Git hooks: Deny git tag removal + - Fix group edit in admin area + +## 6.6.0 (2014-02-22) + + - Permission reset button for LDAP groups + - Better performance with large numbers of users with access to one project + +## 6.5.0 (2014-01-22) + + - Add reset permissions button to Group#members page + +## 6.4.0 (2013-12-22) + + - Respect existing group permissions during sync with LDAP group (d3844662ec7ce816b0a85c8b40f66ee6c5ae90a1) + +## 6.3.0 (2013-11-22) + + - When looking up a user by DN, use single scope (bc8a875df1609728f1c7674abef46c01168a0d20) + - Try sAMAccountName if omniauth nickname is nil (9b7174c333fa07c44cc53b80459a115ef1856e38) + +## 6.2.0 (2013-10-22) + + - API: expose ldap_cn and ldap_access group attributes + - Use omniauth-ldap nickname attribute as GitLab username + - Improve group sharing UI for installation with many groups + - Fix empty LDAP group raises exception + - Respect LDAP user filter for git access diff --git a/CHANGELOG.md b/CHANGELOG.md index 6088a1b3515e6772bc7001edee60ff0bcdaf8664..aa050d5246a923d9b8b63c2126c1936f63df777c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,261 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.3.9 (2018-03-16) + +### Security (3 changes) + +- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337 +- Update nokogiri to 1.8.2. !16807 +- Fix GitLab Auth0 integration signing in the wrong user. + + +## 10.3.8 (2018-03-01) + +### Security (1 change) + +- Ensure that OTP backup codes are always invalidated. + + +## 10.3.7 (2018-02-05) + +### Security (4 changes) + +- Fix namespace access issue for GitHub, BitBucket, and GitLab.com project importers. +- Fix stored XSS in code blocks that ignore highlighting. +- Fix wilcard protected tags protecting all branches. +- Restrict Todo API mark_as_done endpoint to the user's todos only. + + +## 10.3.6 (2018-01-22) + +### Fixed (17 changes, 2 of them are from the community) + +- Fix abuse reports link url in admin area navbar. !16068 (megos) +- Fix gitlab-rake gitlab:import:repos import schedule. !16115 +- Fixing bug when wiki last version. !16197 +- Prevent excessive DB load due to faulty DeleteConflictingRedirectRoutes background migration. !16205 +- Default merge request title is set correctly again when external issue tracker is activated. !16356 (Ben305) +- Prevent invalid Route path if path is unchanged. !16397 +- Fixing rack request mime type when using rack attack. !16427 +- Prevent RevList failing on non utf8 paths. !16440 +- Fix 500 error when visiting a commit where the blobs do not exist. +- Fix viewing merge request diffs where the underlying blobs are unavailable. +- Gracefully handle garbled URIs in Markdown. +- Fix hooks not being set up properly for bare import Rake task. +- Fix Mermaid drawings not loading on some browsers. +- Fixed chanages dropdown ellipsis positioning. +- Avoid leaving a push event empty if payload cannot be created. +- Set target_branch to the ref branch when creating MR from issue. +- Fix shortcut links on help page. + + +## 10.3.5 (2018-01-18) + +- No changes. + +## 10.3.4 (2018-01-10) + +### Security (7 changes, 1 of them is from the community) + +- Prevent a SQL injection in the MilestonesFinder. +- Fix RCE via project import mechanism. +- Prevent OAuth login POST requests when a provider has been disabled. +- Filter out sensitive fields from the project services API. (Robert Schilling) +- Check user authorization for source and target projects when creating a merge request. +- Fix path traversal in gitlab-ci.yml cache:key. +- Fix writable shared deploy keys. + + +## 10.3.3 (2018-01-02) + +### Fixed (3 changes) + +- Fix links to old commits in merge request comments. +- Fix 404 errors after a user edits an issue description and solves the reCAPTCHA. +- Gracefully handle orphaned write deploy keys in /internal/post_receive. + + +## 10.3.2 (2017-12-28) + +### Fixed (1 change) + +- Fix migration for removing orphaned issues.moved_to_id values in MySQL and PostgreSQL. + + +## 10.3.1 (2017-12-27) + +### Fixed (3 changes) + +- Don't link LFS objects to a project when unlinking forks when they were already linked. !16006 +- Execute project hooks and services after commit when moving an issue. +- Fix Error 500s with anonymous clones for a project that has moved. + +### Changed (1 change) + +- Reduce the number of buckets in gitlab_cache_operation_duration_seconds metric. !15881 + + +## 10.3.0 (2017-12-22) + +### Security (1 change, 1 of them is from the community) + +- Upgrade jQuery to 2.2.4. !15570 (Takuya Noguchi) + +### Fixed (55 changes, 8 of them are from the community) + +- Fail jobs if its dependency is missing. !14009 +- Fix errors when selecting numeric-only labels in the labels autocomplete selector. !14607 (haseebeqx) +- Fix pipeline status transition for single manual job. This would also fix pipeline duration becuse it is depending on status transition. !15251 +- Fix acceptance of username for Mattermost service update. !15275 +- Set the default gitlab-shell timeout to 3 hours. !15292 +- Make sure a user can add projects to subgroups they have access to. !15294 +- OAuth identity lookups case-insensitive. !15312 +- Fix filter by my reaction is not working. !15345 (Hiroyuki Sato) +- Avoid deactivation when pipeline schedules execute a branch includes `[ci skip]` comment. !15405 +- Add recaptcha modal to issue updates detected as spam. !15408 +- Fix item name and namespace text overflow in Projects dropdown. !15451 +- Removed unused rake task, 'rake gitlab:sidekiq:drop_post_receive'. !15493 +- Fix commits page throwing 500 when the multi-file editor was enabled. !15502 +- Fix Issue comment submit button being disabled when pasting content from another GFM note. !15530 +- Reenable Prometheus metrics, add more control over Prometheus method instrumentation. !15558 +- Fix broadcast message not showing up on login page. !15578 +- Initializes the branches dropdown when the 'Start new pipeline' failed due to validation errors. !15588 (Christiaan Van den Poel) +- Fix merge requests where the source or target branch name matches a tag name. !15591 +- Create a fork network for forks with a deleted source. !15595 +- Fix search results when a filename would contain a special character. !15606 (haseebeqx) +- Strip leading & trailing whitespaces in CI/CD secret variable keys. !15615 +- Correctly link to a forked project from the new fork page. !15653 +- Fix the fork project functionality for projects with hashed storage. !15671 +- Added default order to UsersFinder. !15679 +- Fix graph notes number duplication. !15696 (Vladislav Kaverin) +- Fix updateEndpoint undefined error for issue_show app root. !15698 +- Change boards page boards_data absolute urls to paths. !15703 +- Using appropiate services in the API for managing forks. !15709 +- Confirming email with invalid token should no longer generate an error. !15726 +- fix #39233 - 500 in merge request. !15774 (Martin Nowak) +- Use Markdown styling for new project guidelines. !15785 (Markus Koller) +- Fix error during schema dump. !15866 +- Fix broken illustration images for monitoring page empty states. !15889 +- Make sure user email is read only when synced with LDAP. !15915 +- Fixed outdated browser flash positioning. +- Fix gitlab:import:repos Rake task moving repositories into the wrong location. +- Gracefully handle case when repository's root ref does not exist. +- Fix GitHub importer using removed interface. +- Align retry button with job title with new grid size. +- Fixed admin welcome screen new group path. +- Fix related branches/Merge requests failing to load when the hostname setting is changed. +- Init zen mode in snippets pages. +- Remove extra margin from wordmark in header. +- Fixed long commit links not wrapping correctly. +- Fixed deploy keys remove button loading state not resetting. +- Use app host instead of asset host when rendering image blob or diff. +- Hide log size for mobile screens. +- Fix sending notification emails to users with the mention level set who were mentioned in an issue or merge request description. +- Changed validation error message on wrong milestone dates. (Xurxo Méndez Pérez) +- Fix access to the final page of todos. +- Fixed new group milestone breadcrumbs. +- Fix image diff notification email from showing wrong content. +- Fixed merge request lock icon size. +- Make sure head pippeline always corresponds with the head sha of an MR. +- Prevent 500 error when inspecting job after trigger was removed. + +### Changed (14 changes, 2 of them are from the community) + +- Only owner or master can erase jobs. !15216 +- Allow password authentication to be disabled entirely. !15223 (Markus Koller) +- Add the option to automatically run a pipeline after updating AutoDevOps settings. !15380 +- Add total_time_spent to the `changes` hash in issuable Webhook payloads. !15381 +- Monitor NFS shards for circuitbreaker in a separate process. !15426 +- Add inline editing to issues on mobile. !15438 +- Add custom brand text on new project pages. !15541 (Markus Koller) +- Show only group name by default and put full namespace in tooltip in Groups tree. !15650 +- Use custom user agent header in all GCP API requests. !15705 +- Changed the deploy markers on the prometheus dashboard to be more verbose. !38032 +- Animate contextual sidebar on collapse/expand. +- Update emojis. Add :gay_pride_flag: and :speech_left:. Remove extraneous comma in :cartwheel_tone4:. +- When a custom header logo is present, don't show GitLab type logo. +- Improved diff changed files dropdown design. + +### Performance (19 changes) + +- Add timeouts for Gitaly calls. !15047 +- Performance issues when loading large number of wiki pages. !15276 +- Add performance logging to UpdateMergeRequestsWorker. !15360 +- Keep track of all circuitbreaker keys in a set. !15613 +- Improve the performance for counting commits. !15628 +- Reduce requests for project forks on show page of projects that have forks. !15663 +- Perform SQL matching of Build&Runner tags to greatly speed-up job picking. +- Only load branch names for protected branch checks. +- Optimize API /groups/:id/projects by preloading associations. +- Remove allocation tracking code from InfluxDB sampler for performance. +- Throttle the number of UPDATEs triggered by touch. +- Make finding most recent merge request diffs more efficient. +- Fetch blobs in bulk when generating diffs. +- Cache commits for MergeRequest diffs. +- Use fuzzy search with minimum length of 3 characters where appropriate. +- Add axios to common file. +- Remove template selector from global namespace. +- check the import_status field before doing SQL operations to check the import url. +- Stop sending milestone and labels data over the wire for MR widget requests. + +### Added (22 changes, 15 of them are from the community) + +- Limit autocomplete menu to applied labels. !11110 (Vitaliy @blackst0ne Klachkov) +- Make diff notes created on a commit in a merge request to persist a rebase. !12148 +- Allow creation of merge request from email. !13817 (janp) +- Add an ability to use a custom branch name on creation from issues. !13884 (Vitaliy @blackst0ne Klachkov) +- Add anonymous rate limit per IP, and authenticated (web or API) rate limits per user. !14708 +- Create a new form to add Existing Kubernetes Cluster. !14805 +- Add support of Mermaid (generation of diagrams and flowcharts from text). !15107 (Vitaliy @blackst0ne Klachkov) +- Add total time spent to milestones. !15116 (George Andrinopoulos) +- Add /groups/:id/subgroups endpoint to API. !15142 (marbemac) +- Add administrative endpoint to list all pages domains. !15160 (Travis Miller) +- Adds Rubocop rule for line break after guard clause. !15188 (Jacopo Beschi @jacopo-beschi) +- Add edit button to mobile file view. !15199 (Travis Miller) +- Add dropdown sort to group milestones. !15230 (George Andrinopoulos) +- added support for ordering and sorting in notes api. !15342 (haseebeqx) +- Hashed Storage migration script now supports migrating project attachments. !15352 +- New API endpoint - list jobs for a specified runner. !15432 +- Add new API endpoint - get a namespace by ID. !15442 +- Disables autocomplete in filtered searc. !15477 (Jacopo Beschi @jacopo-beschi) +- Update empty state page of merge request 'changes' tab. !15611 (Vitaliy @blackst0ne Klachkov) +- Allow git pull/push on group/user/project redirects. !15670 +- show status of gitlab reference links in wiki. !15694 (haseebeqx) +- Add email confirmation parameters for user creation and update via API. (Daniel Juarez) + +### Other (17 changes, 7 of them are from the community) + +- Enable UnnecessaryMantissa in scss-lint. !15255 (Takuya Noguchi) +- Add untracked files to uploads table. !15270 +- Move update_project_counter_caches? out of issue and merge request. !15300 (George Andrinopoulos) +- Removed tooltip from clone dropdown. !15334 +- Clean up empty fork networks. !15373 +- Create issuable destroy service. !15604 (George Andrinopoulos) +- Upgrade seed-fu to 2.3.7. !15607 (Takuya Noguchi) +- Rename GKE as Kubernetes Engine. !15608 (Takuya Noguchi) +- Prefer ci_config_path validation for leading slashes instead of sanitizing the input. !15672 (Christiaan Van den Poel) +- Fix typo in docs about Elasticsearch. !15699 (Takuya Noguchi) +- Add internationalization support for the prometheus integration. !33338 +- Export text utils functions as es6 module and add tests. +- Stop reloading the page when using pagination and tabs - use API calls - in Pipelines table. +- Clean up schema of the "issues" table. +- Clarify wording of protected branch settings for the default branch. +- Update svg external depencency. +- Clean up schema of the "merge_requests" table. + + +## 10.2.4 (2017-12-07) + +### Security (5 changes) + +- Fix e-mail address disclosure through member search fields +- Prevent creating issues through API when user does not have permissions +- Prevent an information disclosure in the Groups API +- Fix user without access to private Wiki being able to see it on the project page +- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment + + ## 10.2.3 (2017-11-30) ### Fixed (7 changes) @@ -237,6 +492,17 @@ entry. - Add Gitaly metrics to the performance bar. +## 10.1.5 (2017-12-07) + +### Security (5 changes) + +- Fix e-mail address disclosure through member search fields +- Prevent creating issues through API when user does not have permissions +- Prevent an information disclosure in the Groups API +- Fix user without access to private Wiki being able to see it on the project page +- Fix Cross-Site Scripting (XSS) vulnerability while editing a comment + + ## 10.1.4 (2017-11-14) ### Fixed (4 changes) @@ -391,7 +657,7 @@ entry. - [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] Re-arrange + + diff --git a/app/assets/javascripts/add_gitlab_slack_application/index.js b/app/assets/javascripts/add_gitlab_slack_application/index.js new file mode 100644 index 0000000000000000000000000000000000000000..4c698f3d227b615a9f4564b0ed9ee0f9df3ebc79 --- /dev/null +++ b/app/assets/javascripts/add_gitlab_slack_application/index.js @@ -0,0 +1,30 @@ +import Vue from 'vue'; +import AddGitlabSlackApplication from './components/add_gitlab_slack_application.vue'; + +function mountAddGitlabSlackApplication() { + const el = document.getElementById('js-add-gitlab-slack-application-entry-point'); + + if (!el) return; + + const dataNode = document.getElementById('js-add-gitlab-slack-application-entry-data'); + const initialData = JSON.parse(dataNode.innerHTML); + + const AddGitlabSlackApplicationComp = Vue.extend(AddGitlabSlackApplication); + + new AddGitlabSlackApplicationComp({ + propsData: { + projects: initialData.projects, + isSignedIn: initialData.is_signed_in, + gitlabForSlackGifPath: initialData.gitlab_for_slack_gif_path, + signInPath: initialData.sign_in_path, + slackLinkPath: initialData.slack_link_profile_slack_path, + gitlabLogoPath: initialData.gitlab_logo_path, + slackLogoPath: initialData.slack_logo_path, + docsPath: initialData.docs_path, + }, + }).$mount(el); +} + +document.addEventListener('DOMContentLoaded', mountAddGitlabSlackApplication); + +export default mountAddGitlabSlackApplication; diff --git a/app/assets/javascripts/add_gitlab_slack_application/services/gitlab_slack_service.js b/app/assets/javascripts/add_gitlab_slack_application/services/gitlab_slack_service.js new file mode 100644 index 0000000000000000000000000000000000000000..a1ee7760220534e79fb82b8bcec83c486eb8a877 --- /dev/null +++ b/app/assets/javascripts/add_gitlab_slack_application/services/gitlab_slack_service.js @@ -0,0 +1,11 @@ +import axios from '../../lib/utils/axios_utils'; + +export default { + addToSlack(url, projectId) { + return axios.get(url, { + params: { + project_id: projectId, + }, + }); + }, +}; diff --git a/app/assets/javascripts/admin_email_select.js b/app/assets/javascripts/admin_email_select.js new file mode 100644 index 0000000000000000000000000000000000000000..3f2477341ce47255183b5f59dd1fdbc645280ef4 --- /dev/null +++ b/app/assets/javascripts/admin_email_select.js @@ -0,0 +1,87 @@ +/* eslint-disable no-var, wrap-iife, func-names, space-before-function-paren, camelcase, no-unused-vars, quotes, object-shorthand, one-var, one-var-declaration-per-line, prefer-arrow-callback, comma-dangle, prefer-template, no-else-return, yoda, prefer-rest-params, prefer-spread, max-len */ +import Api from './api'; + +var slice = [].slice; + +window.AdminEmailSelect = (function() { + function AdminEmailSelect() { + $('.ajax-admin-email-select').each((function(_this) { + return function(i, select) { + var skip_ldap; + skip_ldap = $(select).hasClass('skip_ldap'); + return $(select).select2({ + placeholder: "Select group or project", + multiple: $(select).hasClass('multiselect'), + minimumInputLength: 0, + query: function(query) { + var group_result, project_result; + group_result = Api.groups(query.term, {}, function(groups) { + return groups; + }); + project_result = Api.projects(query.term, { + order_by: 'id', + membership: false + }, function(projects) { + return projects; + }); + return $.when(project_result, group_result).done(function(projects, groups) { + var all, data; + all = { + id: "all" + }; + data = [all].concat(groups[0], projects[0]); + return query.callback({ + results: data + }); + }); + }, + id: function(object) { + if (object.path_with_namespace) { + return "project-" + object.id; + } else if (object.path) { + return "group-" + object.id; + } else { + return "all"; + } + }, + formatResult: function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return _this.formatResult.apply(_this, args); + }, + formatSelection: function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return _this.formatSelection.apply(_this, args); + }, + dropdownCssClass: "ajax-admin-email-dropdown", + escapeMarkup: function(m) { + return m; + } + }); + }; + })(this)); + } + + AdminEmailSelect.prototype.formatResult = function(object) { + if (object.path_with_namespace) { + return "
" + object.name + "
" + object.path_with_namespace + "
"; + } else if (object.path) { + return "
" + object.name + "
" + object.path + "
"; + } else { + return "
All
All groups and projects
"; + } + }; + + AdminEmailSelect.prototype.formatSelection = function(object) { + if (object.path_with_namespace) { + return "Project: " + object.name; + } else if (object.path) { + return "Group: " + object.name; + } else { + return "All groups and projects"; + } + }; + + return AdminEmailSelect; +})(); diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index d963101028a789b31ec48ad906eb40f708c0f5d7..81fcfcd3ad2e6c68b849e691a627494b26930934 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -7,10 +7,11 @@ const Api = { groupProjectsPath: '/api/:version/groups/:id/projects.json', projectsPath: '/api/:version/projects.json', projectLabelsPath: '/:namespace_path/:project_path/labels', - groupLabelsPath: '/groups/:namespace_path/labels', + groupLabelsPath: '/groups/:namespace_path/-/labels', licensePath: '/api/:version/templates/licenses/:key', gitignorePath: '/api/:version/templates/gitignores/:key', gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key', + ldapGroupsPath: '/api/:version/ldap/:provider/groups.json', dockerfilePath: '/api/:version/templates/dockerfiles/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', usersPath: '/api/:version/users.json', @@ -29,7 +30,7 @@ const Api = { }, // Return groups list. Filtered by query - groups(query, options, callback) { + groups(query, options, callback = $.noop) { const url = Api.buildUrl(Api.groupsPath); return $.ajax({ url, @@ -192,6 +193,31 @@ const Api = { }); }, + approverUsers(search, options, callback = $.noop) { + const url = Api.buildUrl('/autocomplete/users.json'); + return $.ajax({ + url, + data: $.extend({ + search, + per_page: 20, + }, options), + dataType: 'json', + }).done(callback); + }, + + ldap_groups(query, provider, callback) { + const url = Api.buildUrl(this.ldapGroupsPath).replace(':provider', provider); + return Api.wrapAjaxCall({ + url, + data: Object.assign({ + search: query, + per_page: 20, + active: true, + }), + dataType: 'json', + }).then(groups => callback(groups)); + }, + buildUrl(url) { let urlRoot = ''; if (gon.relative_url_root != null) { diff --git a/app/assets/javascripts/application_settings.js b/app/assets/javascripts/application_settings.js new file mode 100644 index 0000000000000000000000000000000000000000..efa962cc3d417413ff04641dea2f371eef83a145 --- /dev/null +++ b/app/assets/javascripts/application_settings.js @@ -0,0 +1,16 @@ + +function ApplicationSettings() { + const usageDataUrl = $('.usage-data').data('endpoint'); + + $.ajax({ + type: 'GET', + url: usageDataUrl, + dataType: 'html', + success(html) { + $('.usage-data').html(html); + }, + }); +} + +window.gl = window.gl || {}; +window.gl.ApplicationSettings = ApplicationSettings; diff --git a/app/assets/javascripts/approvals.js b/app/assets/javascripts/approvals.js new file mode 100644 index 0000000000000000000000000000000000000000..f050fb3632f79249f0e758383844919af01c5d55 --- /dev/null +++ b/app/assets/javascripts/approvals.js @@ -0,0 +1,70 @@ +/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, quotes, no-var, comma-spacing, keyword-spacing, one-var, one-var-declaration-per-line, no-unused-vars, camelcase, prefer-template, max-len */ + +$(function() { + $(".approver-list").on("click", ".unsaved-approvers.approver .btn-remove", function(ev) { + var removeElement = $(this).closest("li"); + var approverId = parseInt(removeElement.attr("id").replace("user_",""), 10); + var approverIds = $("input#merge_request_approver_ids"); + var skipUsers = approverIds.data("skip-users") || []; + var approverIndex = skipUsers.indexOf(approverId); + + removeElement.remove(); + + if(approverIndex > -1) { + approverIds.data("skip-users", skipUsers.splice(approverIndex, 1)); + } + + ev.preventDefault(); + }); + + $(".approver-list").on("click", ".unsaved-approvers.approver-group .btn-remove", function(ev) { + var removeElement = $(this).closest("li"); + var approverGroupId = parseInt(removeElement.attr("id").replace("group_",""), 10); + var approverGroupIds = $("input#merge_request_approver_group_ids"); + var skipGroups = approverGroupIds.data("skip-groups") || []; + var approverGroupIndex = skipGroups.indexOf(approverGroupId); + + removeElement.remove(); + + if(approverGroupIndex > -1) { + approverGroupIds.data("skip-groups", skipGroups.splice(approverGroupIndex, 1)); + } + + ev.preventDefault(); + }); + + $("form.merge-request-form").submit(function() { + var approverIds, approversInput, approverGroupIds, approverGroupsInput; + + if ($("input#merge_request_approver_ids").length) { + approverIds = $.map($("li.unsaved-approvers.approver").not(".approver-template"), function(li, i) { + return li.id.replace("user_", ""); + }); + approversInput = $(this).find("input#merge_request_approver_ids"); + approverIds = approverIds.concat(approversInput.val().split(",")); + approversInput.val(_.compact(approverIds).join(",")); + } + + if ($("input#merge_request_approver_group_ids").length) { + approverGroupIds = $.map($("li.unsaved-approvers.approver-group"), function(li, i) { + return li.id.replace("group_", ""); + }); + approverGroupsInput = $(this).find("input#merge_request_approver_group_ids"); + approverGroupIds = approverGroupIds.concat(approverGroupsInput.val().split(",")); + approverGroupsInput.val(_.compact(approverGroupIds).join(",")); + } + }); + + return $(".suggested-approvers a").click(function() { + var approver_item_html, user_id, user_name; + user_id = this.id.replace("user_", ""); + user_name = this.text; + if ($(".approver-list #user_" + user_id).length) { + return false; + } + approver_item_html = $(".unsaved-approvers.approver-template").clone().removeClass("hide approver-template")[0].outerHTML.replace(/\{approver_name\}/g, user_name).replace(/\{user_id\}/g, user_id); + $(".no-approvers").remove(); + $(".approver-list").append(approver_item_html); + return false; + }); +}); diff --git a/app/assets/javascripts/approvers_select.js b/app/assets/javascripts/approvers_select.js new file mode 100644 index 0000000000000000000000000000000000000000..656ed95494e5cbc8e3bccfca0f1e3b7feedfc875 --- /dev/null +++ b/app/assets/javascripts/approvers_select.js @@ -0,0 +1,202 @@ +import Api from './api'; + +export default class ApproversSelect { + constructor() { + this.$approverSelect = $('.js-select-user-and-group'); + const name = this.$approverSelect.data('name'); + this.fieldNames = [`${name}[approver_ids]`, `${name}[approver_group_ids]`]; + this.$loadWrapper = $('.load-wrapper'); + + this.bindEvents(); + this.addEvents(); + this.initSelect2(); + } + + bindEvents() { + this.handleSelectChange = this.handleSelectChange.bind(this); + this.fetchGroups = this.fetchGroups.bind(this); + this.fetchUsers = this.fetchUsers.bind(this); + } + + addEvents() { + $(document).on('click', '.js-add-approvers', () => this.addApprover()); + $(document).on('click', '.js-approver-remove', e => ApproversSelect.removeApprover(e)); + } + + static getApprovers(fieldName, approverList) { + const input = $(`[name="${fieldName}"]`); + const existingApprovers = $(approverList).map((i, el) => + parseInt($(el).data('id'), 10), + ); + const selectedApprovers = input.val() + .split(',') + .filter(val => val !== ''); + return [...existingApprovers, ...selectedApprovers]; + } + + fetchGroups(term) { + const options = { + skip_groups: ApproversSelect.getApprovers(this.fieldNames[1], '.js-approver-group'), + }; + return Api.groups(term, options); + } + + fetchUsers(term) { + const options = { + skip_users: ApproversSelect.getApprovers(this.fieldNames[0], '.js-approver'), + project_id: $('#project_id').val(), + }; + return Api.approverUsers(term, options); + } + + handleSelectChange(e) { + const { added, removed } = e; + const userInput = $(`[name="${this.fieldNames[0]}"]`); + const groupInput = $(`[name="${this.fieldNames[1]}"]`); + + if (added) { + if (added.full_name) { + groupInput.val(`${groupInput.val()},${added.id}`.replace(/^,/, '')); + } else { + userInput.val(`${userInput.val()},${added.id}`.replace(/^,/, '')); + } + } + + if (removed) { + if (removed.full_name) { + groupInput.val(groupInput.val().replace(new RegExp(`,?${removed.id}`), '')); + } else { + userInput.val(userInput.val().replace(new RegExp(`,?${removed.id}`), '')); + } + } + } + + initSelect2() { + this.$approverSelect.select2({ + placeholder: 'Search for users or groups', + multiple: true, + minimumInputLength: 0, + query: (query) => { + const fetchGroups = this.fetchGroups(query.term); + const fetchUsers = this.fetchUsers(query.term); + return $.when(fetchGroups, fetchUsers).then((groups, users) => { + const data = { + results: groups[0].concat(users[0]), + }; + return query.callback(data); + }); + }, + formatResult: ApproversSelect.formatResult, + formatSelection: ApproversSelect.formatSelection, + dropdownCss() { + const $input = $('.js-select-user-and-group .select2-input'); + const offset = $input.offset(); + const inputRightPosition = offset.left + $input.outerWidth(); + const $dropdown = $('.select2-drop-active'); + + let left = offset.left; + if ($dropdown.outerWidth() > $input.outerWidth()) { + left = `${inputRightPosition - $dropdown.width()}px`; + } + return { + left, + right: 'auto', + width: 'auto', + }; + }, + }) + .on('change', this.handleSelectChange); + } + + static formatSelection(group) { + return group.full_name || group.name; + } + + static formatResult({ + name, + username, + avatar_url: avatarUrl, + full_name: fullName, + full_path: fullPath, + }) { + if (username) { + const avatar = avatarUrl || gon.default_avatar_url; + return ` +
+
+ +
+ +
+ `; + } + + return ` +
+
${fullName}
+
${fullPath}
+
+ `; + } + + addApprover() { + this.fieldNames.forEach(ApproversSelect.saveApprovers); + } + + static saveApprovers(fieldName) { + const $input = window.$(`[name="${fieldName}"]`); + const newValue = $input.val(); + const $loadWrapper = $('.load-wrapper'); + const $approverSelect = $('.js-select-user-and-group'); + + if (!newValue) { + return; + } + + const $form = $('.js-add-approvers').closest('form'); + $loadWrapper.removeClass('hidden'); + window.$.ajax({ + url: $form.attr('action'), + type: 'POST', + data: { + _method: 'PATCH', + [fieldName]: newValue, + }, + success: ApproversSelect.updateApproverList, + complete() { + $input.val(''); + $approverSelect.select2('val', ''); + $loadWrapper.addClass('hidden'); + }, + error() { + window.Flash('Failed to add Approver', 'alert'); + }, + }); + } + + static removeApprover(e) { + e.preventDefault(); + const target = e.currentTarget; + const $loadWrapper = $('.load-wrapper'); + $loadWrapper.removeClass('hidden'); + $.ajax({ + url: target.getAttribute('href'), + type: 'POST', + data: { + _method: 'DELETE', + }, + success: ApproversSelect.updateApproverList, + complete: () => $loadWrapper.addClass('hidden'), + error() { + window.Flash('Failed to remove Approver', 'alert'); + }, + }); + } + + static updateApproverList(html) { + $('.js-current-approvers').html($(html).find('.js-current-approvers').html()); + } +} diff --git a/app/assets/javascripts/audit_logs.js b/app/assets/javascripts/audit_logs.js new file mode 100644 index 0000000000000000000000000000000000000000..e0907d3024db52e8fb9a2f1e4a273782e92e31e3 --- /dev/null +++ b/app/assets/javascripts/audit_logs.js @@ -0,0 +1,43 @@ +/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props, no-new */ + +import UsersSelect from './users_select'; +import groupsSelect from './groups_select'; +import projectSelect from './project_select'; + +class AuditLogs { + constructor() { + this.initFilters(); + } + + initFilters() { + projectSelect(); + groupsSelect(); + new UsersSelect(); + + this.initFilterDropdown($('.js-type-filter'), 'event_type', null, () => { + $('.hidden-filter-value').val(''); + $('form.filter-form').submit(); + }); + + $('.project-item-select').on('click', () => { + $('form.filter-form').submit(); + }); + } + + initFilterDropdown($dropdown, fieldName, searchFields, cb) { + const dropdownOptions = { + fieldName, + selectable: true, + filterable: searchFields ? true : false, + search: { fields: searchFields }, + data: $dropdown.data('data'), + clicked: () => $dropdown.closest('form.filter-form').submit(), + }; + if (cb) { + dropdownOptions.clicked = cb; + } + $dropdown.glDropdown(dropdownOptions); + } +} + +export default AuditLogs; diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index b70b0a9bbf8465112d67b52652909227b9b609a8..8ef2c3954ea9933b4bf3b11915c5b633250985bd 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -5,6 +5,7 @@ // %button.js-toggle-button // %div.js-toggle-content // +import '~/lib/utils/url_utility'; $(() => { function toggleContainer(container, toggleState) { diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 20d23162940790053c141c86061c3b1d3500d60a..27f105e30319e1411e4d979dd259de101c5722d3 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -12,6 +12,7 @@ import './models/issue'; import './models/label'; import './models/list'; import './models/milestone'; +import './models/project'; import './models/assignee'; import './stores/boards_store'; import './stores/modal_store'; @@ -25,12 +26,18 @@ import './components/new_list_dropdown'; import './components/modal/index'; import '../vue_shared/vue_resource_interceptor'; +import './components/boards_selector'; +import collapseIcon from './icons/fullscreen_collapse.svg'; +import expandIcon from './icons/fullscreen_expand.svg'; +import tooltip from '../vue_shared/directives/tooltip'; + Vue.use(VueResource); $(() => { const $boardApp = document.getElementById('board-app'); const Store = gl.issueBoards.BoardsStore; const ModalStore = gl.issueBoards.ModalStore; + const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board'); window.gl = window.gl || {}; @@ -82,15 +89,17 @@ $(() => { eventHub.$on('newDetailIssue', this.updateDetailIssue); eventHub.$on('clearDetailIssue', this.clearDetailIssue); sidebarEventHub.$on('toggleSubscription', this.toggleSubscription); + sidebarEventHub.$on('updateWeight', this.updateWeight); }, beforeDestroy() { eventHub.$off('updateTokens', this.updateTokens); eventHub.$off('newDetailIssue', this.updateDetailIssue); eventHub.$off('clearDetailIssue', this.clearDetailIssue); sidebarEventHub.$off('toggleSubscription', this.toggleSubscription); + sidebarEventHub.$off('updateWeight', this.updateWeight); }, mounted () { - this.filterManager = new FilteredSearchBoards(Store.filter, true); + this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit); this.filterManager.setup(); Store.disabled = this.disabled; @@ -111,6 +120,7 @@ $(() => { this.state.lists = _.sortBy(this.state.lists, 'position'); Store.addBlankState(); + Store.addPromotionState(); this.loading = false; }) .catch(() => new Flash('An error occurred. Please try again.')); @@ -123,16 +133,20 @@ $(() => { const sidebarInfoEndpoint = newIssue.sidebarInfoEndpoint; if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { newIssue.setFetchingState('subscriptions', true); + newIssue.setFetchingState('weight', true); BoardService.getIssueInfo(sidebarInfoEndpoint) .then(res => res.json()) .then((data) => { newIssue.setFetchingState('subscriptions', false); + newIssue.setFetchingState('weight', false); newIssue.updateData({ subscribed: data.subscribed, + weight: data.weight, }); }) .catch(() => { newIssue.setFetchingState('subscriptions', false); + newIssue.setFetchingState('weight', false); Flash(__('An error occurred while fetching sidebar data')); }); } @@ -158,6 +172,24 @@ $(() => { Flash(__('An error occurred when toggling the notification subscription')); }); } + }, + updateWeight(newWeight, id) { + const issue = Store.detail.issue; + if (issue.id === id && issue.sidebarInfoEndpoint) { + issue.setLoadingState('weight', true); + BoardService.updateWeight(issue.sidebarInfoEndpoint, newWeight) + .then(res => res.json()) + .then((data) => { + issue.setLoadingState('weight', false); + issue.updateData({ + weight: data.weight, + }); + }) + .catch(() => { + issue.setLoadingState('weight', false); + Flash(__('An error occurred when updating the issue weight')); + }); + } } }, }); @@ -166,12 +198,56 @@ $(() => { el: document.getElementById('js-add-list'), data: { filters: Store.state.filters, + milestoneTitle: $boardApp.dataset.boardMilestoneTitle, }, mounted () { gl.issueBoards.newListDropdownInit(); }, }); + const configEl = document.querySelector('.js-board-config'); + + if (configEl) { + gl.boardConfigToggle = new Vue({ + el: configEl, + data() { + return { + canAdminList: this.$options.el.hasAttribute('data-can-admin-list'), + hasScope: this.$options.el.hasAttribute('data-has-scope'), + state: Store.state, + }; + }, + directives: { + tooltip, + }, + methods: { + showPage: page => gl.issueBoards.BoardsStore.showPage(page), + }, + computed: { + buttonText() { + return this.canAdminList ? 'Edit board' : 'View scope'; + }, + tooltipTitle() { + return this.hasScope ? __('This board\'s scope is reduced') : ''; + } + }, + template: ` +
+ +
+ `, + }); + } + gl.IssueBoardsModalAddBtn = new Vue({ mixins: [gl.issueBoards.ModalMixins], el: document.getElementById('js-add-issues-btn'), @@ -179,6 +255,9 @@ $(() => { return { modal: ModalStore.store, store: Store.state, + isFullscreen: false, + focusModeAvailable: $boardApp.hasAttribute('data-focus-mode-available'), + canAdminList: this.$options.el.hasAttribute('data-can-admin-list'), }; }, watch: { @@ -232,10 +311,58 @@ $(() => { :class="{ 'disabled': disabled }" :title="tooltipTitle" :aria-disabled="disabled" + v-if="canAdminList" @click="openModal"> Add issues `, }); + + gl.IssueBoardsToggleFocusBtn = new Vue({ + el: document.getElementById('js-toggle-focus-btn'), + data: { + modal: ModalStore.store, + store: Store.state, + isFullscreen: false, + focusModeAvailable: $boardApp.hasAttribute('data-focus-mode-available'), + }, + methods: { + toggleFocusMode() { + if (!this.focusModeAvailable) { return; } + + $(this.$refs.toggleFocusModeButton).tooltip('hide'); + issueBoardsContent.classList.toggle('is-focused'); + + this.isFullscreen = !this.isFullscreen; + }, + }, + template: ` + + `, + }); + + gl.IssueboardsSwitcher = new Vue({ + el: '#js-multiple-boards-switcher', + components: { + 'boards-selector': gl.issueBoards.BoardsSelector, + } + }); }); diff --git a/app/assets/javascripts/boards/components/assignee_select.vue b/app/assets/javascripts/boards/components/assignee_select.vue new file mode 100644 index 0000000000000000000000000000000000000000..cdbd12944a7b579098e3a6c05fb59eec03f22530 --- /dev/null +++ b/app/assets/javascripts/boards/components/assignee_select.vue @@ -0,0 +1,194 @@ + + + diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index adb7360327c6cd82a8b26d2e2e6decbfbd85e11e..86a4cbe89d046e56b53673f36fc29357c1223691 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -1,6 +1,7 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* global Sortable */ import Vue from 'vue'; +import boardPromotionState from 'ee/boards/components/board_promotion_state'; import AccessorUtilities from '../../lib/utils/accessor'; import boardList from './board_list'; import boardBlankState from './board_blank_state'; @@ -17,6 +18,7 @@ gl.issueBoards.Board = Vue.extend({ boardList, 'board-delete': gl.issueBoards.BoardDelete, boardBlankState, + boardPromotionState, }, props: { list: Object, diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 0b220a56e0bf94ccfdfc6bf4ec8fe7866c0de748..6bc9b836de81f9ff225a9c038e61817c4fca8a8e 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -16,6 +16,7 @@ export default { disabled: Boolean, index: Number, rootPath: String, + groupId: Number, }, data() { return { @@ -65,6 +66,7 @@ export default { :list="list" :issue="issue" :issue-link-base="issueLinkBase" + :group-id="groupId" :root-path="rootPath" :update-filters="true" /> diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue new file mode 100644 index 0000000000000000000000000000000000000000..543bea25d01b755ced9b758ecf079dc721c39db7 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -0,0 +1,278 @@ + + + diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js index 29aeb8e84aac648f12a1a021a7760488d0967da1..77377d9e2133c9ab92e4a4350185fcf1a8c7cae7 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.js @@ -9,6 +9,11 @@ const Store = gl.issueBoards.BoardsStore; export default { name: 'BoardList', props: { + groupId: { + type: Number, + required: false, + default: 0, + }, disabled: { type: Boolean, required: true, @@ -77,7 +82,7 @@ export default { this.showIssueForm = !this.showIssueForm; }, onScroll() { - if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { + if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) { this.loadNextPage(); } }, @@ -160,15 +165,19 @@ export default { template: `
    diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js index bc28f7f45f46381b108cc04e8524c2917ee5959d..b813943518c5736931c96fdd908aa883a42e8544 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js +++ b/app/assets/javascripts/boards/components/board_new_issue.js @@ -1,11 +1,17 @@ /* global ListIssue */ import eventHub from '../eventhub'; +import ProjectSelect from './project_select.vue'; const Store = gl.issueBoards.BoardsStore; export default { name: 'BoardNewIssue', props: { + groupId: { + type: Number, + required: false, + default: 0, + }, list: { type: Object, required: true, @@ -15,8 +21,20 @@ export default { return { title: '', error: false, + selectedProject: {}, }; }, + components: { + 'project-select': ProjectSelect, + }, + computed: { + disabled() { + if (this.groupId) { + return this.title === '' || !this.selectedProject.name; + } + return this.title === ''; + }, + }, methods: { submit(e) { e.preventDefault(); @@ -30,6 +48,7 @@ export default { labels, subscribed: true, assignees: [], + project_id: this.selectedProject.id, }); eventHub.$emit(`scroll-board-list-${this.list.id}`); @@ -58,43 +77,53 @@ export default { this.title = ''; eventHub.$emit(`hide-issue-form-${this.list.id}`); }, + setSelectedProject(selectedProject) { + this.selectedProject = selectedProject; + }, }, mounted() { this.$refs.input.focus(); + eventHub.$on('setSelectedProject', this.setSelectedProject); }, template: ` -
    -
    -
    -
    - An error occurred. Please try again. +
    +
    + +
    +
    + An error occurred. Please try again. +
    +
    + + + +
    + +
    -
    - - -
    - - -
    - + +
    `, }; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index faa76da964f3def695348d8a427e85f197e9f1d4..5300535f3e68b676ef2509b80798ee05f3faae35 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -3,6 +3,7 @@ /* global Sidebar */ import Vue from 'vue'; +import weight from 'ee/sidebar/components/weight/weight.vue'; import Flash from '../../flash'; import eventHub from '../../sidebar/event_hub'; import assigneeTitle from '../../sidebar/components/assignees/assignee_title'; @@ -124,5 +125,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({ assignees, removeBtn: gl.issueBoards.RemoveIssueBtn, subscriptions, + weight, }, }); diff --git a/app/assets/javascripts/boards/components/boards_selector.js b/app/assets/javascripts/boards/components/boards_selector.js new file mode 100644 index 0000000000000000000000000000000000000000..87f7595373880e56b3bfaffab51459eb9dda3e65 --- /dev/null +++ b/app/assets/javascripts/boards/components/boards_selector.js @@ -0,0 +1,131 @@ +import Vue from 'vue'; +import { throttle } from 'underscore'; +import BoardForm from './board_form.vue'; + +(() => { + window.gl = window.gl || {}; + window.gl.issueBoards = window.gl.issueBoards || {}; + + const Store = gl.issueBoards.BoardsStore; + + Store.createNewListDropdownData(); + + gl.issueBoards.BoardsSelector = Vue.extend({ + name: 'boards-selector', + components: { + BoardForm, + }, + props: { + currentBoard: { + type: Object, + required: true, + }, + milestonePath: { + type: String, + required: true, + }, + throttleDuration: { + type: Number, + default: 200, + }, + }, + data() { + return { + open: false, + loading: true, + hasScrollFade: false, + scrollFadeInitialized: false, + boards: [], + state: Store.state, + throttledSetScrollFade: throttle(this.setScrollFade, this.throttleDuration), + contentClientHeight: 0, + maxPosition: 0, + }; + }, + watch: { + reload() { + if (this.reload) { + this.boards = []; + this.loading = true; + this.reload = false; + + this.loadBoards(false); + } + }, + }, + computed: { + currentPage() { + return this.state.currentPage; + }, + reload: { + get() { + return this.state.reload; + }, + set(newValue) { + this.state.reload = newValue; + }, + }, + board() { + return this.state.currentBoard; + }, + showDelete() { + return this.boards.length > 1; + }, + scrollFadeClass() { + return { + 'fade-out': !this.hasScrollFade, + }; + }, + }, + methods: { + showPage(page) { + this.state.reload = false; + this.state.currentPage = page; + }, + toggleDropdown() { + this.open = !this.open; + }, + loadBoards(toggleDropdown = true) { + if (toggleDropdown) { + this.toggleDropdown(); + } + + if (this.open && !this.boards.length) { + gl.boardService.allBoards() + .then(res => res.json()) + .then((json) => { + this.loading = false; + this.boards = json; + }) + .then(() => this.$nextTick()) // Wait for boards list in DOM + .then(this.setScrollFade) + .catch(() => { + this.loading = false; + }); + } + }, + isScrolledUp() { + const { content } = this.$refs; + const currentPosition = this.contentClientHeight + content.scrollTop; + + return content && currentPosition < this.maxPosition; + }, + initScrollFade() { + this.scrollFadeInitialized = true; + + const { content } = this.$refs; + + this.contentClientHeight = content.clientHeight; + this.maxPosition = content.scrollHeight; + }, + setScrollFade() { + if (!this.scrollFadeInitialized) this.initScrollFade(); + + this.hasScrollFade = this.isScrolledUp(); + }, + }, + created() { + this.state.currentBoard = this.currentBoard; + }, + }); +})(); diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index bf474879024498902a644584fb5b72bfa56a3672..fc2bad2415f3e8b0f44d32c87a8d5e3be8e1be30 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -31,6 +31,10 @@ gl.issueBoards.IssueCardInner = Vue.extend({ required: false, default: false, }, + groupId: { + type: Number, + required: false, + }, }, data() { return { @@ -64,7 +68,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({ return this.issue.assignees.length > this.numberOverLimit; }, cardUrl() { - return `${this.issueLinkBase}/${this.issue.iid}`; + let baseUrl = this.issueLinkBase; + + if (this.groupId && this.issue.project) { + baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path); + } + + return `${baseUrl}/${this.issue.iid}`; }, issueId() { if (this.issue.iid) { @@ -148,7 +158,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({ class="card-number" v-if="issueId" > - {{ issueId }} + {{ issueId }}
    diff --git a/app/assets/javascripts/boards/components/labels_select.vue b/app/assets/javascripts/boards/components/labels_select.vue new file mode 100644 index 0000000000000000000000000000000000000000..b82a89883588fca42304fe9580c33d148a7abb48 --- /dev/null +++ b/app/assets/javascripts/boards/components/labels_select.vue @@ -0,0 +1,157 @@ + + + diff --git a/app/assets/javascripts/boards/components/milestone_select.vue b/app/assets/javascripts/boards/components/milestone_select.vue new file mode 100644 index 0000000000000000000000000000000000000000..64190d30441fad43ba37c217503f91f260dd2625 --- /dev/null +++ b/app/assets/javascripts/boards/components/milestone_select.vue @@ -0,0 +1,150 @@ + + + diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js index 182957113a27a788cdad7c4403b313ef0fb51a8f..c9acf6384ad23b2793a919d0bdacf30b81b10e3d 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js +++ b/app/assets/javascripts/boards/components/modal/footer.js @@ -31,10 +31,16 @@ gl.issueBoards.ModalFooter = Vue.extend({ const list = this.modal.selectedList || this.state.lists[firstListIndex]; const selectedIssues = ModalStore.getSelectedIssues(); const issueIds = selectedIssues.map(issue => issue.id); + const currentBoard = this.state.currentBoard; + const boardLabelIds = currentBoard.labels.map(label => label.id); + const assigneeIds = currentBoard.assignee && [currentBoard.assignee.id]; // Post the data to the backend gl.boardService.bulkUpdate(issueIds, { - add_label_ids: [list.label.id], + add_label_ids: [list.label.id, ...boardLabelIds], + milestone_id: currentBoard.milestone_id, + assignee_ids: assigneeIds, + weight: currentBoard.weight, }).catch(() => { new Flash('Failed to update issues, please try again.', 'alert'); diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue new file mode 100644 index 0000000000000000000000000000000000000000..a3bbed842a409294f0d565c734b1939193197b07 --- /dev/null +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -0,0 +1,103 @@ + + + diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js index 1ad972119349e06c4268bbad7c46708b54410ea7..05683984ff5208e7bfd5836e22988ccd6c91f395 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js @@ -25,25 +25,49 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ }, computed: { updateUrl() { - return this.issueUpdate; + return this.issueUpdate.replace(':project_path', this.issue.project.path); }, }, methods: { removeIssue() { + const board = Store.state.currentBoard; const issue = this.issue; const lists = issue.getLists(); + const boardLabelIds = board.labels.map(label => label.id); const listLabelIds = lists.map(list => list.label.id); - let labelIds = this.issue.labels + + let labelIds = issue.labels .map(label => label.id) - .filter(id => !listLabelIds.includes(id)); + .filter(id => !listLabelIds.includes(id)) + .filter(id => !boardLabelIds.includes(id)); if (labelIds.length === 0) { labelIds = ['']; } + + let assigneeIds = issue.assignees + .map(assignee => assignee.id) + .filter(id => id !== board.assignee.id); + if (assigneeIds.length === 0) { + // for backend to explicitly set No Assignee + assigneeIds = ['0']; + } + const data = { issue: { label_ids: labelIds, + assignee_ids: assigneeIds, }, }; + + if (board.milestone_id) { + data.issue.milestone_id = -1; + } + + if (board.weight) { + data.issue.weight = null; + } + + // Post the remove data Vue.http.patch(this.updateUrl, data).catch(() => { new Flash('Failed to remove issue from board, please try again.', 'alert'); diff --git a/app/assets/javascripts/boards/components/weight_select.vue b/app/assets/javascripts/boards/components/weight_select.vue new file mode 100644 index 0000000000000000000000000000000000000000..1afbc046d1ca96952173c836d6f8277df4a14668 --- /dev/null +++ b/app/assets/javascripts/boards/components/weight_select.vue @@ -0,0 +1,142 @@ + + + diff --git a/app/assets/javascripts/boards/icons/fullscreen_collapse.svg b/app/assets/javascripts/boards/icons/fullscreen_collapse.svg new file mode 100644 index 0000000000000000000000000000000000000000..6bd773dc4c5a8a306e823015e596b0f2a2d17c69 --- /dev/null +++ b/app/assets/javascripts/boards/icons/fullscreen_collapse.svg @@ -0,0 +1 @@ + diff --git a/app/assets/javascripts/boards/icons/fullscreen_expand.svg b/app/assets/javascripts/boards/icons/fullscreen_expand.svg new file mode 100644 index 0000000000000000000000000000000000000000..306073b8af2ba32f1f590f600bd0f28cb1c5fa53 --- /dev/null +++ b/app/assets/javascripts/boards/icons/fullscreen_expand.svg @@ -0,0 +1 @@ + diff --git a/app/assets/javascripts/boards/mixins/extra_milestones.js b/app/assets/javascripts/boards/mixins/extra_milestones.js new file mode 100644 index 0000000000000000000000000000000000000000..a2de065af8a5ab6f088dfd680a70fc4c6c24cff6 --- /dev/null +++ b/app/assets/javascripts/boards/mixins/extra_milestones.js @@ -0,0 +1,14 @@ +export default [ + { + id: null, + title: 'Any Milestone', + }, + { + id: -2, + title: 'Upcoming', + }, + { + id: -3, + title: 'Started', + }, +]; diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 81edd95bf2ba336c28cd46bb1d2a60352bda09b7..f3b3355080fef5cc0dd9c7cb19e8a4297de66247 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -4,6 +4,7 @@ /* global ListAssignee */ import Vue from 'vue'; +import IssueProject from './project'; class ListIssue { constructor (obj, defaultAvatar) { @@ -19,10 +20,20 @@ class ListIssue { this.position = obj.relative_position || Infinity; this.isFetching = { subscriptions: true, + weight: true, + }; + this.isLoading = { + weight: false, }; - this.isLoading = {}; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; + this.milestone_id = obj.milestone_id; + this.project_id = obj.project_id; + this.weight = obj.weight; + + if (obj.project) { + this.project = new IssueProject(obj.project); + } if (obj.milestone) { this.milestone = new ListMilestone(obj.milestone); @@ -105,7 +116,8 @@ class ListIssue { data.issue.label_ids = ['']; } - return Vue.http.patch(url, data); + const projectPath = this.project ? this.project.path : ''; + return Vue.http.patch(url.replace(':project_path', projectPath), data); } } diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index df2809e18052ae9980c2be14dbdb131e64dfd61c..a9734189fafc21afab3b9089a2650dc1fb7ab473 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -12,7 +12,7 @@ class List { this.position = obj.position; this.title = obj.title; this.type = obj.list_type; - this.preset = ['backlog', 'closed', 'blank'].indexOf(this.type) > -1; + this.preset = ['backlog', 'closed', 'blank', 'promotion'].indexOf(this.type) > -1; this.isExpandable = ['backlog', 'closed'].indexOf(this.type) > -1; this.isExpanded = true; this.page = 1; @@ -26,7 +26,7 @@ class List { this.label = new ListLabel(obj.label); } - if (this.type !== 'blank' && this.id) { + if (this.type !== 'blank' && this.type !== 'promotion' && this.id) { this.getIssues().catch(() => { // TODO: handle request error }); @@ -112,7 +112,10 @@ class List { .then((data) => { issue.id = data.id; issue.iid = data.iid; + issue.milestone = data.milestone; issue.project = data.project; + issue.assignees = data.assignees; + issue.labels = data.labels; if (this.issuesSize > 1) { const moveBeforeId = this.issues[1].id; diff --git a/app/assets/javascripts/boards/models/project.js b/app/assets/javascripts/boards/models/project.js new file mode 100644 index 0000000000000000000000000000000000000000..a3d5c7af7acee96301b8abb037df7824ed8a47d7 --- /dev/null +++ b/app/assets/javascripts/boards/models/project.js @@ -0,0 +1,6 @@ +export default class IssueProject { + constructor(obj) { + this.id = obj.id; + this.path = obj.path; + } +} diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index fa7ddd25e1fdd0910f3352fa5a6b9f540ad069f2..bf5fc3fb20c091e8d70d476546ce7135ac79b0ea 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -25,6 +25,35 @@ export default class BoardService { }); } + allBoards () { + return this.boards.get(); + } + + createBoard (board) { + board.label_ids = (board.labels || []).map(b => b.id); + + if (board.label_ids.length === 0) { + board.label_ids = ['']; + } + + if (board.assignee) { + board.assignee_id = board.assignee.id; + } + + if (board.milestone) { + board.milestone_id = board.milestone.id; + } + + if (board.id) { + return this.boards.update({ id: board.id }, { board }); + } + return this.boards.save({}, { board }); + } + + deleteBoard ({ id }) { + return this.boards.delete({ id }); + } + all () { return this.lists.get(); } @@ -93,6 +122,14 @@ export default class BoardService { return Vue.http.get(endpoint); } + static updateWeight(endpoint, weight = null) { + return Vue.http.put(endpoint, { + 'issue[weight]': weight, + }, { + emulateJSON: true, + }); + } + static toggleIssueSubscription(endpoint) { return Vue.http.post(endpoint); } diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 798d7e0d147d770422e1e14d25b5ddbcb5088daa..97625b683981929a88c7988798ffd2a2164039c3 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -2,6 +2,7 @@ /* global List */ import _ from 'underscore'; import Cookies from 'js-cookie'; +import boardsStoreEE from 'ee/boards/stores/boards_store_ee'; import { getUrlParamsArray } from '../../lib/utils/common_utils'; window.gl = window.gl || {}; @@ -12,7 +13,13 @@ gl.issueBoards.BoardsStore = { filter: { path: '', }, - state: {}, + state: { + currentBoard: { + labels: [], + }, + currentPage: '', + reload: false, + }, detail: { issue: {}, }, @@ -27,6 +34,17 @@ gl.issueBoards.BoardsStore = { issue: {}, }; }, + createNewListDropdownData() { + this.state.currentBoard = { + labels: [], + }; + this.state.currentPage = ''; + this.state.reload = false; + }, + showPage(page) { + this.state.reload = false; + this.state.currentPage = page; + }, addList (listObj, defaultAvatar) { const list = new List(listObj, defaultAvatar); this.state.lists.push(list); @@ -130,7 +148,13 @@ gl.issueBoards.BoardsStore = { return list[key] === val && byType; })[0]; }, - updateFiltersUrl () { - history.pushState(null, null, `?${this.filter.path}`); - } + updateFiltersUrl (replaceState = false) { + if (replaceState) { + history.replaceState(null, null, `?${this.filter.path}`); + } else { + history.pushState(null, null, `?${this.filter.path}`); + } + }, }; + +boardsStoreEE.initEESpecific(gl.issueBoards.BoardsStore); diff --git a/app/assets/javascripts/burndown_chart/burndown_chart.js b/app/assets/javascripts/burndown_chart/burndown_chart.js new file mode 100644 index 0000000000000000000000000000000000000000..e37e0fe17dce62e55597c4cdc18883d7e51e6656 --- /dev/null +++ b/app/assets/javascripts/burndown_chart/burndown_chart.js @@ -0,0 +1,367 @@ +import d3 from 'd3'; + +const margin = { top: 5, right: 65, bottom: 30, left: 50 }; +const parseDate = d3.time.format('%Y-%m-%d').parse; +const bisectDate = d3.bisector(d => d.date).left; +const tooltipPadding = { x: 8, y: 3 }; +const tooltipDistance = 15; + +export default class BurndownChart { + constructor({ container, startDate, dueDate }) { + this.canvas = d3.select(container).append('svg') + .attr('height', '100%') + .attr('width', '100%'); + + // create svg nodes + this.chartGroup = this.canvas.append('g').attr('class', 'chart'); + this.xAxisGroup = this.chartGroup.append('g').attr('class', 'x axis'); + this.yAxisGroup = this.chartGroup.append('g').attr('class', 'y axis'); + this.idealLinePath = this.chartGroup.append('path').attr('class', 'ideal line'); + this.actualLinePath = this.chartGroup.append('path').attr('class', 'actual line'); + + this.xAxisGroup.append('line').attr('class', 'domain-line'); + + // create y-axis label + this.label = 'Remaining'; + const yAxisLabel = this.yAxisGroup.append('g').attr('class', 'axis-label'); + this.yAxisLabelText = yAxisLabel.append('text').text(this.label); + this.yAxisLabelBBox = this.yAxisLabelText.node().getBBox(); + this.yAxisLabelLineA = yAxisLabel.append('line'); + this.yAxisLabelLineB = yAxisLabel.append('line'); + + // create chart legend + this.chartLegendGroup = this.chartGroup.append('g').attr('class', 'legend'); + this.chartLegendGroup.append('rect'); + + this.chartLegendIdealKey = this.chartLegendGroup.append('g'); + this.chartLegendIdealKey.append('line').attr('class', 'ideal line'); + this.chartLegendIdealKey.append('text').text('Guideline'); + this.chartLegendIdealKeyBBox = this.chartLegendIdealKey.select('text').node().getBBox(); + + this.chartLegendActualKey = this.chartLegendGroup.append('g'); + this.chartLegendActualKey.append('line').attr('class', 'actual line'); + this.chartLegendActualKey.append('text').text('Progress'); + this.chartLegendActualKeyBBox = this.chartLegendActualKey.select('text').node().getBBox(); + + // create tooltips + this.chartFocus = this.chartGroup.append('g').attr('class', 'focus').style('display', 'none'); + this.chartFocus.append('circle').attr('r', 4); + this.tooltipGroup = this.chartFocus.append('g').attr('class', 'chart-tooltip'); + this.tooltipGroup.append('rect').attr('rx', 3).attr('ry', 3); + this.tooltipGroup.append('text'); + + this.chartOverlay = this.chartGroup.append('rect').attr('class', 'overlay') + .on('mouseover', () => this.chartFocus.style('display', null)) + .on('mouseout', () => this.chartFocus.style('display', 'none')) + .on('mousemove', () => this.handleMousemove()); + + // parse start and due dates + this.startDate = parseDate(startDate); + this.dueDate = parseDate(dueDate); + + // get width and height + const dimensions = this.canvas.node().getBoundingClientRect(); + this.width = dimensions.width; + this.height = dimensions.height; + this.chartWidth = this.width - (margin.left + margin.right); + this.chartHeight = this.height - (margin.top + margin.bottom); + + // set default scale domains + this.xMax = this.dueDate; + this.yMax = 1; + + // create scales + this.xScale = d3.time.scale() + .range([0, this.chartWidth]) + .domain([this.startDate, this.xMax]); + + this.yScale = d3.scale.linear() + .range([this.chartHeight, 0]) + .domain([0, this.yMax]); + + // create axes + this.xAxis = d3.svg.axis() + .scale(this.xScale) + .orient('bottom') + .tickFormat(d3.time.format('%b %-d')) + .tickPadding(6) + .tickSize(4, 0); + + this.yAxis = d3.svg.axis() + .scale(this.yScale) + .orient('left') + .tickPadding(6) + .tickSize(4, 0); + + // create lines + this.line = d3.svg.line() + .x(d => this.xScale(d.date)) + .y(d => this.yScale(d.value)); + + // render the chart + this.scheduleRender(); + } + + // set data and force re-render + setData(data, { label = 'Remaining', animate } = {}) { + this.data = data.map(datum => ({ + date: parseDate(datum[0]), + value: parseInt(datum[1], 10), + })).sort((a, b) => (a.date - b.date)); + + // adjust axis domain to correspond with data + this.xMax = Math.max(d3.max(this.data, d => d.date) || 0, this.dueDate); + this.yMax = d3.max(this.data, d => d.value) || 1; + + this.xScale.domain([this.startDate, this.xMax]); + this.yScale.domain([0, this.yMax]); + + // calculate the bounding box for the axis label if updated + // (this must be done here to prevent layout thrashing) + if (this.label !== label) { + this.label = label; + this.yAxisLabelBBox = this.yAxisLabelText.text(label).node().getBBox(); + } + + // set ideal line data + if (this.data.length > 1) { + const idealStart = this.data[0] || { date: this.startDate, value: 0 }; + const idealEnd = { date: this.dueDate, value: 0 }; + this.idealData = [idealStart, idealEnd]; + } + + this.scheduleLineAnimation = !!animate; + this.scheduleRender(); + } + + handleMousemove() { + if (!this.data) return; + + const mouseOffsetX = d3.mouse(this.chartOverlay.node())[0]; + const dateOffset = this.xScale.invert(mouseOffsetX); + const i = bisectDate(this.data, dateOffset, 1); + const d0 = this.data[i - 1]; + const d1 = this.data[i]; + if (d1 == null || dateOffset - d0.date < d1.date - dateOffset) { + this.renderTooltip(d0); + } else { + this.renderTooltip(d1); + } + } + + // reset width and height to match the svg element, then re-render if necessary + handleResize() { + const dimensions = this.canvas.node().getBoundingClientRect(); + if (this.width !== dimensions.width || this.height !== dimensions.height) { + this.width = dimensions.width; + this.height = dimensions.height; + + // adjust axis range to correspond with chart size + this.chartWidth = this.width - (margin.left + margin.right); + this.chartHeight = this.height - (margin.top + margin.bottom); + + this.xScale.range([0, this.chartWidth]); + this.yScale.range([this.chartHeight, 0]); + + this.scheduleRender(); + } + } + + scheduleRender() { + if (this.queuedRender == null) { + this.queuedRender = requestAnimationFrame(() => this.render()); + } + } + + render() { + this.queuedRender = null; + this.renderedTooltipPoint = null; // force tooltip re-render + + this.xAxis.ticks(Math.floor(this.chartWidth / 120)); + this.yAxis.ticks(Math.min(Math.floor(this.chartHeight / 60), this.yMax)); + + this.chartGroup.attr('transform', `translate(${margin.left}, ${margin.top})`); + this.xAxisGroup.attr('transform', `translate(0, ${this.chartHeight})`); + + this.xAxisGroup.call(this.xAxis); + this.yAxisGroup.call(this.yAxis); + + // replace x-axis line with one which continues into the right margin + this.xAxisGroup.select('.domain').remove(); + this.xAxisGroup.select('.domain-line').attr('x1', 0).attr('x2', this.chartWidth + margin.right); + + // update y-axis label + const axisLabelOffset = (this.yAxisLabelBBox.height / 2) - margin.left; + const axisLabelPadding = (this.chartHeight - this.yAxisLabelBBox.width - 10) / 2; + + this.yAxisLabelText + .attr('y', 0 - margin.left) + .attr('x', 0 - (this.chartHeight / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .attr('transform', 'rotate(-90)'); + this.yAxisLabelLineA + .attr('x1', axisLabelOffset) + .attr('x2', axisLabelOffset) + .attr('y1', 0) + .attr('y2', axisLabelPadding); + this.yAxisLabelLineB + .attr('x1', axisLabelOffset) + .attr('x2', axisLabelOffset) + .attr('y1', this.chartHeight - axisLabelPadding) + .attr('y2', this.chartHeight); + + // update legend + const legendPadding = 10; + const legendSpacing = 5; + + const idealBBox = this.chartLegendIdealKeyBBox; + const actualBBox = this.chartLegendActualKeyBBox; + const keyWidth = Math.ceil(Math.max(idealBBox.width, actualBBox.width)); + const keyHeight = Math.ceil(Math.max(idealBBox.height, actualBBox.height)); + + const idealKeyOffset = legendPadding; + const actualKeyOffset = legendPadding + keyHeight + legendSpacing; + const legendWidth = (legendPadding * 2) + 24 + keyWidth; + const legendHeight = (legendPadding * 2) + (keyHeight * 2) + legendSpacing; + const legendOffset = (this.chartWidth + margin.right) - legendWidth - 1; + + this.chartLegendGroup.select('rect') + .attr('width', legendWidth) + .attr('height', legendHeight); + + this.chartLegendGroup.selectAll('text') + .attr('x', 24) + .attr('dy', '1em'); + this.chartLegendGroup.selectAll('line') + .attr('y1', keyHeight / 2) + .attr('y2', keyHeight / 2) + .attr('x1', 0) + .attr('x2', 18); + + this.chartLegendGroup.attr('transform', `translate(${legendOffset}, 0)`); + this.chartLegendIdealKey.attr('transform', `translate(${legendPadding}, ${idealKeyOffset})`); + this.chartLegendActualKey.attr('transform', `translate(${legendPadding}, ${actualKeyOffset})`); + + // update overlay + this.chartOverlay + .attr('fill', 'none') + .attr('pointer-events', 'all') + .attr('width', this.chartWidth) + .attr('height', this.chartHeight); + + // render lines if data available + if (this.data != null && this.data.length > 1) { + this.actualLinePath.datum(this.data).attr('d', this.line); + this.idealLinePath.datum(this.idealData).attr('d', this.line); + + if (this.scheduleLineAnimation === true) { + this.scheduleLineAnimation = false; + + // hide tooltips until animation is finished + this.chartFocus.attr('opacity', 0); + + this.constructor.animateLinePath(this.actualLinePath, 800, () => { + this.chartFocus.attr('opacity', null); + }); + } + } + } + + renderTooltip(datum) { + if (this.renderedTooltipPoint === datum) return; + this.renderedTooltipPoint = datum; + + // generate tooltip content + const format = d3.time.format('%b %-d, %Y'); + const tooltip = `${datum.value} ${this.label} / ${format(datum.date)}`; + + // move the tooltip point of origin to the point on the graph + const x = this.xScale(datum.date); + const y = this.yScale(datum.value); + + const textSize = this.tooltipGroup.select('text').text(tooltip).node().getBBox(); + const width = textSize.width + (tooltipPadding.x * 2); + const height = textSize.height + (tooltipPadding.y * 2); + + // calculate bounraries + const xMin = 0 - x - margin.left; + const yMin = 0 - y - margin.top; + const xMax = (this.chartWidth + margin.right) - x - width; + const yMax = (this.chartHeight + margin.bottom) - y - height; + + // try to fit tooltip above point + let xOffset = 0 - Math.floor(width / 2); + let yOffset = 0 - tooltipDistance - height; + + if (yOffset <= yMin) { + // else try to fit tooltip to the right + xOffset = tooltipDistance; + yOffset = 0 - Math.floor(height / 2); + + if (xOffset >= xMax) { + // else place tooltip on the left + xOffset = 0 - tooltipDistance - width; + } + } + + // ensure coordinates keep the entire tooltip in-bounds + xOffset = Math.max(xMin, Math.min(xMax, xOffset)); + yOffset = Math.max(yMin, Math.min(yMax, yOffset)); + + // move everything into place + this.chartFocus.attr('transform', `translate(${x}, ${y})`); + this.tooltipGroup.attr('transform', `translate(${xOffset}, ${yOffset})`); + + this.tooltipGroup.select('text') + .attr('dy', '1em') + .attr('x', tooltipPadding.x) + .attr('y', tooltipPadding.y); + + this.tooltipGroup.select('rect') + .attr('width', width) + .attr('height', height); + } + + animateResize(seconds = 5) { + this.ticksLeft = this.ticksLeft || 0; + if (this.ticksLeft <= 0) { + const interval = setInterval(() => { + this.ticksLeft -= 1; + if (this.ticksLeft <= 0) { + clearInterval(interval); + } + this.handleResize(); + }, 20); + } + this.ticksLeft = seconds * 50; + } + + static animateLinePath(path, duration = 1000, cb) { + // hack to run a callback at transition end + function after(transition, callback) { + let i = 0; + transition + .each(() => (i += 1)) + .each('end', function end(...args) { + i -= 1; + if (i === 0) { + callback.apply(this, args); + } + }); + } + + const lineLength = path.node().getTotalLength(); + path + .attr('stroke-dasharray', `${lineLength} ${lineLength}`) + .attr('stroke-dashoffset', lineLength) + .transition() + .duration(duration) + .ease('linear') + .attr('stroke-dashoffset', 0) + .call(after, () => { + path.attr('stroke-dasharray', null); + if (cb) cb(); + }); + } +} diff --git a/app/assets/javascripts/burndown_chart/index.js b/app/assets/javascripts/burndown_chart/index.js new file mode 100644 index 0000000000000000000000000000000000000000..88547663a4deaadccb47c3b41be2b8e09e841587 --- /dev/null +++ b/app/assets/javascripts/burndown_chart/index.js @@ -0,0 +1,50 @@ +import Cookies from 'js-cookie'; +import BurndownChart from './burndown_chart'; + +$(() => { + // handle hint dismissal + const hint = $('.burndown-hint'); + hint.on('click', '.dismiss-icon', () => { + hint.hide(); + Cookies.set('hide_burndown_message', 'true'); + }); + + // generate burndown chart (if data available) + const container = '.burndown-chart'; + const $chartElm = $(container); + + if ($chartElm.length) { + const startDate = $chartElm.data('startDate'); + const dueDate = $chartElm.data('dueDate'); + const chartData = $chartElm.data('chartData'); + const openIssuesCount = chartData.map(d => [d[0], d[1]]); + const openIssuesWeight = chartData.map(d => [d[0], d[2]]); + + const chart = new BurndownChart({ container, startDate, dueDate }); + + let currentView = 'count'; + chart.setData(openIssuesCount, { label: 'Open issues', animate: true }); + + $('.js-burndown-data-selector').on('click', 'button', function switchData() { + const $this = $(this); + const show = $this.data('show'); + if (currentView !== show) { + currentView = show; + $this.addClass('active').siblings().removeClass('active'); + switch (show) { + case 'count': + chart.setData(openIssuesCount, { label: 'Open issues', animate: true }); + break; + case 'weight': + chart.setData(openIssuesWeight, { label: 'Open issue weight', animate: true }); + break; + default: + break; + } + } + }); + + window.addEventListener('resize', () => chart.animateResize(1)); + $(document).on('click', '.js-sidebar-toggle', () => chart.animateResize(2)); + } +}); diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 50d0cb5c86d8c8760891f48ee004116cb5d8b07e..5662802525e210237913355a0f7deb1325ba4e55 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -121,7 +121,7 @@ export default class ImageFile { return $('.swipe.view', this.file).each((function(_this) { return function(index, view) { var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; - ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; $swipeFrame = $('.swipe-frame', view); $swipeWrap = $('.swipe-wrap', view); $swipeBar = $('.swipe-bar', view); @@ -158,7 +158,7 @@ export default class ImageFile { return $('.onion-skin.view', this.file).each((function(_this) { return function(index, view) { var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false; - ref = this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + ref = _this.prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; $frame = $('.onion-skin-frame', view); $frameAdded = $('.frame.added', view); $track = $('.drag-track', view); diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js index b93e94a3c977dc819cbde1887510ab783d69dac9..358935d8488df00c6a8db02f60a2a1969bbdff5c 100644 --- a/app/assets/javascripts/commons/jquery.js +++ b/app/assets/javascripts/commons/jquery.js @@ -8,3 +8,6 @@ import 'vendor/jquery.atwho'; import 'vendor/jquery.scrollTo'; import 'vendor/jquery.waitforimages'; import 'select2/select2'; + +// EE-only +import 'vendor/jquery.tablesorter'; diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index eae4a7eab555fd3ac25533a0c9c9cf06abdec60e..2a3b17cf99ef9118dda77cf297cae51f512c024c 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -2,10 +2,14 @@ import { rstrip } from './lib/utils/common_utils'; window.ConfirmDangerModal = (function() { - function ConfirmDangerModal(form, text) { - var project_path, submit; + function ConfirmDangerModal(form, text, arg) { + var project_path, submit, warningMessage; + warningMessage = (arg != null ? arg : {}).warningMessage; this.form = form; - $('.js-confirm-text').text(text || ''); + $('.js-confirm-text').html(text || ''); + if (warningMessage) { + $('.js-warning-text').html(warningMessage); + } $('.js-confirm-danger-input').val(''); $('#modal-confirm-danger').modal('show'); project_path = $('.js-confirm-danger-match').text(); diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js index 46b68ebe15897163c84206affa67f0c006536e88..cd20dde295106d61d02d1ce045b7518684d67da6 100644 --- a/app/assets/javascripts/contextual_sidebar.js +++ b/app/assets/javascripts/contextual_sidebar.js @@ -28,7 +28,7 @@ export default class ContextualSidebar { this.$closeSidebar.on('click', () => this.toggleSidebarNav(false)); this.$overlay.on('click', () => this.toggleSidebarNav(false)); this.$sidebarToggle.on('click', () => { - const value = !this.$sidebar.hasClass('sidebar-icons-only'); + const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop'); this.toggleCollapsedSidebar(value); }); @@ -43,16 +43,16 @@ export default class ContextualSidebar { } toggleSidebarNav(show) { - this.$sidebar.toggleClass('nav-sidebar-expanded', show); + this.$sidebar.toggleClass('sidebar-expanded-mobile', show); this.$overlay.toggleClass('mobile-nav-open', show); - this.$sidebar.removeClass('sidebar-icons-only'); + this.$sidebar.removeClass('sidebar-collapsed-desktop'); } toggleCollapsedSidebar(collapsed) { const breakpoint = bp.getBreakpointSize(); if (this.$sidebar.length) { - this.$sidebar.toggleClass('sidebar-icons-only', collapsed); + this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); } ContextualSidebar.setCollapsedCookie(collapsed); diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index 23425672b1697ed2f75748fda5a1ed13ecc1574d..eedbd3feeb5a3d18fd8514f861d8903eb37ce0dd 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -276,13 +276,13 @@ export default class CreateMergeRequestDropdown { let target; let value; - if (event.srcElement === this.branchInput) { + if (event.target === this.branchInput) { target = 'branch'; value = this.branchInput.value; - } else if (event.srcElement === this.refInput) { + } else if (event.target === this.refInput) { target = 'ref'; - value = event.srcElement.value.slice(0, event.srcElement.selectionStart) + - event.srcElement.value.slice(event.srcElement.selectionEnd); + value = event.target.value.slice(0, event.target.selectionStart) + + event.target.value.slice(event.target.selectionEnd); } else { return false; } diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index b41d464475fa8cc20141dee96682bdfbdef6ad93..7df2440a7c41fa89adde8cb961f4da0d87fa0715 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -1,5 +1,6 @@ @@ -51,20 +58,22 @@
    {{ deployKey.fingerprint }}
    -
    - Write access allowed -
    diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js index e0422057090bbbbd7e20e192a226cfccade66f42..45e5a7947e9cc09934cea5c749e08fe37b5e8fc4 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js @@ -1,5 +1,6 @@ /* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */ /* global ResolveCount */ +/* global ResolveServiceClass */ import Vue from 'vue'; import './models/discussion'; @@ -71,7 +72,7 @@ $(() => { el: '#resolve-count-app', components: { 'resolve-count': ResolveCount - } + }, }); $(window).trigger('resize.nav'); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 678af8f7b7ac9dc69cc0286bccd4eab3dad33136..a26600a570331c85bb5f7664974bd01a11c72a87 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -24,12 +24,15 @@ import projectAvatar from './project_avatar'; /* global MergeRequest */ /* global Compare */ /* global CompareAutocomplete */ +/* global PathLocks */ /* global ProjectFindFile */ import ProjectNew from './project_new'; import projectImport from './project_import'; import Labels from './labels'; import LabelManager from './label_manager'; /* global Sidebar */ +/* global WeightSelect */ +/* global AdminEmailSelect */ import IssuableTemplateSelectors from './templates/issuable_template_selectors'; import Flash from './flash'; import CommitsList from './commits'; @@ -48,6 +51,7 @@ import UserCallout from './user_callout'; import ShortcutsWiki from './shortcuts_wiki'; import Pipelines from './pipelines'; import BlobViewer from './blob/viewer/index'; +import GeoNodes from './geo_nodes'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import UsersSelect from './users_select'; import RefSelectDropdown from './ref_select_dropdown'; @@ -92,6 +96,12 @@ import Diff from './diff'; import ProjectLabelSubscription from './project_label_subscription'; import ProjectVariables from './project_variables'; +// EE-only +import ApproversSelect from './approvers_select'; +import AuditLogs from './audit_logs'; +import initGeoInfoModal from './init_geo_info_modal'; +import initGroupAnalytics from './init_group_analytics'; + (function() { var Dispatcher; @@ -125,6 +135,19 @@ import ProjectVariables from './project_variables'; }); }); + function initBlobEE() { + const dataEl = document.getElementById('js-file-lock'); + + if (dataEl) { + const { + toggle_path, + path, + } = JSON.parse(dataEl.innerHTML); + + PathLocks.init(toggle_path, path); + } + } + function initBlob() { new LineHighlighter(); @@ -150,6 +173,8 @@ import ProjectVariables from './project_variables'; actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'), }) .init(); + + initBlobEE(); } const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); @@ -190,6 +215,7 @@ import ProjectVariables from './project_variables'; projectSelect(); break; case 'projects:milestones:show': + new UserCallout(); case 'groups:milestones:show': case 'dashboard:milestones:show': new Milestone(); @@ -244,6 +270,9 @@ import ProjectVariables from './project_variables'; new DueDateSelectors(); new GLForm($('.milestone-form'), false); break; + case 'groups:epics:show': + new ZenMode(); + break; case 'projects:compare:show': new Diff(); const paddingTop = 16; @@ -264,6 +293,7 @@ import ProjectVariables from './project_variables'; new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); + new WeightSelect(); new IssuableTemplateSelectors(); break; case 'projects:merge_requests:creations:new': @@ -280,6 +310,7 @@ import ProjectVariables from './project_variables'; action: mrNewSubmitNode.dataset.mrSubmitAction, }); } + new UserCallout(); case 'projects:merge_requests:creations:diffs': case 'projects:merge_requests:edit': new Diff(); @@ -362,6 +393,9 @@ import ProjectVariables from './project_variables'; shortcut_handler = new ShortcutsNavigation(); GpgBadges.fetch(); break; + case 'projects:imports:show': + projectImport(); + break; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); new NotificationsForm(); @@ -373,14 +407,21 @@ import ProjectVariables from './project_variables'; if ($('#tree-slider').length) new TreeView(); if ($('.blob-viewer').length) new BlobViewer(); if ($('.project-show-activity').length) new gl.Activities(); + $('#tree-slider').waitForImages(function() { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); + + initGeoInfoModal(); break; case 'projects:edit': + new UsersSelect(); + groupsSelect(); setupProjectEdit(); // Initialize expandable settings panels initSettingsPanels(); + new UserCallout({ className: 'js-service-desk-callout' }); + new UserCallout({ className: 'js-mr-approval-callout' }); break; case 'projects:imports:show': projectImport(); @@ -451,6 +492,14 @@ import ProjectVariables from './project_variables'; new TreeView(); new BlobViewer(); new NewCommitForm($('.js-create-dir-form')); + + if (document.querySelector('.js-tree-content').dataset.pathLocksAvailable === 'true') { + PathLocks.init( + document.querySelector('.js-tree-content').dataset.pathLocksToggle, + document.querySelector('.js-tree-content').dataset.pathLocksPath, + ); + } + $('#tree-slider').waitForImages(function() { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); @@ -517,8 +566,21 @@ import ProjectVariables from './project_variables'; break; case 'search:show': new Search(); + new UserCallout(); + break; + case 'projects:mirrors:show': + case 'projects:mirrors:update': + new UsersSelect(); + break; + case 'admin:emails:show': + new AdminEmailSelect(); + break; + case 'admin:audit_logs:index': + new AuditLogs(); break; case 'projects:settings:repository:show': + new UsersSelect(); + new UserCallout(); // Initialize expandable settings panels initSettingsPanels(); break; @@ -552,6 +614,8 @@ import ProjectVariables from './project_variables'; new DueDateSelectors(); break; case 'projects:clusters:show': + case 'projects:clusters:update': + case 'projects:clusters:destroy': import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle') .then(cluster => new cluster.default()) // eslint-disable-line new-cap .catch((err) => { @@ -567,6 +631,31 @@ import ProjectVariables from './project_variables'; throw err; }); break; + case 'projects:clusters:index': + import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index') + .then(clusterIndex => clusterIndex.default()) + .catch((err) => { + Flash(s__('ClusterIntegration|Problem setting up the clusters list')); + throw err; + }); + break; + case 'admin:licenses:new': + const $licenseFile = $('.license-file'); + const $licenseKey = $('.license-key'); + + const showLicenseType = () => { + const $checkedFile = $('input[name="license_type"]:checked').val() === 'file'; + + $licenseFile.toggle($checkedFile); + $licenseKey.toggle(!$checkedFile); + }; + + $('input[name="license_type"]').on('change', showLicenseType); + showLicenseType(); + break; + case 'groups:analytics:show': + initGroupAnalytics(); + break; } switch (path[0]) { case 'sessions': @@ -607,6 +696,12 @@ import ProjectVariables from './project_variables'; case 'abuse_reports': new AbuseReports(); break; + case 'geo_nodes': + new GeoNodes($('.geo-nodes')); + import(/* webpackChunkName: 'geo_node_form' */ './geo/geo_node_form') + .then(geoNodeForm => geoNodeForm.default($('.js-geo-node-form'))) + .catch(() => {}); + break; } break; case 'dashboard': @@ -627,6 +722,7 @@ import ProjectVariables from './project_variables'; case 'edit': shortcut_handler = new ShortcutsNavigation(); new ProjectNew(); + new ApproversSelect(); import(/* webpackChunkName: 'project_permissions' */ './projects/permissions') .then(permissions => permissions.default()) .catch(() => {}); @@ -665,7 +761,7 @@ import ProjectVariables from './project_variables'; case 'builds': case 'hooks': case 'services': - case 'protected_branches': + case 'repository': shortcut_handler = new ShortcutsNavigation(); } break; diff --git a/app/assets/javascripts/docs/docs_bundle.js b/app/assets/javascripts/docs/docs_bundle.js new file mode 100644 index 0000000000000000000000000000000000000000..a32bd6d0fc7652f5078b602fefc620f4e3b746fb --- /dev/null +++ b/app/assets/javascripts/docs/docs_bundle.js @@ -0,0 +1,13 @@ +import Mousetrap from 'mousetrap'; + +function addMousetrapClick(el, key) { + el.addEventListener('click', () => Mousetrap.trigger(key)); +} + +function domContentLoaded() { + addMousetrapClick(document.querySelector('.js-trigger-shortcut'), '?'); + addMousetrapClick(document.querySelector('.js-trigger-search-bar'), 's'); +} + +document.addEventListener('DOMContentLoaded', domContentLoaded); + diff --git a/app/assets/javascripts/ee_trial_banner/ee_trial_banner.js b/app/assets/javascripts/ee_trial_banner/ee_trial_banner.js new file mode 100644 index 0000000000000000000000000000000000000000..654006761b4ce6d6df8a9bf32cb0287b9393dbce --- /dev/null +++ b/app/assets/javascripts/ee_trial_banner/ee_trial_banner.js @@ -0,0 +1,102 @@ +import Cookies from 'js-cookie'; + +export default class EETrialBanner { + constructor($trialBanner) { + this.COOKIE_KEY = 'show_ee_trial_banner'; + this.$trialBanner = $trialBanner; + this.$mainNavbar = this.$trialBanner.siblings('.js-navbar-gitlab'); + this.$secondaryNavbar = this.$mainNavbar.siblings('.js-page-with-sidebar'); + + this.licenseExpiresOn = new Date(this.$trialBanner.data('license-expiry')); + } + + init() { + // Wait for navbars to render before querying + this.setCookies(); + this.$trialBanner.on('close.bs.alert', e => this.handleTrialBannerDismiss(e)); + } + + /** + * Trial Expiring/Expired Banner has two stages; + * 1. Show banner when user enters last 7 days of trial + * 2. Show banner again when last 7 days are over and license has expired + * + * Stage 1: + * Banner is showed when `trial_license_message` is sent by backend + * for the first time (in `app/views/layouts/header/_default.html.haml`). + * Here, we perform following steps; + * + * 1. Set cookie `show_ee_trial_banner` with expiry same as license + * 2. Set cookie value to `true` + * 3. Show banner using `toggleBanner(true)` + * + * At this stage, if user dismisses banner, we set cookie value to `false` + * and everytime page is initialized, we check for cookie existence as + * well as its value, and decide show/hide status of banner + * + * Stage 2: + * At this point, Cookie we had set earlier will be expired and + * backend will now send updated message in `trial_license_message`. + * Here, we perform following steps; + * + * 1. Check if cookie is defined (it'll not be defined as it is expired now) + * 2. If cookie is gone, we re-set `show_ee_trial_banner` cookie but with + * expiry of 20 years + * 3. Set cookie value to `true` + * 4. Show banner using `toggleBanner(true)`, which now has updated message + * + * At this stage, if user dismisses banner, we set cookie value to `false` + * and our existing logic of show/hide banner based on cookie value continues + * to work. And since, cookie is set to expire after 20 years, user won't be + * seeing banner again. + */ + setCookies() { + const today = new Date(); + + // Check if Cookie is defined + if (!Cookies.get(this.COOKIE_KEY)) { + // Cookie was not defined, let's define with default value + + // Check if License is yet to expire + if (today < this.licenseExpiresOn) { + // License has not expired yet, we show initial banner of 7 days + // with cookie set to validity same as license expiry + Cookies.set(this.COOKIE_KEY, 'true', { expires: this.licenseExpiresOn }); + } else { + // License is already expired so we show final Banner with cookie set to 20 years validity. + Cookies.set(this.COOKIE_KEY, 'true', { expires: 7300 }); + } + + this.toggleBanner(true); + } else { + // Cookie was defined, let's read value and show/hide banner + this.toggleBanner(Cookies.get(this.COOKIE_KEY) === 'true'); + } + } + + toggleMainNavbarMargin(state) { + if (this.$mainNavbar.length) { + this.$mainNavbar.toggleClass('has-trial-banner', state); + } + } + + toggleSecondaryNavbarMargin(state) { + if (this.$secondaryNavbar.length) { + this.$secondaryNavbar.toggleClass('has-trial-banner', state); + } + } + + toggleBanner(state) { + this.$trialBanner.toggleClass('hidden', !state); + this.toggleMainNavbarMargin(state); + this.toggleSecondaryNavbarMargin(state); + } + + handleTrialBannerDismiss() { + this.toggleMainNavbarMargin(false); + this.toggleSecondaryNavbarMargin(false); + if (Cookies.get(this.COOKIE_KEY)) { + Cookies.set(this.COOKIE_KEY, 'false'); + } + } +} diff --git a/app/assets/javascripts/ee_trial_banner/index.js b/app/assets/javascripts/ee_trial_banner/index.js new file mode 100644 index 0000000000000000000000000000000000000000..406c1c2c9c1c468615829fc8727095a999937c98 --- /dev/null +++ b/app/assets/javascripts/ee_trial_banner/index.js @@ -0,0 +1,9 @@ +import EETrialBanner from './ee_trial_banner'; + +$(() => { + const $trialBanner = $('.js-gitlab-ee-license-banner'); + if ($trialBanner.length) { + const eeTrialBanner = new EETrialBanner($trialBanner); + eeTrialBanner.init(); + } +}); diff --git a/app/assets/javascripts/environments/components/deploy_board_component.vue b/app/assets/javascripts/environments/components/deploy_board_component.vue new file mode 100644 index 0000000000000000000000000000000000000000..8d5c53943e7658e8528ade57f309e75605a8de28 --- /dev/null +++ b/app/assets/javascripts/environments/components/deploy_board_component.vue @@ -0,0 +1,121 @@ + + diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue index 8c81c5d6df358548dbd7ba3a537e87ff7a6aa490..3ceb961f58efc5f1ddfe944d6aa11a26279310cc 100644 --- a/app/assets/javascripts/notes/components/issue_note.vue +++ b/app/assets/javascripts/notes/components/issue_note.vue @@ -1,5 +1,6 @@ + + diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue new file mode 100644 index 0000000000000000000000000000000000000000..3f51ae6aad821677039b18d8d7df1baae8c8fd01 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue @@ -0,0 +1,51 @@ + + + diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 9b1bbb0906f69e16474a9bd6e23131c21b27455b..acfa64d64ea1f748f30b97bad1b56849439830f8 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -25,6 +25,10 @@ export default { required: false, default: '', }, + hasTriggeredBy: { + type: Boolean, + required: true, + }, }, components: { @@ -40,10 +44,6 @@ export default { jobId(job) { return `ci-badge-${job.name}`; }, - - buildConnnectorClass(index) { - return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; - }, }, }; @@ -60,7 +60,9 @@ export default { v-for="(job, index) in jobs" :key="job.id" class="build" - :class="buildConnnectorClass(index)" + :class="{ + 'left-connector': index === 0 && (!isFirstColumn || hasTriggeredBy) + }" :id="jobId(job)">
    diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 3131e71d9d642d4ae45748290757a07e6304614f..024f47e7df7a4af471bcfb4d81a2eae6ea68c23f 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -26,6 +26,11 @@ export default class Project { $projectCloneField.val(url); $cloneBtnText.text(activeText); + $('#modal-geo-info').data({ + cloneUrlSecondary: $this.attr('href'), + cloneUrlPrimary: $this.data('primaryUrl') || '', + }); + return $('.clone').text(url); }); // Ref switcher @@ -43,10 +48,17 @@ export default class Project { $(this).parents('.no-password-message').remove(); return e.preventDefault(); }); + $('.hide-shared-runner-limit-message').on('click', function(e) { + var $alert = $(this).parents('.shared-runner-quota-message'); + var scope = $alert.data('scope'); + Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope }); + $alert.remove(); + e.preventDefault(); + }); Project.projectSelectDropdown(); } - static projectSelectDropdown () { + static projectSelectDropdown() { projectSelect(); $('.project-item-select').on('click', e => Project.changeProject($(e.currentTarget).val())); } diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index ca548d011b6843fd0d3b7221cc7912a2a733a6bc..faeda4aa2a8060453ac8d40970de08ca8dc8ca5c 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/ +/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */ import VisibilitySelect from './visibility_select'; @@ -12,20 +12,28 @@ export default class ProjectNew { this.toggleSettings = this.toggleSettings.bind(this); this.$selects = $('.features select'); this.$repoSelects = this.$selects.filter('.js-repo-select'); + this.$enableApprovers = $('.js-require-approvals-toggle'); this.$projectSelects = this.$selects.not('.js-repo-select'); - $('.project-edit-container').on('ajax:before', () => { - $('.project-edit-container').hide(); - return $('.save-project-loader').show(); - }); + $('.project-edit-container').on('ajax:before', (function(_this) { + return function() { + $('.project-edit-container').hide(); + return $('.save-project-loader').show(); + }; + })(this)); this.initVisibilitySelect(); this.toggleSettings(); - this.toggleSettingsOnclick(); + this.bindEvents(); this.toggleRepoVisibility(); } + bindEvents () { + this.$selects.on('change', () => this.toggleSettings()); + $('#require_approvals').on('change', e => this.toggleApproverSettingsVisibility(e)); + } + initVisibilitySelect() { const visibilityContainer = document.querySelector('.js-visibility-select'); if (!visibilityContainer) return; @@ -69,6 +77,15 @@ export default class ProjectNew { }); } + toggleApproverSettingsVisibility(e) { + this.$requiredApprovals = $('#project_approvals_before_merge'); + const enabled = $(e.target).prop('checked'); + const val = enabled ? 1 : 0; + this.$requiredApprovals.val(val); + this.$requiredApprovals.prop('min', val); + $('.nested-settings').toggleClass('hidden', !enabled); + } + toggleSettings() { this.$selects.each(function () { var $select = $(this); @@ -79,20 +96,17 @@ export default class ProjectNew { }); } - toggleSettingsOnclick() { - this.$selects.on('change', this.toggleSettings); - } - static _showOrHide(checkElement, container) { - const $container = $(container); + var $container = $(container); if ($(checkElement).val() !== '0') { return $container.show(); + } else { + return $container.hide(); } - return $container.hide(); } - toggleRepoVisibility() { + toggleRepoVisibility () { var $repoAccessLevel = $('.js-repo-access-level select'); var $lfsEnabledOption = $('.js-lfs-enabled select'); var containerRegistry = document.querySelectorAll('.js-container-registry')[0]; @@ -103,8 +117,7 @@ export default class ProjectNew { .nextAll() .hide(); - $repoAccessLevel - .off('change') + $repoAccessLevel.off('change') .on('change', function () { var selectedVal = parseInt($repoAccessLevel.val(), 10); diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 3ecc0c2a6e58831617695189ed79faed9327f38b..5ff5471e8a57a119138886850ca7001bc7b83861 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -97,6 +97,12 @@ const bindEvents = () => { }); $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath)); + + $('.import_git').on('click', () => { + const $projectMirror = $('#project_mirror'); + + $projectMirror.attr('disabled', !$projectMirror.attr('disabled')); + }); }; document.addEventListener('DOMContentLoaded', bindEvents); diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue new file mode 100644 index 0000000000000000000000000000000000000000..179b7fff67a81a08035ea848f408a5a4735cafba --- /dev/null +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue @@ -0,0 +1,111 @@ + + + diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue new file mode 100644 index 0000000000000000000000000000000000000000..77988ce3866fb2bbc1046969369a0f7dc4f37c29 --- /dev/null +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/assets/javascripts/projects/settings_service_desk/event_hub.js b/app/assets/javascripts/projects/settings_service_desk/event_hub.js new file mode 100644 index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a --- /dev/null +++ b/app/assets/javascripts/projects/settings_service_desk/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/projects/settings_service_desk/service_desk_bundle.js b/app/assets/javascripts/projects/settings_service_desk/service_desk_bundle.js new file mode 100644 index 0000000000000000000000000000000000000000..e7bd5a79980ffaf2635a0da73b22ee2b48a3e7bc --- /dev/null +++ b/app/assets/javascripts/projects/settings_service_desk/service_desk_bundle.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import serviceDeskRoot from './components/service_desk_root.vue'; +import { convertPermissionToBoolean } from '../../lib/utils/common_utils'; + +document.addEventListener('DOMContentLoaded', () => { + const serviceDeskRootElement = document.querySelector('.js-service-desk-setting-root'); + if (serviceDeskRootElement) { + // eslint-disable-next-line no-new + new Vue({ + el: serviceDeskRootElement, + data() { + const dataset = serviceDeskRootElement.dataset; + return { + initialIsEnabled: convertPermissionToBoolean( + dataset.enabled, + ), + endpoint: dataset.endpoint, + incomingEmail: dataset.incomingEmail, + }; + }, + components: { + serviceDeskRoot, + }, + render(createElement) { + return createElement('service-desk-root', { + props: { + initialIsEnabled: this.initialIsEnabled, + endpoint: this.endpoint, + incomingEmail: this.incomingEmail, + }, + }); + }, + }); + } +}); diff --git a/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js b/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js new file mode 100644 index 0000000000000000000000000000000000000000..2a36c039dd74f99c5c53ee5193661b370350025c --- /dev/null +++ b/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js @@ -0,0 +1,22 @@ +import Vue from 'vue'; +import vueResource from 'vue-resource'; + +Vue.use(vueResource); + +class ServiceDeskService { + constructor(endpoint) { + this.serviceDeskResource = Vue.resource(`${endpoint}`); + } + + fetchIncomingEmail() { + return this.serviceDeskResource.get(); + } + + toggleServiceDesk(enable) { + return this.serviceDeskResource.update({ + service_desk_enabled: enable, + }); + } +} + +export default ServiceDeskService; diff --git a/app/assets/javascripts/projects/settings_service_desk/stores/service_desk_store.js b/app/assets/javascripts/projects/settings_service_desk/stores/service_desk_store.js new file mode 100644 index 0000000000000000000000000000000000000000..eea56ee2db6f7cc3d25b5fe7cc0e3a8598829a2b --- /dev/null +++ b/app/assets/javascripts/projects/settings_service_desk/stores/service_desk_store.js @@ -0,0 +1,17 @@ +class ServiceDeskStore { + constructor(initialState = {}) { + this.state = Object.assign({ + incomingEmail: '', + }, initialState); + } + + setIncomingEmail(value) { + this.state.incomingEmail = value; + } + + resetIncomingEmail() { + this.state.incomingEmail = ''; + } +} + +export default ServiceDeskStore; diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js index 41942c04a4ee7045b876106a2aba546fc9b63773..4cfa0761e12bcdb5c8c2d805c8dd3e1020afbc67 100644 --- a/app/assets/javascripts/render_mermaid.js +++ b/app/assets/javascripts/render_mermaid.js @@ -19,11 +19,25 @@ export default function renderMermaid($els) { import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => { mermaid.initialize({ - loadOnStart: false, + // mermaid core options + mermaid: { + startOnLoad: false, + }, + // mermaidAPI options theme: 'neutral', }); $els.each((i, el) => { + // Handle a condition that happens in CI and some of the time locally, + // where the `textContent` is the content of the styles injected by + // Mermaid, as well as any labels. + if (el.querySelector('style')) { return; } + + const source = el.textContent; + + // Remove any extra spans added by the backend syntax highlighting. + Object.assign(el, { textContent: source }); + mermaid.init(undefined, el); }); }).catch((err) => { diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue index f37cbd1e961cb2eca90351cd44811db67d4342e6..0b2ccfcffee84d0730f43aa325c0b53053d57c7a 100644 --- a/app/assets/javascripts/repo/components/repo_editor.vue +++ b/app/assets/javascripts/repo/components/repo_editor.vue @@ -64,7 +64,7 @@ export default { 'activeFileExtension', ]), shouldHideEditor() { - return this.activeFile.binary && !this.activeFile.raw; + return this.activeFile && this.activeFile.binary && !this.activeFile.raw; }, }, }; diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue index 75787ad6103ad5a3c5f8572dd23c04d610d24b2b..642b72b6f845a2e8afe1500bf6bac9a7e9b83ec9 100644 --- a/app/assets/javascripts/repo/components/repo_file.vue +++ b/app/assets/javascripts/repo/components/repo_file.vue @@ -2,6 +2,7 @@ import { mapActions, mapGetters } from 'vuex'; import timeAgoMixin from '../../vue_shared/mixins/timeago'; import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue'; + import fileStatusIcon from './repo_file_status_icon.vue'; export default { mixins: [ @@ -9,6 +10,7 @@ ], components: { skeletonLoadingContainer, + fileStatusIcon, }, props: { file: { @@ -70,6 +72,9 @@ class="repo-file-name" > {{ file.name }} + +