Merge branch 'master' into ide-file-finder
This commit is contained in:
commit
bdc84d4f4a
|
@ -75,7 +75,7 @@ stages:
|
|||
|
||||
.use-mysql: &use-mysql
|
||||
services:
|
||||
- mysql:latest
|
||||
- mysql:5.7
|
||||
- redis:alpine
|
||||
|
||||
.rails5-variables: &rails5-variables
|
||||
|
|
207
CHANGELOG.md
207
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)
|
||||
|
|
|
@ -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.)
|
||||
|
||||
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 | 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 | 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 | 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")
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.95.0
|
||||
0.96.1
|
||||
|
|
26
LICENSE
26
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.
|
||||
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.
|
|
@ -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).
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')}<br />${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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
|
||||
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
|
||||
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
|
||||
}
|
||||
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}<br />${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(() => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}<br />${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');
|
||||
}
|
||||
|
||||
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,19 +160,20 @@ 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('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(
|
||||
'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')
|
||||
|
@ -156,16 +184,19 @@ export default class ActivityCalendar {
|
|||
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()
|
||||
|
@ -178,7 +209,8 @@ export default class ActivityCalendar {
|
|||
}
|
||||
|
||||
renderMonths() {
|
||||
this.svg.append('g')
|
||||
this.svg
|
||||
.append('g')
|
||||
.attr('direction', 'ltr')
|
||||
.selectAll('text')
|
||||
.data(this.months)
|
||||
|
@ -191,11 +223,24 @@ export default class ActivityCalendar {
|
|||
}
|
||||
|
||||
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()
|
||||
|
@ -211,8 +256,17 @@ export default class ActivityCalendar {
|
|||
}
|
||||
|
||||
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,7 +281,8 @@ export default class ActivityCalendar {
|
|||
|
||||
$('.user-calendar-activities').html(LOADING_HTML);
|
||||
|
||||
axios.get(this.calendarActivitiesPath, {
|
||||
axios
|
||||
.get(this.calendarActivitiesPath, {
|
||||
params: {
|
||||
date,
|
||||
},
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
name: 'Assignees',
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
rootPath: {
|
||||
type: String,
|
||||
|
@ -14,6 +20,11 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
require: true,
|
||||
default: 'issue',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -62,6 +73,12 @@ export default {
|
|||
names.push(`+ ${this.users.length - maxRender} more`);
|
||||
}
|
||||
|
||||
if (!this.users.length) {
|
||||
const emptyTooltipLabel = this.issuableType === 'issue' ?
|
||||
__('Assignee(s)') : __('Assignee');
|
||||
names.push(emptyTooltipLabel);
|
||||
}
|
||||
|
||||
return names.join(', ');
|
||||
},
|
||||
sidebarAvatarCounter() {
|
||||
|
@ -109,7 +126,8 @@ export default {
|
|||
<div>
|
||||
<div
|
||||
class="sidebar-collapsed-icon sidebar-collapsed-user"
|
||||
:class="{ 'multiple-users': hasMoreThanOneAssignee, 'has-tooltip': hasAssignees }"
|
||||
:class="{ 'multiple-users': hasMoreThanOneAssignee }"
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
data-placement="left"
|
||||
:title="collapsedTooltipTitle"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import Flash from '../../../flash';
|
||||
import Flash from '~/flash';
|
||||
import eventHub from '~/sidebar/event_hub';
|
||||
import Store from '~/sidebar/stores/sidebar_store';
|
||||
import AssigneeTitle from './assignee_title.vue';
|
||||
import Assignees from './assignees.vue';
|
||||
import Store from '../../stores/sidebar_store';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'SidebarAssignees',
|
||||
|
@ -25,6 +25,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
require: true,
|
||||
default: 'issue',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -90,6 +95,7 @@ export default {
|
|||
:users="store.assignees"
|
||||
:editable="store.editable"
|
||||
@assign-self="assignSelf"
|
||||
:issuable-type="issuableType"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
<script>
|
||||
import Flash from '../../../flash';
|
||||
import { __ } from '~/locale';
|
||||
import Flash from '~/flash';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import eventHub from '~/sidebar/event_hub';
|
||||
import editForm from './edit_form.vue';
|
||||
import Icon from '../../../vue_shared/components/icon.vue';
|
||||
import { __ } from '../../../locale';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editForm,
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
isConfidential: {
|
||||
required: true,
|
||||
|
@ -33,6 +37,9 @@ export default {
|
|||
confidentialityIcon() {
|
||||
return this.isConfidential ? 'eye-slash' : 'eye';
|
||||
},
|
||||
tooltipLabel() {
|
||||
return this.isConfidential ? __('Confidential') : __('Not confidential');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('closeConfidentialityForm', this.toggleForm);
|
||||
|
@ -65,6 +72,10 @@ export default {
|
|||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
@click="toggleForm"
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
data-placement="left"
|
||||
:title="tooltipLabel"
|
||||
>
|
||||
<icon
|
||||
:name="confidentialityIcon"
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import Flash from '~/flash';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import issuableMixin from '~/vue_shared/mixins/issuable';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import eventHub from '~/sidebar/event_hub';
|
||||
import editForm from './edit_form.vue';
|
||||
import issuableMixin from '../../../vue_shared/mixins/issuable';
|
||||
import Icon from '../../../vue_shared/components/icon.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editForm,
|
||||
Icon,
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
mixins: [issuableMixin],
|
||||
|
||||
props: {
|
||||
|
@ -44,6 +51,10 @@ export default {
|
|||
isLockDialogOpen() {
|
||||
return this.mediator.store.isLockDialogOpen;
|
||||
},
|
||||
|
||||
tooltipLabel() {
|
||||
return this.isLocked ? __('Locked') : __('Unlocked');
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
|
@ -85,6 +96,10 @@ export default {
|
|||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
@click="toggleForm"
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
data-placement="left"
|
||||
:title="tooltipLabel"
|
||||
>
|
||||
<icon
|
||||
:name="lockIcon"
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script>
|
||||
import { __, n__, sprintf } from '../../../locale';
|
||||
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
|
||||
import userAvatarImage from '../../../vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
import { __, n__, sprintf } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||
import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
loadingIcon,
|
||||
userAvatarImage,
|
||||
|
@ -72,7 +76,13 @@
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div class="sidebar-collapsed-icon">
|
||||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
data-placement="left"
|
||||
:title="participantLabel"
|
||||
>
|
||||
<i
|
||||
class="fa fa-users"
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
<script>
|
||||
import icon from '../../../vue_shared/components/icon.vue';
|
||||
import { abbreviateTime } from '../../../lib/utils/pretty_time';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { abbreviateTime } from '~/lib/utils/pretty_time';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
name: 'TimeTrackingCollapsedState',
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
showComparisonState: {
|
||||
type: Boolean,
|
||||
|
@ -79,6 +84,21 @@
|
|||
|
||||
return '';
|
||||
},
|
||||
timeTrackedTooltipText() {
|
||||
let title;
|
||||
if (this.showComparisonState) {
|
||||
title = __('Time remaining');
|
||||
} else if (this.showEstimateOnlyState) {
|
||||
title = __('Estimated');
|
||||
} else if (this.showSpentOnlyState) {
|
||||
title = __('Time spent');
|
||||
}
|
||||
|
||||
return sprintf('%{title}: %{text}', ({ title, text: this.text }));
|
||||
},
|
||||
tooltipText() {
|
||||
return this.showNoTimeTrackingState ? __('Time tracking') : this.timeTrackedTooltipText;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
abbreviateTime(timeStr) {
|
||||
|
@ -89,7 +109,13 @@
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sidebar-collapsed-icon">
|
||||
<div
|
||||
class="sidebar-collapsed-icon"
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
data-placement="left"
|
||||
:title="tooltipText"
|
||||
>
|
||||
<icon name="timer" />
|
||||
<div class="time-tracking-collapsed-summary">
|
||||
<div :class="divClass">
|
||||
|
|
|
@ -27,6 +27,7 @@ function mountAssigneesComponent(mediator) {
|
|||
mediator,
|
||||
field: el.dataset.field,
|
||||
signedIn: el.hasAttribute('data-signed-in'),
|
||||
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { __ } from './locale';
|
||||
import ModalStore from './boards/stores/modal_store';
|
||||
|
||||
// TODO: remove eventHub hack after code splitting refactor
|
||||
|
@ -182,7 +183,7 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
|
||||
return axios.put(issueURL, data)
|
||||
.then(({ data }) => {
|
||||
var user;
|
||||
var user, tooltipTitle;
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
if (data.assignee) {
|
||||
|
@ -191,15 +192,17 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
username: data.assignee.username,
|
||||
avatar: data.assignee.avatar_url
|
||||
};
|
||||
tooltipTitle = _.escape(user.name);
|
||||
} else {
|
||||
user = {
|
||||
name: 'Unassigned',
|
||||
username: '',
|
||||
avatar: ''
|
||||
};
|
||||
tooltipTitle = __('Assignee');
|
||||
}
|
||||
$value.html(assigneeTemplate(user));
|
||||
$collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
|
||||
$collapsedSidebar.attr('title', tooltipTitle).tooltip('fixTitle');
|
||||
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
<script>
|
||||
export default {
|
||||
import { __ } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
name: 'ToggleSidebar',
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tooltipLabel() {
|
||||
return this.collapsed ? __('Expand sidebar') : __('Collapse sidebar');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.$emit('toggle');
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -20,6 +31,10 @@
|
|||
type="button"
|
||||
class="btn btn-blank gutter-toggle btn-sidebar-action"
|
||||
@click="toggle"
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
data-placement="left"
|
||||
:title="tooltipLabel"
|
||||
>
|
||||
<i
|
||||
aria-label="toggle collapse"
|
||||
|
|
|
@ -247,6 +247,7 @@ $btn-sm-side-margin: 7px;
|
|||
$btn-xs-side-margin: 5px;
|
||||
$issue-status-expired: $orange-500;
|
||||
$issuable-sidebar-color: $gl-text-color-secondary;
|
||||
$sidebar-block-hover-color: #ebebeb;
|
||||
$group-path-color: #999;
|
||||
$namespace-kind-color: #aaa;
|
||||
$panel-heading-link-color: #777;
|
||||
|
@ -373,6 +374,8 @@ $dropdown-hover-color: $blue-400;
|
|||
$link-active-background: rgba(0, 0, 0, 0.04);
|
||||
$link-hover-background: rgba(0, 0, 0, 0.06);
|
||||
$inactive-badge-background: rgba(0, 0, 0, 0.08);
|
||||
$sidebar-toggle-height: 60px;
|
||||
$sidebar-milestone-toggle-bottom-margin: 10px;
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
|
|
|
@ -187,7 +187,12 @@
|
|||
padding-left: 10px;
|
||||
|
||||
&:hover {
|
||||
color: $gray-darkest;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,6 +373,14 @@
|
|||
padding: 15px 0 0;
|
||||
border-bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background-color: $sidebar-block-hover-color;
|
||||
}
|
||||
|
||||
&.issuable-sidebar-header {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.participants {
|
||||
|
@ -380,8 +393,17 @@
|
|||
|
||||
.gutter-toggle {
|
||||
width: 100%;
|
||||
height: $sidebar-toggle-height;
|
||||
margin-left: 0;
|
||||
padding-left: 25px;
|
||||
padding-left: 0;
|
||||
border-bottom: 1px solid $border-gray-dark;
|
||||
}
|
||||
|
||||
a.gutter-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
|
@ -428,10 +450,10 @@
|
|||
|
||||
.btn-clipboard {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: $issuable-sidebar-color;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,6 @@
|
|||
}
|
||||
|
||||
.milestone-sidebar {
|
||||
.gutter-toggle {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.milestone-progress {
|
||||
.title {
|
||||
padding-top: 5px;
|
||||
|
@ -102,7 +98,17 @@
|
|||
margin-right: 0;
|
||||
}
|
||||
|
||||
.right-sidebar-expanded & {
|
||||
.gutter-toggle {
|
||||
margin-bottom: $sidebar-milestone-toggle-bottom-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.right-sidebar-collapsed & {
|
||||
.milestone-progress {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.reference {
|
||||
border-top: 1px solid $border-gray-normal;
|
||||
}
|
||||
|
|
|
@ -134,11 +134,11 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
def can_create_branch
|
||||
can_create = current_user &&
|
||||
can?(current_user, :push_code, @project) &&
|
||||
@issue.can_be_worked_on?(current_user)
|
||||
@issue.can_be_worked_on?
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { can_create_branch: can_create, has_related_branch: @issue.has_related_branch? }
|
||||
render json: { can_create_branch: can_create, suggested_branch_name: @issue.suggested_branch_name }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -177,7 +177,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def authorize_create_merge_request!
|
||||
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
|
||||
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?
|
||||
end
|
||||
|
||||
def render_issue_json
|
||||
|
|
|
@ -78,8 +78,6 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
result.merge!(trace.to_h)
|
||||
end
|
||||
|
||||
result[:html] = result[:html].presence || 'No job log'
|
||||
|
||||
render json: result
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,11 +28,12 @@ class Projects::RepositoriesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def assign_archive_vars
|
||||
@id = params[:id]
|
||||
|
||||
return unless @id
|
||||
|
||||
@ref, @filename = extract_ref(@id)
|
||||
if params[:id]
|
||||
@ref, @filename = extract_ref(params[:id])
|
||||
else
|
||||
@ref = params[:ref]
|
||||
@filename = nil
|
||||
end
|
||||
rescue InvalidPathError
|
||||
render_404
|
||||
end
|
||||
|
|
|
@ -9,6 +9,32 @@ module IssuablesHelper
|
|||
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
|
||||
end
|
||||
|
||||
def sidebar_gutter_tooltip_text
|
||||
sidebar_gutter_collapsed? ? _('Expand sidebar') : _('Collapse sidebar')
|
||||
end
|
||||
|
||||
def sidebar_assignee_tooltip_label(issuable)
|
||||
if issuable.assignee
|
||||
issuable.assignee.name
|
||||
else
|
||||
issuable.allows_multiple_assignees? ? _('Assignee(s)') : _('Assignee')
|
||||
end
|
||||
end
|
||||
|
||||
def sidebar_due_date_tooltip_label(issuable)
|
||||
if issuable.due_date
|
||||
"#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
|
||||
else
|
||||
_('Due date')
|
||||
end
|
||||
end
|
||||
|
||||
def due_date_remaining_days(issuable)
|
||||
remaining_days_in_words = remaining_days_in_words(issuable)
|
||||
|
||||
"#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})"
|
||||
end
|
||||
|
||||
def multi_label_name(current_labels, default_label)
|
||||
if current_labels && current_labels.any?
|
||||
title = current_labels.first.try(:title)
|
||||
|
@ -153,10 +179,14 @@ module IssuablesHelper
|
|||
def issuable_labels_tooltip(labels, limit: 5)
|
||||
first, last = labels.partition.with_index { |_, i| i < limit }
|
||||
|
||||
if labels && labels.any?
|
||||
label_names = first.collect(&:name)
|
||||
label_names << "and #{last.size} more" unless last.empty?
|
||||
|
||||
label_names.join(', ')
|
||||
else
|
||||
_("Labels")
|
||||
end
|
||||
end
|
||||
|
||||
def issuables_state_counter_text(issuable_type, state, display_count)
|
||||
|
@ -321,7 +351,7 @@ module IssuablesHelper
|
|||
def issuable_todo_button_data(issuable, todo, is_collapsed)
|
||||
{
|
||||
todo_text: "Add todo",
|
||||
mark_text: "Mark done",
|
||||
mark_text: "Mark todo as done",
|
||||
todo_icon: (is_collapsed ? icon('plus-square') : nil),
|
||||
mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
|
||||
issuable_id: issuable.id,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
module MilestonesHelper
|
||||
include EntityDateHelper
|
||||
|
||||
def milestones_filter_path(opts = {})
|
||||
if @project
|
||||
project_milestones_path(@project, opts)
|
||||
|
@ -72,6 +74,19 @@ module MilestonesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def milestone_progress_tooltip_text(milestone)
|
||||
has_issues = milestone.total_issues_count(current_user) > 0
|
||||
|
||||
if has_issues
|
||||
[
|
||||
_('Progress'),
|
||||
_("%{percent}%% complete") % { percent: milestone.percent_complete(current_user) }
|
||||
].join('<br />')
|
||||
else
|
||||
_('Progress')
|
||||
end
|
||||
end
|
||||
|
||||
def milestone_progress_bar(milestone)
|
||||
options = {
|
||||
class: 'progress-bar progress-bar-success',
|
||||
|
@ -95,27 +110,69 @@ module MilestonesHelper
|
|||
end
|
||||
|
||||
def milestone_tooltip_title(milestone)
|
||||
if milestone.due_date
|
||||
[milestone.due_date.to_s(:medium), "(#{milestone_remaining_days(milestone)})"].join(' ')
|
||||
if milestone
|
||||
"#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}"
|
||||
else
|
||||
_('Milestone')
|
||||
end
|
||||
end
|
||||
|
||||
def milestone_remaining_days(milestone)
|
||||
if milestone.expired?
|
||||
content_tag(:strong, 'Past due')
|
||||
elsif milestone.upcoming?
|
||||
content_tag(:strong, 'Upcoming')
|
||||
elsif milestone.due_date
|
||||
time_ago = time_ago_in_words(milestone.due_date)
|
||||
content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" }
|
||||
content.slice!("about ")
|
||||
content << " remaining"
|
||||
content.html_safe
|
||||
elsif milestone.start_date && milestone.start_date.past?
|
||||
days = milestone.elapsed_days
|
||||
content = content_tag(:strong, days)
|
||||
content << " #{'day'.pluralize(days)} elapsed"
|
||||
def milestone_time_for(date, date_type)
|
||||
title = date_type == :start ? "Start date" : "End date"
|
||||
|
||||
if date
|
||||
time_ago = time_ago_in_words(date)
|
||||
time_ago.slice!("about ")
|
||||
|
||||
time_ago << if date.past?
|
||||
" ago"
|
||||
else
|
||||
" remaining"
|
||||
end
|
||||
|
||||
content = [
|
||||
title,
|
||||
"<br />",
|
||||
date.to_s(:medium),
|
||||
"(#{time_ago})"
|
||||
].join(" ")
|
||||
|
||||
content.html_safe
|
||||
else
|
||||
title
|
||||
end
|
||||
end
|
||||
|
||||
def milestone_issues_tooltip_text(milestone)
|
||||
issues = milestone.count_issues_by_state(current_user)
|
||||
|
||||
return _("Issues") if issues.empty?
|
||||
|
||||
content = []
|
||||
|
||||
content << n_("1 open issue", "%d open issues", issues["opened"]) % issues["opened"] if issues["opened"]
|
||||
content << n_("1 closed issue", "%d closed issues", issues["closed"]) % issues["closed"] if issues["closed"]
|
||||
|
||||
content.join('<br />').html_safe
|
||||
end
|
||||
|
||||
def milestone_merge_requests_tooltip_text(milestone)
|
||||
merge_requests = milestone.merge_requests
|
||||
|
||||
return _("Merge requests") if merge_requests.empty?
|
||||
|
||||
content = []
|
||||
|
||||
content << n_("1 open merge request", "%d open merge requests", merge_requests.opened.count) % merge_requests.opened.count if merge_requests.opened.any?
|
||||
content << n_("1 closed merge request", "%d closed merge requests", merge_requests.closed.count) % merge_requests.closed.count if merge_requests.closed.any?
|
||||
content << n_("1 merged merge request", "%d merged merge requests", merge_requests.merged.count) % merge_requests.merged.count if merge_requests.merged.any?
|
||||
|
||||
content.join('<br />').html_safe
|
||||
end
|
||||
|
||||
def milestone_tooltip_due_date(milestone)
|
||||
if milestone.due_date
|
||||
"#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ class Commit
|
|||
end
|
||||
|
||||
def notes_with_associations
|
||||
notes.includes(:author)
|
||||
notes.includes(:author, :award_emoji)
|
||||
end
|
||||
|
||||
def merge_requests
|
||||
|
|
|
@ -27,8 +27,9 @@ module AtomicInternalId
|
|||
module ClassMethods
|
||||
def has_internal_id(column, scope:, init:) # rubocop:disable Naming/PredicateName
|
||||
before_validation(on: :create) do
|
||||
if read_attribute(column).blank?
|
||||
scope_attrs = { scope => association(scope).reader }
|
||||
scope_value = association(scope).reader
|
||||
if read_attribute(column).blank? && scope_value
|
||||
scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
|
||||
usage = self.class.table_name.to_sym
|
||||
|
||||
new_iid = InternalId.generate_next(self, scope_attrs, usage, init)
|
||||
|
|
|
@ -102,14 +102,14 @@ module Milestoneish
|
|||
Gitlab::TimeTrackingFormatter.output(total_issue_time_estimate)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def count_issues_by_state(user)
|
||||
memoize_per_user(user, :count_issues_by_state) do
|
||||
issues_visible_to_user(user).reorder(nil).group(:state).count
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def memoize_per_user(user, method_name)
|
||||
memoized_users[method_name][user&.id] ||= yield
|
||||
end
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
module NonatomicInternalId
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
validate :set_iid, on: :create
|
||||
validates :iid, presence: true, numericality: true
|
||||
end
|
||||
|
||||
def set_iid
|
||||
if iid.blank?
|
||||
parent = project || group
|
||||
records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend
|
||||
max_iid = records.maximum(:iid)
|
||||
|
||||
self.iid = max_iid.to_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
iid.to_s
|
||||
end
|
||||
end
|
|
@ -1,13 +1,21 @@
|
|||
# Uniquify
|
||||
#
|
||||
# Return a version of the given 'base' string that is unique
|
||||
# by appending a counter to it. Uniqueness is determined by
|
||||
# repeated calls to the passed block.
|
||||
#
|
||||
# You can pass an initial value for the counter, if not given
|
||||
# counting starts from 1.
|
||||
#
|
||||
# If `base` is a function/proc, we expect that calling it with a
|
||||
# candidate counter returns a string to test/return.
|
||||
class Uniquify
|
||||
# Return a version of the given 'base' string that is unique
|
||||
# by appending a counter to it. Uniqueness is determined by
|
||||
# repeated calls to the passed block.
|
||||
#
|
||||
# If `base` is a function/proc, we expect that calling it with a
|
||||
# candidate counter returns a string to test/return.
|
||||
def initialize(counter = nil)
|
||||
@counter = counter
|
||||
end
|
||||
|
||||
def string(base)
|
||||
@base = base
|
||||
@counter = nil
|
||||
|
||||
increment_counter! while yield(base_string)
|
||||
base_string
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
class Deployment < ActiveRecord::Base
|
||||
include NonatomicInternalId
|
||||
include AtomicInternalId
|
||||
|
||||
belongs_to :project, required: true
|
||||
belongs_to :environment, required: true
|
||||
belongs_to :user
|
||||
belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
|
||||
has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.deployments&.maximum(:iid) }
|
||||
|
||||
validates :sha, presence: true
|
||||
validates :ref, presence: true
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
# * (Optionally) add columns to `internal_ids` if needed for scope.
|
||||
class InternalId < ActiveRecord::Base
|
||||
belongs_to :project
|
||||
belongs_to :namespace
|
||||
|
||||
enum usage: { issues: 0 }
|
||||
enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4 }
|
||||
|
||||
validates :usage, presence: true
|
||||
|
||||
|
|
|
@ -194,6 +194,15 @@ class Issue < ActiveRecord::Base
|
|||
branches_with_iid - branches_with_merge_request
|
||||
end
|
||||
|
||||
def suggested_branch_name
|
||||
return to_branch_name unless project.repository.branch_exists?(to_branch_name)
|
||||
|
||||
start_counting_from = 2
|
||||
Uniquify.new(start_counting_from).string(-> (counter) { "#{to_branch_name}-#{counter}" }) do |suggested_branch_name|
|
||||
project.repository.branch_exists?(suggested_branch_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns boolean if a related branch exists for the current issue
|
||||
# ignores merge requests branchs
|
||||
def has_related_branch?
|
||||
|
@ -248,11 +257,8 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def can_be_worked_on?(current_user)
|
||||
!self.closed? &&
|
||||
!self.project.forked? &&
|
||||
self.related_branches(current_user).empty? &&
|
||||
self.closed_by_merge_requests(current_user).empty?
|
||||
def can_be_worked_on?
|
||||
!self.closed? && !self.project.forked?
|
||||
end
|
||||
|
||||
# Returns `true` if the current issue can be viewed by either a logged in User
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class MergeRequest < ActiveRecord::Base
|
||||
include NonatomicInternalId
|
||||
include AtomicInternalId
|
||||
include Issuable
|
||||
include Noteable
|
||||
include Referable
|
||||
|
@ -18,6 +18,8 @@ class MergeRequest < ActiveRecord::Base
|
|||
belongs_to :source_project, class_name: "Project"
|
||||
belongs_to :merge_user, class_name: "User"
|
||||
|
||||
has_internal_id :iid, scope: :target_project, init: ->(s) { s&.target_project&.merge_requests&.maximum(:iid) }
|
||||
|
||||
has_many :merge_request_diffs
|
||||
|
||||
has_one :merge_request_diff,
|
||||
|
|
|
@ -8,7 +8,7 @@ class Milestone < ActiveRecord::Base
|
|||
Started = MilestoneStruct.new('Started', '#started', -3)
|
||||
|
||||
include CacheMarkdownField
|
||||
include NonatomicInternalId
|
||||
include AtomicInternalId
|
||||
include Sortable
|
||||
include Referable
|
||||
include StripAttribute
|
||||
|
@ -21,6 +21,9 @@ class Milestone < ActiveRecord::Base
|
|||
belongs_to :project
|
||||
belongs_to :group
|
||||
|
||||
has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.milestones&.maximum(:iid) }
|
||||
has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) }
|
||||
|
||||
has_many :issues
|
||||
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
|
||||
has_many :merge_requests
|
||||
|
|
|
@ -947,10 +947,13 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def manageable_groups
|
||||
union = Gitlab::SQL::Union.new([owned_groups.select(:id),
|
||||
masters_groups.select(:id)])
|
||||
arel_union = Arel::Nodes::SqlLiteral.new(union.to_sql)
|
||||
owned_and_master_groups = Group.where(Group.arel_table[:id].in(arel_union))
|
||||
union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), masters_groups.select(:id)]).to_sql
|
||||
|
||||
# Update this line to not use raw SQL when migrated to Rails 5.2.
|
||||
# Either ActiveRecord or Arel constructions are fine.
|
||||
# This was replaced with the raw SQL construction because of bugs in the arel gem.
|
||||
# Bugs were fixed in arel 9.0.0 (Rails 5.2).
|
||||
owned_and_master_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection
|
||||
|
||||
Gitlab::GroupHierarchy.new(owned_and_master_groups).base_and_descendants
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module EntityDateHelper
|
||||
include ActionView::Helpers::DateHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def interval_in_words(diff)
|
||||
return 'Not started' unless diff
|
||||
|
@ -34,4 +35,30 @@ module EntityDateHelper
|
|||
|
||||
duration_hash
|
||||
end
|
||||
|
||||
# Generates an HTML-formatted string for remaining dates based on start_date and due_date
|
||||
#
|
||||
# It returns "Past due" for expired entities
|
||||
# It returns "Upcoming" for upcoming entities
|
||||
# If due date is provided, it returns "# days|weeks|months remaining|ago"
|
||||
# If start date is provided and elapsed, with no due date, it returns "# days elapsed"
|
||||
def remaining_days_in_words(entity)
|
||||
if entity.try(:expired?)
|
||||
content_tag(:strong, 'Past due')
|
||||
elsif entity.try(:upcoming?)
|
||||
content_tag(:strong, 'Upcoming')
|
||||
elsif entity.due_date
|
||||
is_upcoming = (entity.due_date - Date.today).to_i > 0
|
||||
time_ago = time_ago_in_words(entity.due_date)
|
||||
content = time_ago.gsub(/\d+/) { |match| "<strong>#{match}</strong>" }
|
||||
content.slice!("about ")
|
||||
content << " " + (is_upcoming ? _("remaining") : _("ago"))
|
||||
content.html_safe
|
||||
elsif entity.start_date && entity.start_date.past?
|
||||
days = entity.elapsed_days
|
||||
content = content_tag(:strong, days)
|
||||
content << " #{'day'.pluralize(days)} elapsed"
|
||||
content.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,9 +8,10 @@ module Projects
|
|||
template_name = params.delete(:template_name)
|
||||
file = Gitlab::ProjectTemplate.find(template_name).file
|
||||
|
||||
override_params = params.dup
|
||||
params[:file] = file
|
||||
|
||||
GitlabProjectsImportService.new(current_user, params).execute
|
||||
GitlabProjectsImportService.new(current_user, params, override_params).execute
|
||||
|
||||
ensure
|
||||
file&.close
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
- if issue.milestone
|
||||
%span.issuable-milestone.hidden-xs
|
||||
|
||||
= link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: issuable_milestone_tooltip_title(issue) } do
|
||||
= link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do
|
||||
= icon('clock-o')
|
||||
= issue.milestone.title
|
||||
- if issue.due_date
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
- if merge_request.milestone
|
||||
%span.issuable-milestone.hidden-xs
|
||||
|
||||
= link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: issuable_milestone_tooltip_title(merge_request) } do
|
||||
= link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_due_date(merge_request.milestone) } do
|
||||
= icon('clock-o')
|
||||
= merge_request.milestone.title
|
||||
- if merge_request.target_project.default_branch != merge_request.target_branch
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
- if current_user
|
||||
%span.issuable-header-text.hide-collapsed.pull-left
|
||||
= _('Todo')
|
||||
%a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
|
||||
%a.gutter-toggle.pull-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left' } }
|
||||
= sidebar_gutter_toggle_icon
|
||||
- if current_user
|
||||
= render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable
|
||||
|
@ -19,11 +19,10 @@
|
|||
.block.assignee
|
||||
= render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
|
||||
.block.milestone
|
||||
.sidebar-collapsed-icon
|
||||
.sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= icon('clock-o', 'aria-hidden': 'true')
|
||||
%span.milestone-title
|
||||
- if issuable.milestone
|
||||
%span.has-tooltip{ title: "#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}", data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= issuable.milestone.title
|
||||
- else
|
||||
= _('None')
|
||||
|
@ -34,7 +33,7 @@
|
|||
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right'
|
||||
.value.hide-collapsed
|
||||
- if issuable.milestone
|
||||
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_title(issuable.milestone), data: { container: "body", html: 1 }
|
||||
= link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 1 }
|
||||
- else
|
||||
%span.no-value
|
||||
= _('None')
|
||||
|
@ -50,7 +49,7 @@
|
|||
= icon('spinner spin', 'aria-hidden': 'true')
|
||||
- if issuable.has_attribute?(:due_date)
|
||||
.block.due_date
|
||||
.sidebar-collapsed-icon
|
||||
.sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 1 }, title: sidebar_due_date_tooltip_label(issuable) }
|
||||
= icon('calendar', 'aria-hidden': 'true')
|
||||
%span.js-due-date-sidebar-value
|
||||
= issuable.due_date.try(:to_s, :medium) || 'None'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
= _('Assignee')
|
||||
= icon('spinner spin')
|
||||
- else
|
||||
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
|
||||
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: sidebar_assignee_tooltip_label(issuable) }
|
||||
- if issuable.assignee
|
||||
= link_to_member(@project, issuable.assignee, size: 24)
|
||||
- else
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
- is_collapsed = local_assigns.fetch(:is_collapsed, false)
|
||||
- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark done')
|
||||
- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark todo as done')
|
||||
- todo_content = is_collapsed ? icon('plus-square') : _('Add todo')
|
||||
|
||||
%button.issuable-todo-btn.js-issuable-todo{ type: 'button',
|
||||
class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn pull-right'),
|
||||
title: (todo.nil? ? _('Add todo') : _('Mark done')),
|
||||
'aria-label' => (todo.nil? ? _('Add todo') : _('Mark done')),
|
||||
title: (todo.nil? ? _('Add todo') : _('Mark todo as done')),
|
||||
'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')),
|
||||
data: issuable_todo_button_data(issuable, todo, is_collapsed) }
|
||||
%span.issuable-todo-inner.js-issuable-todo-inner<
|
||||
- if todo
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- merge_request = issuable
|
||||
.block.assignee
|
||||
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (merge_request.assignee.name if merge_request.assignee) }
|
||||
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: sidebar_assignee_tooltip_label(issuable) }
|
||||
- if merge_request.assignee
|
||||
= link_to_member(@project, merge_request.assignee, size: 24)
|
||||
- else
|
||||
|
|
|
@ -4,12 +4,8 @@
|
|||
%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
|
||||
.issuable-sidebar.milestone-sidebar
|
||||
.block.milestone-progress.issuable-sidebar-header
|
||||
%a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
|
||||
%a.gutter-toggle.pull-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left' } }
|
||||
= sidebar_gutter_toggle_icon
|
||||
|
||||
.sidebar-collapsed-icon
|
||||
%span== #{milestone.percent_complete(current_user)}%
|
||||
= milestone_progress_bar(milestone)
|
||||
.title.hide-collapsed
|
||||
%strong.bold== #{milestone.percent_complete(current_user)}%
|
||||
%span.hide-collapsed
|
||||
|
@ -17,6 +13,11 @@
|
|||
.value.hide-collapsed
|
||||
= milestone_progress_bar(milestone)
|
||||
|
||||
.block.milestone-progress.hide-expanded
|
||||
.sidebar-collapsed-icon.has-tooltip{ title: milestone_progress_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
%span== #{milestone.percent_complete(current_user)}%
|
||||
= milestone_progress_bar(milestone)
|
||||
|
||||
.block.start_date.hide-collapsed
|
||||
.title
|
||||
Start date
|
||||
|
@ -35,18 +36,24 @@
|
|||
%span.collapsed-milestone-date
|
||||
- if milestone.start_date && milestone.due_date
|
||||
- if milestone.start_date.year == milestone.due_date.year
|
||||
.milestone-date= milestone.start_date.strftime('%b %-d')
|
||||
.milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= milestone.start_date.strftime('%b %-d')
|
||||
- else
|
||||
.milestone-date= milestone.start_date.strftime('%b %-d %Y')
|
||||
.milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= milestone.start_date.strftime('%b %-d %Y')
|
||||
.date-separator -
|
||||
.due_date= milestone.due_date.strftime('%b %-d %Y')
|
||||
.due_date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= milestone.due_date.strftime('%b %-d %Y')
|
||||
- elsif milestone.start_date
|
||||
From
|
||||
.milestone-date= milestone.start_date.strftime('%b %-d %Y')
|
||||
.milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= milestone.start_date.strftime('%b %-d %Y')
|
||||
- elsif milestone.due_date
|
||||
Until
|
||||
.milestone-date= milestone.due_date.strftime('%b %-d %Y')
|
||||
.milestone-date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
= milestone.due_date.strftime('%b %-d %Y')
|
||||
- else
|
||||
.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
None
|
||||
.title.hide-collapsed
|
||||
Due date
|
||||
|
@ -58,14 +65,14 @@
|
|||
%span.bold= milestone.due_date.to_s(:medium)
|
||||
- else
|
||||
%span.no-value No due date
|
||||
- remaining_days = milestone_remaining_days(milestone)
|
||||
- remaining_days = remaining_days_in_words(milestone)
|
||||
- if remaining_days.present?
|
||||
= surround '(', ')' do
|
||||
%span.remaining-days= remaining_days
|
||||
|
||||
- if !project || can?(current_user, :read_issue, project)
|
||||
.block.issues
|
||||
.sidebar-collapsed-icon
|
||||
.sidebar-collapsed-icon.has-tooltip{ title: milestone_issues_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
%strong
|
||||
= custom_icon('issues')
|
||||
%span= milestone.issues_visible_to_user(current_user).count
|
||||
|
@ -93,7 +100,7 @@
|
|||
= icon('spinner spin')
|
||||
|
||||
.block.merge-requests
|
||||
.sidebar-collapsed-icon
|
||||
.sidebar-collapsed-icon.has-tooltip{ title: milestone_merge_requests_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } }
|
||||
%strong
|
||||
= custom_icon('mr_bold')
|
||||
%span= milestone.merge_requests.count
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Enable restore rake task to handle nested storage directories
|
||||
merge_request: 17516
|
||||
author: Balasankar C
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Add support for patch link extension for commit links on GitLab Flavored Markdown
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Include matching branches and tags in protected branches / tags count
|
||||
merge_request:
|
||||
author: Jan Beckmann
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Send notification emails when push to a merge request
|
||||
merge_request: 7610
|
||||
author: YarNayar
|
||||
type: feature
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve tooltips in collapsed right sidebar
|
||||
merge_request: 17714
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Adds cancel btn to new pages domain page
|
||||
merge_request: 18026
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Atomic generation of internal ids for issues.
|
||||
merge_request: 17580
|
||||
author:
|
||||
type: other
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Create Deploy Tokens to allow permanent access to repository and registry
|
||||
merge_request: 17894
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Drop JSON response in Project Milestone along with avoiding error
|
||||
merge_request: 17977
|
||||
author: Takuya Noguchi
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix generated URL when listing repoitories for import
|
||||
merge_request: 17692
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: lazy load diffs on merge request discussions
|
||||
merge_request:
|
||||
author:
|
||||
type: performance
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fixed bug in dropdown selector when selecting the same selection again
|
||||
merge_request: 14631
|
||||
author: bitsapien
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add an API endpoint to download git repository snapshots
|
||||
merge_request: 18173
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Apply NestingDepth (level 5) (framework/dropdowns.scss)
|
||||
merge_request: 17820
|
||||
author: Takuya Noguchi
|
||||
type: other
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: 'API: Add parameter merge_method to projects'
|
||||
merge_request: 18031
|
||||
author: Jan Beckmann
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Add object storage support for LFS objects, CI artifacts, and uploads.
|
||||
merge_request: 17358
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Increase dropdown width in pipeline graph & center action icon
|
||||
merge_request: 18089
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: 'Introduce simpler env vars for auto devops REPLICAS and CANARY_REPLICAS #41436'
|
||||
merge_request: 18036
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Added confirmation modal for changing username
|
||||
merge_request: 17405
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Adds the option to the project export API to override the project description and display GitLab export description once imported
|
||||
merge_request: 17744
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: adds closed by informations in issue api
|
||||
merge_request: 17042
|
||||
author: haseebeqx
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix XSS on diff view stored on filenames
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Long instance urls do not overflow anymore during project creation
|
||||
merge_request: 17717
|
||||
author:
|
||||
type: fixed
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: Improved visual styles and consistency for commit hash and possible actions
|
||||
across commit lists
|
||||
merge_request: 17406
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Improve empty state for canceled job
|
||||
merge_request: 17646
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix hover style of dropdown items in the right sidebar
|
||||
merge_request: 17519
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show new branch/mr button even when branch exists
|
||||
merge_request: 17712
|
||||
author: Jacopo Beschi @jacopo-beschi
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix Firefox stealing formatting characters on issue notes
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Improve performance of loading issues with lots of references to merge requests
|
||||
merge_request: 17986
|
||||
author:
|
||||
type: performance
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Polish design for verifying domains
|
||||
merge_request: 17767
|
||||
author:
|
||||
type: changed
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: Require at least one filter when listing issues or merge requests on dashboard
|
||||
page
|
||||
merge_request:
|
||||
author:
|
||||
type: performance
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Use specific names for filtered CI variable controller parameters
|
||||
merge_request: 17796
|
||||
author:
|
||||
type: other
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Add empty repo check before running AutoDevOps pipeline
|
||||
merge_request: 17605
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Adds support for OmniAuth JWT provider
|
||||
merge_request: 17774
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Limit the number of failed logins when using LDAP for authentication
|
||||
merge_request: 43525
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Improves the performance of projects list page
|
||||
merge_request: 17934
|
||||
author:
|
||||
type: performance
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Move ci/lint under project's namespace
|
||||
merge_request: 17729
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Update wording to specify create/manage project vs group labels in labels dropdown
|
||||
merge_request: 17640
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Set breadcrumb for admin/runners/show
|
||||
merge_request: 17431
|
||||
author: Takuya Noguchi
|
||||
type: fixed
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: Update documentation to reflect current minimum required versions of node and
|
||||
yarn
|
||||
merge_request: 17706
|
||||
author:
|
||||
type: other
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Store sha256 checksum of artifact metadata
|
||||
merge_request: 18149
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Change avatar error message to include allowed file formats
|
||||
merge_request: 17747
|
||||
author: Fabian Schneider
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Add tooltips to icons in lists of issues and merge requests
|
||||
merge_request: 17700
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Add Gitaly call details to performance bar
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Update ruby-saml to 1.7.2 and omniauth-saml to 1.10.0
|
||||
merge_request: 17734
|
||||
author: Takuya Noguchi
|
||||
type: security
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: Send @mention notifications even if a user has explicitly unsubscribed from
|
||||
item
|
||||
merge_request:
|
||||
author:
|
||||
type: added
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue