diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f97feca9f7b..87c3944dcce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -75,7 +75,7 @@ stages: .use-mysql: &use-mysql services: - - mysql:latest + - mysql:5.7 - redis:alpine .rails5-variables: &rails5-variables diff --git a/CHANGELOG.md b/CHANGELOG.md index d56c86523f5..a423e93d691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,213 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.7.0 (2018-04-22) + +### Security (6 changes, 2 of them are from the community) + +- Fixed some SSRF vulnerabilities in services, hooks and integrations. !2337 +- Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0. !17734 (Takuya Noguchi) +- Update rack-protection to 2.0.1. !17835 (Takuya Noguchi) +- Adds confidential notes channel for Slack/Mattermost. +- Fix XSS on diff view stored on filenames. +- Fix GitLab Auth0 integration signing in the wrong user. + +### Fixed (65 changes, 20 of them are from the community) + +- File uploads in remote storage now support project renaming. !4597 +- Fixed bug in dropdown selector when selecting the same selection again. !14631 (bitsapien) +- Fixed group deletion linked to Mattermost. !16209 (Julien Millau) +- Create commit API and Web IDE obey LFS filters. !16718 +- Set breadcrumb for admin/runners/show. !17431 (Takuya Noguchi) +- Enable restore rake task to handle nested storage directories. !17516 (Balasankar C) +- Fix hover style of dropdown items in the right sidebar. !17519 +- Improve empty state for canceled job. !17646 +- Fix generated URL when listing repoitories for import. !17692 +- Use singular in the diff stats if only one line has been changed. !17697 (Jan Beckmann) +- Long instance urls do not overflow anymore during project creation. !17717 +- Fix importing multiple assignees from GitLab export. !17718 +- Correct copy text for the promote milestone and label modals. !17726 +- Fix search results stripping last endline when parsing the results. !17777 (Jasper Maes) +- Add read-only banner to all pages. !17798 +- Fix viewing diffs on old merge requests. !17805 +- Fix forking to subgroup via API when namespace is given by name. !17815 (Jan Beckmann) +- Fix UI breakdown for Create merge request button. !17821 (Takuya Noguchi) +- Unify format for nested non-task lists. !17823 (Takuya Noguchi) +- UX re-design branch items with flexbox. !17832 (Takuya Noguchi) +- Use porcelain commit lookup method on CI::CreatePipelineService. !17911 +- Update dashboard milestones breadcrumb link. !17933 (George Tsiolis) +- Deleting a MR you are assigned to should decrements counter. !17951 (m b) +- Update no repository placeholder. !17964 (George Tsiolis) +- Drop JSON response in Project Milestone along with avoiding error. !17977 (Takuya Noguchi) +- Fix personal access token clipboard button style. !17978 (Fabian Schneider) +- Avoid validation errors when running the Pages domain verification service. !17992 +- Project creation will now raise an error if a service template is invalid. !18013 +- Add better LDAP connection handling. !18039 +- Fix autolinking URLs containing ampersands. !18045 +- Fix exceptions raised when migrating pipeline stages in the background. !18076 +- Always display Labels section in issuable sidebar, even when the project has no labels. !18081 (Branka Martinovic) +- Fixed gitlab:uploads:migrate task ignoring some uploads. !18082 +- Fixed gitlab:uploads:migrate task failing for Groups' avatar. !18088 +- Increase dropdown width in pipeline graph & center action icon. !18089 +- Fix `JobsController#raw` endpoint can not read traces in database. !18101 +- Fix `gitlab-rake gitlab:two_factor:disable_for_all_users`. !18154 +- Adjust 404's for LegacyDiffNote discussion rendering. !18201 +- Work around Prometheus Helm chart name changes to fix integration. !18206 (joshlambert) +- Prioritize weight over title when sorting charts. !18233 +- Verify that deploy token has valid access when pulling container registry image. !18260 +- Stop redirecting the page in pipeline main actions. +- Fixed IDE button opening the wrong URL in tree list. +- Ensure hooks run when a deploy key without a user pushes. +- Fix 404 in group boards when moving issue between lists. +- Display state indicator for issuable references in non-project scope (e.g. when referencing issuables from group scope). +- Add missing port to artifact links. +- Fix data race between ObjectStorage background_upload and Pages publishing. +- Fixes unresolved discussions rendering the error state instead of the diff. +- Don't show Jump to Discussion button on Issues. +- Fix bug rendering group icons when forking. +- Automatically cleanup stale worktrees and lock files upon a push. +- Use the GitLab version as part of the appearances cache key. +- Fix Firefox stealing formatting characters on issue notes. +- Include matching branches and tags in protected branches / tags count. (Jan Beckmann) +- Fix 500 error when a merge request from a fork has conflicts and has not yet been updated. +- Test if remote repository exists when importing wikis. +- Hide emoji popup after multiple spaces. (Jan Beckmann) +- Fix relative uri when "#" is in branch name. (Jan) +- Escape Markdown characters properly when using autocomplete. +- Ignore project internal references in group context. +- Fix finding wiki file when Gitaly is enabled. +- Fix listing commit branch/tags that contain special characters. +- Ensure internal users (ghost, support bot) get assigned a namespace. +- Fix links to subdirectories of a directory with a plus character in its path. + +### Deprecated (1 change) + +- Remove support for legacy tar.gz pages artifacts. !18090 + +### Changed (22 changes, 2 of them are from the community) + +- Add yellow favicon when `CANARY=true` to differientate canary environment. !12477 +- Use human readable value build_timeout in Project. !17386 +- Improved visual styles and consistency for commit hash and possible actions across commit lists. !17406 +- Don't create permanent redirect routes. !17521 +- Add empty repo check before running AutoDevOps pipeline. !17605 +- Update wording to specify create/manage project vs group labels in labels dropdown. !17640 +- Add tooltips to icons in lists of issues and merge requests. !17700 +- Change avatar error message to include allowed file formats. !17747 (Fabian Schneider) +- Polish design for verifying domains. !17767 +- Move email footer info to a single line. !17916 +- Add average and maximum summary statistics to the prometheus dashboard. !17921 +- Add additional cluster usage metrics to usage ping. !17922 +- Move 'Registry' after 'CI/CD' in project navigation sidebar. !18018 (Elias Werberich) +- Redesign application settings to match project settings. !18019 +- Allow HTTP(s) when git request is made by GitLab CI. !18021 +- Added hover background color to IDE file list rows. +- Make project avatar in IDE consistent with the rest of GitLab. +- Show issues of subgroups in group-level issue board. +- Repository checksum calculation is handled by Gitaly when feature is enabled. +- Allow viewing timings for AJAX requests in the performance bar. +- Fixes remove source branch checkbox being visible when user cannot remove the branch. +- Make /-/ delimiter optional for search endpoints. + +### Performance (24 changes, 11 of them are from the community) + +- Move AssigneeTitle vue component. !17397 (George Tsiolis) +- Move TimeTrackingCollapsedState vue component. !17399 (George Tsiolis) +- Move MemoryGraph and MemoryUsage vue components. !17533 (George Tsiolis) +- Move UnresolvedDiscussions vue component. !17538 (George Tsiolis) +- Move NothingToMerge vue component. !17544 (George Tsiolis) +- Move ShaMismatch vue component. !17546 (George Tsiolis) +- Stop caching highlighted diffs in Redis unnecessarily. !17746 +- Add i18n and update specs for ShaMismatch vue component. !17870 (George Tsiolis) +- Update spec import path for vue mount component helper. !17880 (George Tsiolis) +- Move TimeTrackingComparisonPane vue component. !17931 (George Tsiolis) +- Improves the performance of projects list page. !17934 +- Remove N+1 query for Noteable association. !17956 +- Improve performance of loading issues with lots of references to merge requests. !17986 +- Reuse root_ref_hash for performance on Branches. !17998 (Takuya Noguchi) +- Update asciidoctor-plantuml to 0.0.8. !18022 (Takuya Noguchi) +- Cache personal projects count. !18197 +- Reduce complexity of issuable finder query. !18219 +- Reduce number of queries when viewing a merge request. +- Free open file descriptors and libgit2 buffers in UpdatePagesService. +- Memoize Git::Repository#has_visible_content?. +- Require at least one filter when listing issues or merge requests on dashboard page. +- lazy load diffs on merge request discussions. +- Bulk deleting refs is handled by Gitaly by default. +- ListCommitsByOid is executed by Gitaly by default. + +### Added (38 changes, 7 of them are from the community) + +- Add HTTPS-only pages. !16273 (rfwatson) +- adds closed by informations in issue api. !17042 (haseebeqx) +- Projects and groups badges settings UI. !17114 +- Add per-runner configured job timeout. !17221 +- Add alternate archive route for simplified packaging. !17225 +- Add support for pipeline variables expressions in only/except. !17316 +- Add object storage support for LFS objects, CI artifacts, and uploads. !17358 +- Added confirmation modal for changing username. !17405 +- Implement foreground verification of CI artifacts. !17578 +- Extend API for exporting a project with direct upload URL. !17686 +- Move ci/lint under project's namespace. !17729 +- Add Total CPU/Memory consumption metrics for Kubernetes. !17731 +- Adds the option to the project export API to override the project description and display GitLab export description once imported. !17744 +- Port direct upload of LFS artifacts from EE. !17752 +- Adds support for OmniAuth JWT provider. !17774 +- Display error message on job's tooltip if this one fails. !17782 +- Add 'Assigned Issues' and 'Assigned Merge Requests' as dashboard view choices for users. !17860 (Elias Werberich) +- Extend API for importing a project export with overwrite support. !17883 +- Create Deploy Tokens to allow permanent access to repository and registry. !17894 +- Detect commit message trailers and link users properly to their accounts on Gitlab. !17919 (cousine) +- Adds cancel btn to new pages domain page. !18026 (Jacopo Beschi @jacopo-beschi) +- API: Add parameter merge_method to projects. !18031 (Jan Beckmann) +- Introduce simpler env vars for auto devops REPLICAS and CANARY_REPLICAS #41436. !18036 +- Allow overriding params on project import through API. !18086 +- Support LFS objects when importing/exporting GitLab project archives. !18115 +- Store sha256 checksum of artifact metadata. !18149 +- Limit the number of failed logins when using LDAP for authentication. !43525 +- Allow assigning and filtering issuables by ancestor group labels. +- Include subgroup issues when searching for group issues using the API. +- Allow to store uploads by default on Object Storage. +- Add slash command for moving issues. (Adam Pahlevi) +- Render MR commit SHA instead "diffs" when viable. +- Send @mention notifications even if a user has explicitly unsubscribed from item. +- Add support for Sidekiq JSON logging. +- Add Gitaly call details to performance bar. +- Add support for patch link extension for commit links on GitLab Flavored Markdown. +- Allow feature gates to be removed through the API. +- Allow merge requests related to a commit to be found via API. + +### Other (27 changes, 11 of them are from the community) + +- Send notification emails when push to a merge request. !7610 (YarNayar) +- Rename modal.vue to deprecated_modal.vue. !17438 +- Atomic generation of internal ids for issues. !17580 +- Use object ID to prevent duplicate keys Vue warning on Issue Boards page during development. !17682 +- Update foreman from 0.78.0 to 0.84.0. !17690 (Takuya Noguchi) +- Add realtime pipeline status for adding/viewing files. !17705 +- Update documentation to reflect current minimum required versions of node and yarn. !17706 +- Update knapsack to 1.16.0. !17735 (Takuya Noguchi) +- Update CI services documnetation. !17749 +- Added i18n support for the prometheus memory widget. !17753 +- Use specific names for filtered CI variable controller parameters. !17796 +- Apply NestingDepth (level 5) (framework/dropdowns.scss). !17820 (Takuya Noguchi) +- Clean up selectors in framework/header.scss. !17822 (Takuya Noguchi) +- Bump `state_machines-activerecord` to 0.5.1. !17924 (blackst0ne) +- Increase the memory limits used in the unicorn killer. !17948 +- Replace the spinach test with an rspec analog. !17950 (blackst0ne) +- Remove unused index from events table. !18014 +- Make all workhorse gitaly calls opt-out, take 2. !18043 +- Update brakeman 3.6.1 to 4.2.1. !18122 (Takuya Noguchi) +- Replace the `project/issues/labels.feature` spinach test with an rspec analog. !18126 (blackst0ne) +- Bump html-pipeline to 2.7.1. !18132 (@blackst0ne) +- Remove test_ci rake task. !18139 (Takuya Noguchi) +- Add documentation for Pipelines failure reasons. !18352 +- Improve JIRA event descriptions. +- Add query counts to profiler output. +- Move Sidekiq exporter logs to log/sidekiq_exporter.log. +- Upgrade Gitaly to upgrade its charlock_holmes. + + ## 10.6.4 (2018-04-09) ### Fixed (8 changes, 1 of them is from the community) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c8fdc1275b..02599907af7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,7 +126,7 @@ Most issues will have labels for at least one of the following: - Type: ~"feature proposal", ~bug, ~customer, etc. - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. - Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc. -- Priority: ~Deliverable, ~Stretch, ~"Next Patch Release" +- Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release" All labels, their meaning and priority are defined on the [labels page][labels-page]. @@ -185,10 +185,10 @@ indicate if an issue needs backend work, frontend work, or both. Team labels are always capitalized so that they show up as the first label for any issue. -### Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release") +### Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release") -Priority labels help us clearly communicate expectations of the work for the -release. There are two levels of priority labels: +Milestone labels help us clearly communicate expectations of the work for the +release. There are three levels of Milestone labels: - ~Deliverable: Issues that are expected to be delivered in the current milestone. @@ -203,16 +203,46 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable or ~"Stretch". Any open issue for a previous milestone should be labeled ~"Next Patch Release", or otherwise rescheduled to a different milestone. -### Severity labels (~S1, ~S2, etc.) +### Bug Priority labels (~P1, ~P2, ~P3 & etc.) -Severity labels help us clearly communicate the impact of a ~bug on users. +Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. If there are multiple defects, the priority decides which defect has to be fixed immediately versus later. +This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes. -| Label | Meaning | Example | -|-------|------------------------------------------|---------| -| ~S1 | Feature broken, no workaround | Unable to create an issue | -| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line | -| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue | -| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed | +| Label | Meaning | Estimate time to fix | Guidance | +|-------|-----------------|------------------------------------------------------------------|----------| +| ~P1 | Urgent Priority | The current release | | +| ~P2 | High Priority | The next release | | +| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | | +| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented | + +#### Specific Priority guidance + +| Label | Availability / Performance | +|-------|--------------------------------------------------------------| +| ~P1 | | +| ~P2 | The issue is (almost) guaranteed to occur in the near future | +| ~P3 | The issue is likely to occur in the near future | +| ~P4 | The issue _may_ occur but it's not likely | + +### Bug Severity labels (~S1, ~S2, ~S3 & etc.) + +Severity labels help us clearly communicate the impact of a ~bug on users. + +| Label | Meaning | Impact of the defect | Example | +|-------|-------------------|-------------------------------------------------------|---------| +| ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. | +| ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. | +| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. | +| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. | + +#### Specific Severity guidance + +| Label | Security Impact | +|-------|-------------------------------------------------------------------| +| ~S1 | >50% customers impacted (possible company extinction level event) | +| ~S2 | Multiple customers impacted (but not apocalyptic) | +| ~S3 | A single customer impacted | +| ~S4 | No customer impact, or expected impact within 30 days | ### Label for community contributors (~"Accepting Merge Requests") diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 5f8cbfdb7d7..483b7719418 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.95.0 +0.96.1 diff --git a/LICENSE b/LICENSE index 15c423e1416..a76372fad2c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,7 @@ -Copyright (c) 2011-2017 GitLab B.V. +Copyright GitLab B.V. -With regard to the GitLab Software: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -For all third party components incorporated into the GitLab Software, those -components are licensed under the original license provided by the owner of the -applicable component. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 9ead6d51c5d..9c1aad65307 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,12 @@ You can access a new installation with the login **`root`** and password **`5ive GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details. +## Licensing + +GitLab Community Edition (CE) is available freely under the MIT Expat license. + +All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component. + ## Install a development environment To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index e210d69895e..7144f4190e7 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -113,6 +113,8 @@ class List { issue.id = data.id; issue.iid = data.iid; issue.project = data.project; + issue.path = data.real_path; + issue.referencePath = data.reference_path; if (this.issuesSize > 1) { const moveBeforeId = this.issues[1].id; diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index fb1fc9cd32e..a88b6971f90 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -84,20 +84,21 @@ export default class CreateMergeRequestDropdown { if (data.can_create_branch) { this.available(); this.enable(); + this.updateBranchName(data.suggested_branch_name); if (!this.droplabInitialized) { this.droplabInitialized = true; this.initDroplab(); this.bindEvents(); } - } else if (data.has_related_branch) { + } else { this.hide(); } }) .catch(() => { this.unavailable(); this.disable(); - Flash('Failed to check if a new branch can be created.'); + Flash(__('Failed to check related branches.')); }); } @@ -409,13 +410,16 @@ export default class CreateMergeRequestDropdown { this.unavailableButton.classList.remove('hide'); } + updateBranchName(suggestedBranchName) { + this.branchInput.value = suggestedBranchName; + this.updateCreatePaths('branch', suggestedBranchName); + } + updateInputState(target, ref, result) { // target - 'branch' or 'ref' - which the input field we are searching a ref for. // ref - string - what a user typed. // result - string - what has been found on backend. - const pathReplacement = `$1${ref}`; - // If a found branch equals exact the same text a user typed, // that means a new branch cannot be created as it already exists. if (ref === result) { @@ -426,18 +430,12 @@ export default class CreateMergeRequestDropdown { this.refIsValid = true; this.refInput.dataset.value = ref; this.showAvailableMessage('ref'); - this.createBranchPath = this.createBranchPath.replace(this.regexps.ref.createBranchPath, - pathReplacement); - this.createMrPath = this.createMrPath.replace(this.regexps.ref.createMrPath, - pathReplacement); + this.updateCreatePaths(target, ref); } } else if (target === 'branch') { this.branchIsValid = true; this.showAvailableMessage('branch'); - this.createBranchPath = this.createBranchPath.replace(this.regexps.branch.createBranchPath, - pathReplacement); - this.createMrPath = this.createMrPath.replace(this.regexps.branch.createMrPath, - pathReplacement); + this.updateCreatePaths(target, ref); } else { this.refIsValid = false; this.refInput.dataset.value = ref; @@ -457,4 +455,15 @@ export default class CreateMergeRequestDropdown { this.disableCreateAction(); } } + + // target - 'branch' or 'ref' + // ref - string - the new value to use as branch or ref + updateCreatePaths(target, ref) { + const pathReplacement = `$1${ref}`; + + this.createBranchPath = this.createBranchPath.replace(this.regexps[target].createBranchPath, + pathReplacement); + this.createMrPath = this.createMrPath.replace(this.regexps[target].createMrPath, + pathReplacement); + } } diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 842a4255f08..4164149dd06 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -2,7 +2,9 @@ import $ from 'jquery'; import Pikaday from 'pikaday'; +import { __ } from '~/locale'; import axios from './lib/utils/axios_utils'; +import { timeFor } from './lib/utils/datetime_utility'; import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; class DueDateSelect { @@ -14,6 +16,7 @@ class DueDateSelect { this.$dropdownParent = $dropdownParent; this.$datePicker = $dropdownParent.find('.js-due-date-calendar'); this.$block = $block; + this.$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); this.$selectbox = $dropdown.closest('.selectbox'); this.$value = $block.find('.value'); this.$valueContent = $block.find('.value-content'); @@ -128,7 +131,8 @@ class DueDateSelect { submitSelectedDate(isDropdown) { const selectedDateValue = this.datePayload[this.abilityName].due_date; - const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value'; + const hasDueDate = this.displayedDate !== 'No due date'; + const displayedDateStyle = hasDueDate ? 'bold' : 'no-value'; this.$loading.removeClass('hidden').fadeIn(); @@ -145,10 +149,13 @@ class DueDateSelect { return axios.put(this.issueUpdateURL, this.datePayload) .then(() => { + const tooltipText = hasDueDate ? `${__('Due date')}
${selectedDateValue} (${timeFor(selectedDateValue)})` : __('Due date'); if (isDropdown) { this.$dropdown.trigger('loaded.gl.dropdown'); this.$dropdown.dropdown('toggle'); } + this.$sidebarCollapsedValue.attr('data-original-title', tooltipText); + return this.$loading.fadeOut(); }); } diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js index d47b6704176..016dcda1fa1 100644 --- a/app/assets/javascripts/ide/lib/common/model.js +++ b/app/assets/javascripts/ide/lib/common/model.js @@ -32,7 +32,7 @@ export default class Model { ); } - this.events = new Map(); + this.events = new Set(); this.updateContent = this.updateContent.bind(this); this.updateNewContent = this.updateNewContent.bind(this); @@ -76,10 +76,11 @@ export default class Model { } onChange(cb) { - this.events.set( - this.path, - this.disposable.add(this.model.onDidChangeContent(e => cb(this, e))), - ); + this.events.add(this.disposable.add(this.model.onDidChangeContent(e => cb(this, e)))); + } + + onDispose(cb) { + this.events.add(cb); } updateContent({ content, changed }) { @@ -96,6 +97,11 @@ export default class Model { dispose() { this.disposable.dispose(); + + this.events.forEach(cb => { + if (typeof cb === 'function') cb(); + }); + this.events.clear(); eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose); diff --git a/app/assets/javascripts/ide/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js index 42904774747..13d477bb2cf 100644 --- a/app/assets/javascripts/ide/lib/decorations/controller.js +++ b/app/assets/javascripts/ide/lib/decorations/controller.js @@ -38,6 +38,15 @@ export default class DecorationsController { ); } + hasDecorations(model) { + return this.decorations.has(model.url); + } + + removeDecorations(model) { + this.decorations.delete(model.url); + this.editorDecorations.delete(model.url); + } + dispose() { this.decorations.clear(); this.editorDecorations.clear(); diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js index b136545ad11..f579424cf33 100644 --- a/app/assets/javascripts/ide/lib/diff/controller.js +++ b/app/assets/javascripts/ide/lib/diff/controller.js @@ -3,7 +3,7 @@ import { throttle } from 'underscore'; import DirtyDiffWorker from './diff_worker'; import Disposable from '../common/disposable'; -export const getDiffChangeType = (change) => { +export const getDiffChangeType = change => { if (change.modified) { return 'modified'; } else if (change.added) { @@ -16,12 +16,7 @@ export const getDiffChangeType = (change) => { }; export const getDecorator = change => ({ - range: new monaco.Range( - change.lineNumber, - 1, - change.endLineNumber, - 1, - ), + range: new monaco.Range(change.lineNumber, 1, change.endLineNumber, 1), options: { isWholeLine: true, linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`, @@ -31,6 +26,7 @@ export const getDecorator = change => ({ export default class DirtyDiffController { constructor(modelManager, decorationsController) { this.disposable = new Disposable(); + this.models = new Map(); this.editorSimpleWorker = null; this.modelManager = modelManager; this.decorationsController = decorationsController; @@ -42,7 +38,15 @@ export default class DirtyDiffController { } attachModel(model) { + if (this.models.has(model.url)) return; + model.onChange(() => this.throttledComputeDiff(model)); + model.onDispose(() => { + this.decorationsController.removeDecorations(model); + this.models.delete(model.url); + }); + + this.models.set(model.url, model); } computeDiff(model) { @@ -54,7 +58,11 @@ export default class DirtyDiffController { } reDecorate(model) { - this.decorationsController.decorate(model); + if (this.decorationsController.hasDecorations(model)) { + this.decorationsController.decorate(model); + } else { + this.computeDiff(model); + } } decorate({ data }) { @@ -65,6 +73,7 @@ export default class DirtyDiffController { dispose() { this.disposable.dispose(); + this.models.clear(); this.dirtyDiffWorker.removeEventListener('message', this.decorate); this.dirtyDiffWorker.terminate(); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index d0050abb8e9..9b62cfb8206 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -83,7 +83,7 @@ export default class LabelsSelect { $dropdown.trigger('loading.gl.dropdown'); axios.put(issueUpdateURL, data) .then(({ data }) => { - var labelCount, template, labelTooltipTitle, labelTitles; + var labelCount, template, labelTooltipTitle, labelTitles, formattedLabels; $loading.fadeOut(); $dropdown.trigger('loaded.gl.dropdown'); $selectbox.hide(); @@ -115,8 +115,7 @@ export default class LabelsSelect { labelTooltipTitle = labelTitles.join(', '); } else { - labelTooltipTitle = ''; - $sidebarLabelTooltip.tooltip('destroy'); + labelTooltipTitle = __('Labels'); } $sidebarLabelTooltip diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index d0a2b27b0e6..7e9a50a885d 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -4,6 +4,7 @@ import $ from 'jquery'; import _ from 'underscore'; +import { __ } from '~/locale'; import axios from './lib/utils/axios_utils'; import { timeFor } from './lib/utils/datetime_utility'; import ModalStore from './boards/stores/modal_store'; @@ -25,7 +26,7 @@ export default class MilestoneSelect { } $els.each((i, dropdown) => { - let collapsedSidebarLabelTemplate, milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; + let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; const $dropdown = $(dropdown); const projectId = $dropdown.data('projectId'); const milestonesUrl = $dropdown.data('milestones'); @@ -52,7 +53,6 @@ export default class MilestoneSelect { if (issueUpdateURL) { milestoneLinkTemplate = _.template('<%- title %>'); milestoneLinkNoneTemplate = 'None'; - collapsedSidebarLabelTemplate = _.template(' <%- title %> '); } return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, @@ -214,10 +214,16 @@ export default class MilestoneSelect { data.milestone.remaining = timeFor(data.milestone.due_date); data.milestone.name = data.milestone.title; $value.html(milestoneLinkTemplate(data.milestone)); - return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone)); + return $sidebarCollapsedValue + .attr('data-original-title', `${data.milestone.name}
${data.milestone.remaining}`) + .find('span') + .text(data.milestone.title); } else { $value.html(milestoneLinkNoneTemplate); - return $sidebarCollapsedValue.find('span').text('No'); + return $sidebarCollapsedValue + .attr('data-original-title', __('Milestone')) + .find('span') + .text(__('None')); } }) .catch(() => { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 2121907dff0..96f2b3eac98 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1427,7 +1427,7 @@ export default class Notes { const { discussion_html } = data; const lines = $(discussion_html).find('.line_holder'); lines.addClass('fade-in'); - $container.find('tbody').prepend(lines); + $container.find('.diff-content > table > tbody').prepend(lines); const fileHolder = $container.find('.file-holder'); $container.find('.line-holder-placeholder').remove(); syntaxHighlight(fileHolder); diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index 8ce938c958b..cbc2d80ee18 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -19,7 +19,7 @@ function getSystemDate(systemUtcOffsetSeconds) { const date = new Date(); const localUtcOffsetMinutes = 0 - date.getTimezoneOffset(); const systemUtcOffsetMinutes = systemUtcOffsetSeconds / 60; - date.setMinutes((date.getMinutes() - localUtcOffsetMinutes) + systemUtcOffsetMinutes); + date.setMinutes(date.getMinutes() - localUtcOffsetMinutes + systemUtcOffsetMinutes); return date; } @@ -35,18 +35,36 @@ function formatTooltipText({ date, count }) { return `${contribText}
${dateDayName} ${dateText}`; } -const initColorKey = () => d3.scaleLinear().range(['#acd5f2', '#254e77']).domain([0, 3]); +const initColorKey = () => + d3 + .scaleLinear() + .range(['#acd5f2', '#254e77']) + .domain([0, 3]); export default class ActivityCalendar { - constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) { + constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0, firstDayOfWeek = 0) { this.calendarActivitiesPath = calendarActivitiesPath; this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; this.daySpace = 1; this.daySize = 15; - this.daySizeWithSpace = this.daySize + (this.daySpace * 2); - this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + this.daySizeWithSpace = this.daySize + this.daySpace * 2; + this.monthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; this.months = []; + this.firstDayOfWeek = firstDayOfWeek; // Loop through the timestamps to create a group of objects // The group of objects will be grouped based on the day of the week they are @@ -70,7 +88,7 @@ export default class ActivityCalendar { // Create a new group array if this is the first day of the week // or if is first object - if ((day === 0 && i !== 0) || i === 0) { + if ((day === this.firstDayOfWeek && i !== 0) || i === 0) { this.timestampsTmp.push([]); group += 1; } @@ -109,21 +127,30 @@ export default class ActivityCalendar { } renderSvg(container, group) { - const width = ((group + 1) * this.daySizeWithSpace) + this.getExtraWidthPadding(group); - return d3.select(container) + const width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); + return d3 + .select(container) .append('svg') - .attr('width', width) - .attr('height', 167) - .attr('class', 'contrib-calendar'); + .attr('width', width) + .attr('height', 167) + .attr('class', 'contrib-calendar'); + } + + dayYPos(day) { + return this.daySizeWithSpace * ((day + 7 - this.firstDayOfWeek) % 7); } renderDays() { - this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g') + this.svg + .selectAll('g') + .data(this.timestampsTmp) + .enter() + .append('g') .attr('transform', (group, i) => { _.each(group, (stamp, a) => { if (a === 0 && stamp.day === 0) { const month = stamp.date.getMonth(); - const x = (this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace; + const x = this.daySizeWithSpace * i + 1 + this.daySizeWithSpace; const lastMonth = _.last(this.months); if ( lastMonth == null || @@ -133,86 +160,113 @@ export default class ActivityCalendar { } } }); - return `translate(${(this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace}, 18)`; + return `translate(${this.daySizeWithSpace * i + 1 + this.daySizeWithSpace}, 18)`; }) .selectAll('rect') - .data(stamp => stamp) - .enter() - .append('rect') - .attr('x', '0') - .attr('y', stamp => this.daySizeWithSpace * stamp.day) - .attr('width', this.daySize) - .attr('height', this.daySize) - .attr('fill', stamp => ( - stamp.count !== 0 ? this.color(Math.min(stamp.count, 40)) : '#ededed' - )) - .attr('title', stamp => formatTooltipText(stamp)) - .attr('class', 'user-contrib-cell js-tooltip') - .attr('data-container', 'body') - .on('click', this.clickDay); + .data(stamp => stamp) + .enter() + .append('rect') + .attr('x', '0') + .attr('y', stamp => this.dayYPos(stamp.day)) + .attr('width', this.daySize) + .attr('height', this.daySize) + .attr( + 'fill', + stamp => (stamp.count !== 0 ? this.color(Math.min(stamp.count, 40)) : '#ededed'), + ) + .attr('title', stamp => formatTooltipText(stamp)) + .attr('class', 'user-contrib-cell js-tooltip') + .attr('data-container', 'body') + .on('click', this.clickDay); } renderDayTitles() { const days = [ { text: 'M', - y: 29 + (this.daySizeWithSpace * 1), - }, { + y: 29 + this.dayYPos(1), + }, + { text: 'W', - y: 29 + (this.daySizeWithSpace * 3), - }, { + y: 29 + this.dayYPos(2), + }, + { text: 'F', - y: 29 + (this.daySizeWithSpace * 5), + y: 29 + this.dayYPos(3), }, ]; - this.svg.append('g') + this.svg + .append('g') .selectAll('text') - .data(days) - .enter() - .append('text') - .attr('text-anchor', 'middle') - .attr('x', 8) - .attr('y', day => day.y) - .text(day => day.text) - .attr('class', 'user-contrib-text'); + .data(days) + .enter() + .append('text') + .attr('text-anchor', 'middle') + .attr('x', 8) + .attr('y', day => day.y) + .text(day => day.text) + .attr('class', 'user-contrib-text'); } renderMonths() { - this.svg.append('g') + this.svg + .append('g') .attr('direction', 'ltr') .selectAll('text') - .data(this.months) - .enter() - .append('text') - .attr('x', date => date.x) - .attr('y', 10) - .attr('class', 'user-contrib-text') - .text(date => this.monthNames[date.month]); + .data(this.months) + .enter() + .append('text') + .attr('x', date => date.x) + .attr('y', 10) + .attr('class', 'user-contrib-text') + .text(date => this.monthNames[date.month]); } renderKey() { - const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions']; - const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; + const keyValues = [ + 'no contributions', + '1-9 contributions', + '10-19 contributions', + '20-29 contributions', + '30+ contributions', + ]; + const keyColors = [ + '#ededed', + this.colorKey(0), + this.colorKey(1), + this.colorKey(2), + this.colorKey(3), + ]; - this.svg.append('g') - .attr('transform', `translate(18, ${(this.daySizeWithSpace * 8) + 16})`) + this.svg + .append('g') + .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`) .selectAll('rect') - .data(keyColors) - .enter() - .append('rect') - .attr('width', this.daySize) - .attr('height', this.daySize) - .attr('x', (color, i) => this.daySizeWithSpace * i) - .attr('y', 0) - .attr('fill', color => color) - .attr('class', 'js-tooltip') - .attr('title', (color, i) => keyValues[i]) - .attr('data-container', 'body'); + .data(keyColors) + .enter() + .append('rect') + .attr('width', this.daySize) + .attr('height', this.daySize) + .attr('x', (color, i) => this.daySizeWithSpace * i) + .attr('y', 0) + .attr('fill', color => color) + .attr('class', 'js-tooltip') + .attr('title', (color, i) => keyValues[i]) + .attr('data-container', 'body'); } initColor() { - const colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)]; - return d3.scaleThreshold().domain([0, 10, 20, 30]).range(colorRange); + const colorRange = [ + '#ededed', + this.colorKey(0), + this.colorKey(1), + this.colorKey(2), + this.colorKey(3), + ]; + return d3 + .scaleThreshold() + .domain([0, 10, 20, 30]) + .range(colorRange); } clickDay(stamp) { @@ -227,14 +281,15 @@ export default class ActivityCalendar { $('.user-calendar-activities').html(LOADING_HTML); - axios.get(this.calendarActivitiesPath, { - params: { - date, - }, - responseType: 'text', - }) - .then(({ data }) => $('.user-calendar-activities').html(data)) - .catch(() => flash(__('An error occurred while retrieving calendar activity'))); + axios + .get(this.calendarActivitiesPath, { + params: { + date, + }, + responseType: 'text', + }) + .then(({ data }) => $('.user-calendar-activities').html(data)) + .catch(() => flash(__('An error occurred while retrieving calendar activity'))); } else { this.currentSelectedDate = ''; $('.user-calendar-activities').html(''); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 2088a49590a..6eb0b62fa1c 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -5,6 +5,7 @@ import _ from 'underscore'; import Cookies from 'js-cookie'; import flash from './flash'; import axios from './lib/utils/axios_utils'; +import { __ } from './locale'; function Sidebar(currentUser) { this.toggleTodo = this.toggleTodo.bind(this); @@ -41,12 +42,14 @@ Sidebar.prototype.addEventListeners = function() { }; Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { - var $allGutterToggleIcons, $this, $thisIcon; + var $allGutterToggleIcons, $this, isExpanded, tooltipLabel; e.preventDefault(); $this = $(this); - $thisIcon = $this.find('i'); + isExpanded = $this.find('i').hasClass('fa-angle-double-right'); + tooltipLabel = isExpanded ? __('Expand sidebar') : __('Collapse sidebar'); $allGutterToggleIcons = $('.js-sidebar-toggle i'); - if ($thisIcon.hasClass('fa-angle-double-right')) { + + if (isExpanded) { $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left'); $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); $('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); @@ -57,6 +60,9 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { if (gl.lazyLoader) gl.lazyLoader.loadCheck(); } + + $this.attr('data-original-title', tooltipLabel); + if (!triggered) { Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); } diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue index 1e7f46454bf..2d00e8ac7e0 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue @@ -1,6 +1,12 @@