Merge branch 'master' into feature/runner-per-group
This commit is contained in:
commit
d39b3d4b8d
|
|
@ -72,3 +72,4 @@ eslint-report.html
|
|||
/locale/**/*.time_stamp
|
||||
/.rspec
|
||||
/plugins/*
|
||||
/.gitlab_pages_secret
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ stages:
|
|||
# Jobs that only need to pull cache
|
||||
.dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *except-docs
|
||||
<<: *pull-cache
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
|
|
@ -122,6 +122,10 @@ stages:
|
|||
variables:
|
||||
SETUP_DB: "false"
|
||||
|
||||
.dedicated-no-docs-and-no-qa-pull-cache-job: &dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *except-docs-and-qa
|
||||
|
||||
.rake-exec: &rake-exec
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
script:
|
||||
|
|
@ -222,7 +226,7 @@ stages:
|
|||
- master@gitlab/gitlab-ee
|
||||
|
||||
.gitlab-setup: &gitlab-setup
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
<<: *use-pg
|
||||
variables:
|
||||
CREATE_DB_USER: "true"
|
||||
|
|
@ -262,12 +266,12 @@ stages:
|
|||
|
||||
# DB migration, rollback, and seed jobs
|
||||
.db-migrate-reset: &db-migrate-reset
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
script:
|
||||
- bundle exec rake db:migrate:reset
|
||||
|
||||
.migration-paths: &migration-paths
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
variables:
|
||||
CREATE_DB_USER: "true"
|
||||
script:
|
||||
|
|
@ -647,7 +651,7 @@ migration:path-mysql:
|
|||
<<: *use-mysql
|
||||
|
||||
.db-rollback: &db-rollback
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
script:
|
||||
- bundle exec rake db:migrate VERSION=20170523121229
|
||||
- bundle exec rake db:migrate
|
||||
|
|
@ -670,7 +674,7 @@ gitlab:setup-mysql:
|
|||
|
||||
# Frontend-related jobs
|
||||
gitlab:assets:compile:
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
dependencies: []
|
||||
variables:
|
||||
NODE_ENV: "production"
|
||||
|
|
@ -691,7 +695,7 @@ gitlab:assets:compile:
|
|||
- webpack-report/
|
||||
|
||||
karma:
|
||||
<<: *dedicated-no-docs-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
<<: *use-pg
|
||||
dependencies:
|
||||
- compile-assets
|
||||
|
|
@ -815,7 +819,7 @@ coverage:
|
|||
- coverage/assets/
|
||||
|
||||
lint:javascript:report:
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
stage: post-test
|
||||
dependencies:
|
||||
- compile-assets
|
||||
|
|
|
|||
36
CHANGELOG.md
36
CHANGELOG.md
|
|
@ -2,6 +2,28 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 10.7.3 (2018-05-02)
|
||||
|
||||
### Fixed (8 changes)
|
||||
|
||||
- Fixed wrong avatar URL when the avatar is on object storage. !18092
|
||||
- Fix errors on pushing to an empty repository. !18462
|
||||
- Update doorkeeper to 4.3.2 to fix GitLab OAuth authentication. !18543
|
||||
- Ports omniauth-jwt gem onto GitLab OmniAuth Strategies suite. !18580
|
||||
- Fix redirection error for applications using OpenID. !18599
|
||||
- Fix commit trailer rendering when Gravatar is disabled.
|
||||
- Fix file_store for artifacts and lfs when saving.
|
||||
- Fix users not seeing labels from private groups when being a member of a child project.
|
||||
|
||||
|
||||
## 10.7.2 (2018-04-25)
|
||||
|
||||
### Security (2 changes)
|
||||
|
||||
- Serve archive requests with the correct file in all cases.
|
||||
- Sanitizes user name to avoid XSS attacks.
|
||||
|
||||
|
||||
## 10.7.1 (2018-04-23)
|
||||
|
||||
### Fixed (11 changes)
|
||||
|
|
@ -237,6 +259,13 @@ entry.
|
|||
- Upgrade Gitaly to upgrade its charlock_holmes.
|
||||
|
||||
|
||||
## 10.6.5 (2018-04-24)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Sanitizes user name to avoid XSS attacks.
|
||||
|
||||
|
||||
## 10.6.4 (2018-04-09)
|
||||
|
||||
### Fixed (8 changes, 1 of them is from the community)
|
||||
|
|
@ -478,6 +507,13 @@ entry.
|
|||
- Use host URL to build JIRA remote link icon.
|
||||
|
||||
|
||||
## 10.5.8 (2018-04-24)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Sanitizes user name to avoid XSS attacks.
|
||||
|
||||
|
||||
## 10.5.7 (2018-04-03)
|
||||
|
||||
### Security (2 changes)
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
|
|||
- [Workflow labels](#workflow-labels)
|
||||
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
|
||||
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
|
||||
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc)
|
||||
- [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc)
|
||||
- [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release)
|
||||
- [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc)
|
||||
- [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc)
|
||||
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
|
||||
- [Implement design & UI elements](#implement-design-ui-elements)
|
||||
- [Implement design & UI elements](#implement-design--ui-elements)
|
||||
- [Issue tracker](#issue-tracker)
|
||||
- [Issue triaging](#issue-triaging)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
|
|
@ -114,7 +114,7 @@ is a great place to start. Issues with a lower weight (1 or 2) are deemed
|
|||
suitable for beginners. These issues will be of reasonable size and challenge,
|
||||
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
|
||||
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
|
||||
please consider we favor
|
||||
please consider we favor
|
||||
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
|
||||
|
||||
## Workflow labels
|
||||
|
|
@ -127,7 +127,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.
|
||||
- Team: ~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.
|
||||
- Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release"
|
||||
- Priority: ~P1, ~P2, ~P3, ~P4
|
||||
- Severity: ~S1, ~S2, ~S3, ~S4
|
||||
|
|
@ -171,13 +171,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api,
|
|||
|
||||
Subject labels are always all-lowercase.
|
||||
|
||||
### Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)
|
||||
### Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)
|
||||
|
||||
Team labels specify what team is responsible for this issue.
|
||||
Assigning a team label makes sure issues get the attention of the appropriate
|
||||
people.
|
||||
|
||||
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
|
||||
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Quality,
|
||||
~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
|
||||
|
||||
The descriptions on the [labels page][labels-page] explain what falls under the
|
||||
|
|
@ -199,12 +199,12 @@ release. There are three levels of Milestone labels:
|
|||
- ~Stretch: Issues that are a stretch goal for delivering in the current
|
||||
milestone. If these issues are not done in the current release, they will
|
||||
strongly be considered for the next release.
|
||||
- ~"Next Patch Release": Issues to put in the next patch release. Work on these
|
||||
- ~"Next Patch Release": Issues to put in the next patch release. Work on these
|
||||
first, and add the "Pick Into X" label to the merge request, along with the
|
||||
appropriate milestone.
|
||||
|
||||
Each issue scheduled for the current milestone should be labeled ~Deliverable
|
||||
or ~"Stretch". Any open issue for a previous milestone should be labeled
|
||||
or ~"Stretch". Any open issue for a previous milestone should be labeled
|
||||
~"Next Patch Release", or otherwise rescheduled to a different milestone.
|
||||
|
||||
### Bug Priority labels (~P1, ~P2, ~P3 & etc.)
|
||||
|
|
@ -221,16 +221,16 @@ This label documents the planned timeline & urgency which is used to measure aga
|
|||
|
||||
#### Specific Priority guidance
|
||||
|
||||
| Label | Availability / Performance |
|
||||
| Label | Availability / Performance |
|
||||
|-------|--------------------------------------------------------------|
|
||||
| ~P1 | |
|
||||
| ~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.
|
||||
Severity labels help us clearly communicate the impact of a ~bug on users.
|
||||
|
||||
| Label | Meaning | Impact of the defect | Example |
|
||||
|-------|-------------------|-------------------------------------------------------|---------|
|
||||
|
|
@ -241,9 +241,9 @@ Severity labels help us clearly communicate the impact of a ~bug on users.
|
|||
|
||||
#### Specific Severity guidance
|
||||
|
||||
| Label | Security Impact |
|
||||
| Label | Security Impact |
|
||||
|-------|-------------------------------------------------------------------|
|
||||
| ~S1 | >50% customers impacted (possible company extinction level event) |
|
||||
| ~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 |
|
||||
|
|
@ -727,4 +727,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
|
||||
[^1]: Please note that specs other than JavaScript specs are considered backend
|
||||
code.
|
||||
|
||||
|
|
@ -1 +1 @@
|
|||
0.96.1
|
||||
0.96.2
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.8.0
|
||||
0.9.1
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
4.1.0
|
||||
4.2.0
|
||||
|
|
|
|||
7
Gemfile
7
Gemfile
|
|
@ -51,7 +51,6 @@ gem 'omniauth-shibboleth', '~> 1.2.0'
|
|||
gem 'omniauth-twitter', '~> 1.4'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'omniauth-authentiq', '~> 0.3.1'
|
||||
gem 'omniauth-jwt', '~> 0.0.2'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
gem 'jwt', '~> 1.5.6'
|
||||
|
||||
|
|
@ -185,6 +184,9 @@ gem 're2', '~> 1.1.1'
|
|||
|
||||
gem 'version_sorter', '~> 2.1.0'
|
||||
|
||||
# User agent parsing
|
||||
gem 'device_detector'
|
||||
|
||||
# Cache
|
||||
gem 'redis-rails', '~> 5.0.2'
|
||||
|
||||
|
|
@ -283,7 +285,6 @@ gem 'batch-loader', '~> 1.2.1'
|
|||
gem 'peek', '~> 1.0.1'
|
||||
gem 'peek-gc', '~> 0.0.2'
|
||||
gem 'peek-mysql2', '~> 1.1.0', group: :mysql
|
||||
gem 'peek-performance_bar', '~> 1.3.0'
|
||||
gem 'peek-pg', '~> 1.3.0', group: :postgres
|
||||
gem 'peek-rblineprof', '~> 0.2.0'
|
||||
gem 'peek-redis', '~> 1.2.0'
|
||||
|
|
@ -416,7 +417,7 @@ end
|
|||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly'
|
||||
gem 'grpc', '~> 1.10.0'
|
||||
gem 'grpc', '~> 1.11.0'
|
||||
|
||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||
gem 'google-protobuf', '= 3.5.1'
|
||||
|
|
|
|||
13
Gemfile.lock
13
Gemfile.lock
|
|
@ -161,6 +161,7 @@ GEM
|
|||
activerecord (>= 3.2.0, < 5.1)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
device_detector (1.0.0)
|
||||
devise (4.2.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
|
|
@ -374,7 +375,7 @@ GEM
|
|||
rake
|
||||
grape_logging (1.7.0)
|
||||
grape
|
||||
grpc (1.10.0)
|
||||
grpc (1.11.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
googleauth (>= 0.5.1, < 0.7)
|
||||
|
|
@ -555,9 +556,6 @@ GEM
|
|||
jwt (>= 1.5)
|
||||
omniauth (>= 1.1.1)
|
||||
omniauth-oauth2 (>= 1.5)
|
||||
omniauth-jwt (0.0.2)
|
||||
jwt
|
||||
omniauth (~> 1.1)
|
||||
omniauth-kerberos (0.3.0)
|
||||
omniauth-multipassword
|
||||
timfel-krb5-auth (~> 0.8)
|
||||
|
|
@ -603,8 +601,6 @@ GEM
|
|||
atomic (>= 1.0.0)
|
||||
mysql2
|
||||
peek
|
||||
peek-performance_bar (1.3.1)
|
||||
peek (>= 0.1.0)
|
||||
peek-pg (1.3.0)
|
||||
concurrent-ruby
|
||||
concurrent-ruby-ext
|
||||
|
|
@ -1031,6 +1027,7 @@ DEPENDENCIES
|
|||
database_cleaner (~> 1.5.0)
|
||||
deckar01-task_list (= 2.0.0)
|
||||
default_value_for (~> 3.0.0)
|
||||
device_detector
|
||||
devise (~> 4.2)
|
||||
devise-two-factor (~> 3.0.0)
|
||||
diffy (~> 3.1.0)
|
||||
|
|
@ -1078,7 +1075,7 @@ DEPENDENCIES
|
|||
grape-entity (~> 0.6.0)
|
||||
grape-route-helpers (~> 2.1.0)
|
||||
grape_logging (~> 1.7)
|
||||
grpc (~> 1.10.0)
|
||||
grpc (~> 1.11.0)
|
||||
haml_lint (~> 0.26.0)
|
||||
hamlit (~> 2.6.1)
|
||||
hashie-forbidden_attributes
|
||||
|
|
@ -1119,7 +1116,6 @@ DEPENDENCIES
|
|||
omniauth-github (~> 1.1.1)
|
||||
omniauth-gitlab (~> 1.0.2)
|
||||
omniauth-google-oauth2 (~> 0.5.3)
|
||||
omniauth-jwt (~> 0.0.2)
|
||||
omniauth-kerberos (~> 0.3.0)
|
||||
omniauth-oauth2-generic (~> 0.2.2)
|
||||
omniauth-saml (~> 1.10)
|
||||
|
|
@ -1130,7 +1126,6 @@ DEPENDENCIES
|
|||
peek (~> 1.0.1)
|
||||
peek-gc (~> 0.0.2)
|
||||
peek-mysql2 (~> 1.1.0)
|
||||
peek-performance_bar (~> 1.3.0)
|
||||
peek-pg (~> 1.3.0)
|
||||
peek-rblineprof (~> 0.2.0)
|
||||
peek-redis (~> 1.2.0)
|
||||
|
|
|
|||
|
|
@ -304,12 +304,12 @@ GEM
|
|||
flowdock (~> 0.7)
|
||||
gitlab-grit (>= 2.4.1)
|
||||
multi_json
|
||||
gitlab-gollum-lib (4.2.7.1)
|
||||
gitlab-gollum-lib (4.2.7.2)
|
||||
gemojione (~> 3.2)
|
||||
github-markup (~> 1.6)
|
||||
gollum-grit_adapter (~> 1.0)
|
||||
nokogiri (>= 1.6.1, < 2.0)
|
||||
rouge (~> 2.1)
|
||||
rouge (~> 3.1)
|
||||
sanitize (~> 2.1)
|
||||
stringex (~> 2.6)
|
||||
gitlab-gollum-rugged_adapter (0.4.4)
|
||||
|
|
@ -602,8 +602,6 @@ GEM
|
|||
atomic (>= 1.0.0)
|
||||
mysql2
|
||||
peek
|
||||
peek-performance_bar (1.3.1)
|
||||
peek (>= 0.1.0)
|
||||
peek-pg (1.3.0)
|
||||
concurrent-ruby
|
||||
concurrent-ruby-ext
|
||||
|
|
@ -752,7 +750,7 @@ GEM
|
|||
retriable (3.1.1)
|
||||
rinku (2.0.4)
|
||||
rotp (2.1.2)
|
||||
rouge (2.2.1)
|
||||
rouge (3.1.1)
|
||||
rqrcode (0.10.1)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode-rails3 (0.1.7)
|
||||
|
|
@ -1134,7 +1132,6 @@ DEPENDENCIES
|
|||
peek (~> 1.0.1)
|
||||
peek-gc (~> 0.0.2)
|
||||
peek-mysql2 (~> 1.1.0)
|
||||
peek-performance_bar (~> 1.3.0)
|
||||
peek-pg (~> 1.3.0)
|
||||
peek-rblineprof (~> 0.2.0)
|
||||
peek-redis (~> 1.2.0)
|
||||
|
|
@ -1166,7 +1163,7 @@ DEPENDENCIES
|
|||
redis-rails (~> 5.0.2)
|
||||
request_store (~> 1.3)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 2.0)
|
||||
rouge (~> 3.1)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-parameterized
|
||||
rspec-rails (~> 3.6.0)
|
||||
|
|
|
|||
|
|
@ -7,27 +7,24 @@ export default function installGlEmojiElement() {
|
|||
const GlEmojiElementProto = Object.create(HTMLElement.prototype);
|
||||
GlEmojiElementProto.createdCallback = function createdCallback() {
|
||||
const emojiUnicode = this.textContent.trim();
|
||||
const {
|
||||
name,
|
||||
unicodeVersion,
|
||||
fallbackSrc,
|
||||
fallbackSpriteClass,
|
||||
} = this.dataset;
|
||||
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset;
|
||||
|
||||
const isEmojiUnicode = this.childNodes && Array.prototype.every.call(
|
||||
this.childNodes,
|
||||
childNode => childNode.nodeType === 3,
|
||||
);
|
||||
const isEmojiUnicode =
|
||||
this.childNodes &&
|
||||
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
|
||||
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
|
||||
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
|
||||
|
||||
if (
|
||||
emojiUnicode &&
|
||||
isEmojiUnicode &&
|
||||
!isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
|
||||
) {
|
||||
if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) {
|
||||
// CSS sprite fallback takes precedence over image fallback
|
||||
if (hasCssSpriteFalback) {
|
||||
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
|
||||
const emojiSpriteLinkTag = document.createElement('link');
|
||||
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
|
||||
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
|
||||
document.head.appendChild(emojiSpriteLinkTag);
|
||||
gon.emoji_sprites_css_added = true;
|
||||
}
|
||||
// IE 11 doesn't like adding multiple at once :(
|
||||
this.classList.add('emoji-icon');
|
||||
this.classList.add(fallbackSpriteClass);
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
|
||||
|
||||
import $ from 'jquery';
|
||||
import { localTimeAgo } from './lib/utils/datetime_utility';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
|
||||
export default class Compare {
|
||||
constructor(opts) {
|
||||
this.opts = opts;
|
||||
this.source_loading = $(".js-source-loading");
|
||||
this.target_loading = $(".js-target-loading");
|
||||
$('.js-compare-dropdown').each((function(_this) {
|
||||
return function(i, dropdown) {
|
||||
var $dropdown;
|
||||
$dropdown = $(dropdown);
|
||||
return $dropdown.glDropdown({
|
||||
selectable: true,
|
||||
fieldName: $dropdown.data('fieldName'),
|
||||
filterable: true,
|
||||
id: function(obj, $el) {
|
||||
return $el.data('id');
|
||||
},
|
||||
toggleLabel: function(obj, $el) {
|
||||
return $el.text().trim();
|
||||
},
|
||||
clicked: function(e, el) {
|
||||
if ($dropdown.is('.js-target-branch')) {
|
||||
return _this.getTargetHtml();
|
||||
} else if ($dropdown.is('.js-source-branch')) {
|
||||
return _this.getSourceHtml();
|
||||
} else if ($dropdown.is('.js-target-project')) {
|
||||
return _this.getTargetProject();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
})(this));
|
||||
this.initialState();
|
||||
}
|
||||
|
||||
initialState() {
|
||||
this.getSourceHtml();
|
||||
this.getTargetHtml();
|
||||
}
|
||||
|
||||
getTargetProject() {
|
||||
$('.mr_target_commit').empty();
|
||||
|
||||
return axios.get(this.opts.targetProjectUrl, {
|
||||
params: {
|
||||
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
|
||||
},
|
||||
}).then(({ data }) => {
|
||||
$('.js-target-branch-dropdown .dropdown-content').html(data);
|
||||
});
|
||||
}
|
||||
|
||||
getSourceHtml() {
|
||||
return this.constructor.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
|
||||
ref: $("input[name='merge_request[source_branch]']").val()
|
||||
});
|
||||
}
|
||||
|
||||
getTargetHtml() {
|
||||
return this.constructor.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
|
||||
target_project_id: $("input[name='merge_request[target_project_id]']").val(),
|
||||
ref: $("input[name='merge_request[target_branch]']").val()
|
||||
});
|
||||
}
|
||||
|
||||
static sendAjax(url, loading, target, params) {
|
||||
const $target = $(target);
|
||||
|
||||
loading.show();
|
||||
$target.empty();
|
||||
|
||||
return axios.get(url, {
|
||||
params,
|
||||
}).then(({ data }) => {
|
||||
loading.hide();
|
||||
$target.html(data);
|
||||
const className = '.' + $target[0].className.replace(' ', '.');
|
||||
localTimeAgo($('.js-timeago', className));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,9 @@ import $ from 'jquery';
|
|||
import { __ } from './locale';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import flash from './flash';
|
||||
import { capitalizeFirstCharacter } from './lib/utils/text_utility';
|
||||
|
||||
export default function initCompareAutocomplete() {
|
||||
export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) {
|
||||
$('.js-compare-dropdown').each(function() {
|
||||
var $dropdown, selected;
|
||||
$dropdown = $(this);
|
||||
|
|
@ -15,14 +16,27 @@ export default function initCompareAutocomplete() {
|
|||
const $filterInput = $('input[type="search"]', $dropdownContainer);
|
||||
$dropdown.glDropdown({
|
||||
data: function(term, callback) {
|
||||
axios.get($dropdown.data('refsUrl'), {
|
||||
params: {
|
||||
ref: $dropdown.data('ref'),
|
||||
search: term,
|
||||
},
|
||||
}).then(({ data }) => {
|
||||
callback(data);
|
||||
}).catch(() => flash(__('Error fetching refs')));
|
||||
const params = {
|
||||
ref: $dropdown.data('ref'),
|
||||
search: term,
|
||||
};
|
||||
|
||||
if (limitTo) {
|
||||
params.find = limitTo;
|
||||
}
|
||||
|
||||
axios
|
||||
.get($dropdown.data('refsUrl'), {
|
||||
params,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (limitTo) {
|
||||
callback(data[capitalizeFirstCharacter(limitTo)] || []);
|
||||
} else {
|
||||
callback(data);
|
||||
}
|
||||
})
|
||||
.catch(() => flash(__('Error fetching refs')));
|
||||
},
|
||||
selectable: true,
|
||||
filterable: true,
|
||||
|
|
@ -32,9 +46,15 @@ export default function initCompareAutocomplete() {
|
|||
renderRow: function(ref) {
|
||||
var link;
|
||||
if (ref.header != null) {
|
||||
return $('<li />').addClass('dropdown-header').text(ref.header);
|
||||
return $('<li />')
|
||||
.addClass('dropdown-header')
|
||||
.text(ref.header);
|
||||
} else {
|
||||
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
|
||||
link = $('<a />')
|
||||
.attr('href', '#')
|
||||
.addClass(ref === selected ? 'is-active' : '')
|
||||
.text(ref)
|
||||
.attr('data-ref', escape(ref));
|
||||
return $('<li />').append(link);
|
||||
}
|
||||
},
|
||||
|
|
@ -43,9 +63,10 @@ export default function initCompareAutocomplete() {
|
|||
},
|
||||
toggleLabel: function(obj, $el) {
|
||||
return $el.text().trim();
|
||||
}
|
||||
},
|
||||
clicked: () => clickHandler($dropdown),
|
||||
});
|
||||
$filterInput.on('keyup', (e) => {
|
||||
$filterInput.on('keyup', e => {
|
||||
const keyCode = e.keyCode || e.which;
|
||||
if (keyCode !== 13) return;
|
||||
const text = $filterInput.val();
|
||||
|
|
@ -54,7 +75,7 @@ export default function initCompareAutocomplete() {
|
|||
$dropdownContainer.removeClass('open');
|
||||
});
|
||||
|
||||
$dropdownContainer.on('click', '.dropdown-content a', (e) => {
|
||||
$dropdownContainer.on('click', '.dropdown-content a', e => {
|
||||
$dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
|
||||
if ($dropdown.hasClass('has-tooltip')) {
|
||||
$dropdown.tooltip('fixTitle');
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function getEmojiCategoryMap() {
|
|||
symbols: [],
|
||||
flags: [],
|
||||
};
|
||||
Object.keys(emojiMap).forEach((name) => {
|
||||
Object.keys(emojiMap).forEach(name => {
|
||||
const emoji = emojiMap[name];
|
||||
if (emojiCategoryMap[emoji.category]) {
|
||||
emojiCategoryMap[emoji.category].push(name);
|
||||
|
|
@ -79,7 +79,9 @@ export function glEmojiTag(inputName, options) {
|
|||
classList.push(fallbackSpriteClass);
|
||||
}
|
||||
const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
|
||||
const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : '';
|
||||
const fallbackSpriteAttribute = opts.sprite
|
||||
? `data-fallback-sprite-class="${fallbackSpriteClass}"`
|
||||
: '';
|
||||
let contents = emojiInfo.moji;
|
||||
if (opts.forceFallback && !opts.sprite) {
|
||||
contents = emojiImageTag(name, fallbackImageSrc);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ const unicodeSupportTestMap = {
|
|||
function checkPixelInImageDataArray(pixelOffset, imageDataArray) {
|
||||
// `4 *` because RGBA
|
||||
const indexOffset = 4 * pixelOffset;
|
||||
const hasColor = imageDataArray[indexOffset + 0] ||
|
||||
const hasColor =
|
||||
imageDataArray[indexOffset + 0] ||
|
||||
imageDataArray[indexOffset + 1] ||
|
||||
imageDataArray[indexOffset + 2];
|
||||
const isVisible = imageDataArray[indexOffset + 3];
|
||||
|
|
@ -75,23 +76,23 @@ const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatche
|
|||
const fontSize = 16;
|
||||
function generateUnicodeSupportMap(testMap) {
|
||||
const testMapKeys = Object.keys(testMap);
|
||||
const numTestEntries = testMapKeys
|
||||
.reduce((list, testKey) => list.concat(testMap[testKey]), []).length;
|
||||
const numTestEntries = testMapKeys.reduce((list, testKey) => list.concat(testMap[testKey]), [])
|
||||
.length;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
(window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas;
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = (2 * fontSize);
|
||||
canvas.height = (numTestEntries * fontSize);
|
||||
canvas.width = 2 * fontSize;
|
||||
canvas.height = numTestEntries * fontSize;
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
|
||||
// Write each emoji to the canvas vertically
|
||||
let writeIndex = 0;
|
||||
testMapKeys.forEach((testKey) => {
|
||||
testMapKeys.forEach(testKey => {
|
||||
const testEntry = testMap[testKey];
|
||||
[].concat(testEntry).forEach((emojiUnicode) => {
|
||||
ctx.fillText(emojiUnicode, 0, (writeIndex * fontSize) + (fontSize / 2));
|
||||
[].concat(testEntry).forEach(emojiUnicode => {
|
||||
ctx.fillText(emojiUnicode, 0, writeIndex * fontSize + fontSize / 2);
|
||||
writeIndex += 1;
|
||||
});
|
||||
});
|
||||
|
|
@ -99,29 +100,25 @@ function generateUnicodeSupportMap(testMap) {
|
|||
// Read from the canvas
|
||||
const resultMap = {};
|
||||
let readIndex = 0;
|
||||
testMapKeys.forEach((testKey) => {
|
||||
testMapKeys.forEach(testKey => {
|
||||
const testEntry = testMap[testKey];
|
||||
// This needs to be a `reduce` instead of `every` because we need to
|
||||
// keep the `readIndex` in sync from the writes by running all entries
|
||||
const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => {
|
||||
const isTestSatisfied = [].concat(testEntry).reduce(isSatisfied => {
|
||||
// Sample along the vertical-middle for a couple of characters
|
||||
const imageData = ctx.getImageData(
|
||||
0,
|
||||
(readIndex * fontSize) + (fontSize / 2),
|
||||
2 * fontSize,
|
||||
1,
|
||||
).data;
|
||||
const imageData = ctx.getImageData(0, readIndex * fontSize + fontSize / 2, 2 * fontSize, 1)
|
||||
.data;
|
||||
|
||||
let isValidEmoji = false;
|
||||
for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) {
|
||||
const isLookingAtFirstChar = currentPixel < fontSize;
|
||||
const isLookingAtSecondChar = currentPixel >= (fontSize + (fontSize / 2));
|
||||
const isLookingAtSecondChar = currentPixel >= fontSize + fontSize / 2;
|
||||
// Check for the emoji somewhere along the row
|
||||
if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) {
|
||||
isValidEmoji = true;
|
||||
|
||||
// Check to see that nothing is rendered next to the first character
|
||||
// to ensure that the ZWJ sequence rendered as one piece
|
||||
// Check to see that nothing is rendered next to the first character
|
||||
// to ensure that the ZWJ sequence rendered as one piece
|
||||
} else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) {
|
||||
isValidEmoji = false;
|
||||
break;
|
||||
|
|
@ -170,7 +167,10 @@ export default function getUnicodeSupportMap() {
|
|||
if (isLocalStorageAvailable) {
|
||||
window.localStorage.setItem('gl-emoji-version', GL_EMOJI_VERSION);
|
||||
window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
|
||||
window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
|
||||
window.localStorage.setItem(
|
||||
'gl-emoji-unicode-support-map',
|
||||
JSON.stringify(unicodeSupportMap),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,11 +26,18 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
forceModifiedIcon: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
changedIcon() {
|
||||
const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : '';
|
||||
return this.file.tempFile ? `file-addition${suffix}` : `file-modified${suffix}`;
|
||||
return this.file.tempFile && !this.forceModifiedIcon
|
||||
? `file-addition${suffix}`
|
||||
: `file-modified${suffix}`;
|
||||
},
|
||||
stagedIcon() {
|
||||
return `${this.changedIcon}-solid`;
|
||||
|
|
|
|||
|
|
@ -1,49 +1,54 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import newModal from './modal.vue';
|
||||
import upload from './upload.vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import newModal from './modal.vue';
|
||||
import upload from './upload.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
newModal,
|
||||
upload,
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
newModal,
|
||||
upload,
|
||||
},
|
||||
props: {
|
||||
branch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
branch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openModal: false,
|
||||
modalType: '',
|
||||
dropdownOpen: false,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openModal: false,
|
||||
modalType: '',
|
||||
dropdownOpen: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
dropdownOpen() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dropdownMenu.scrollIntoView();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'createTempEntry',
|
||||
]),
|
||||
createNewItem(type) {
|
||||
this.modalType = type;
|
||||
this.openModal = true;
|
||||
this.dropdownOpen = false;
|
||||
},
|
||||
hideModal() {
|
||||
this.openModal = false;
|
||||
},
|
||||
openDropdown() {
|
||||
this.dropdownOpen = !this.dropdownOpen;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createTempEntry']),
|
||||
createNewItem(type) {
|
||||
this.modalType = type;
|
||||
this.openModal = true;
|
||||
this.dropdownOpen = false;
|
||||
},
|
||||
};
|
||||
hideModal() {
|
||||
this.openModal = false;
|
||||
},
|
||||
openDropdown() {
|
||||
this.dropdownOpen = !this.dropdownOpen;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -71,7 +76,10 @@
|
|||
css-classes="pull-left"
|
||||
/>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<ul
|
||||
class="dropdown-menu dropdown-menu-right"
|
||||
ref="dropdownMenu"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
|
|
|
|||
|
|
@ -40,13 +40,6 @@ export default {
|
|||
|
||||
return __('Create file');
|
||||
},
|
||||
formLabelName() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Directory name');
|
||||
}
|
||||
|
||||
return __('File name');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fieldName.focus();
|
||||
|
|
@ -82,8 +75,8 @@ export default {
|
|||
@submit.prevent="createEntryInStore"
|
||||
>
|
||||
<fieldset class="form-group append-bottom-0">
|
||||
<label class="label-light col-sm-3">
|
||||
{{ formLabelName }}
|
||||
<label class="label-light col-sm-3 ide-new-modal-label">
|
||||
{{ __('Name') }}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export default {
|
|||
:file="file"
|
||||
/>
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
<span class="pull-right ide-file-icon-holder">
|
||||
<mr-file-icon
|
||||
v-if="file.mrChange"
|
||||
/>
|
||||
|
|
@ -106,7 +106,8 @@ export default {
|
|||
:file="file"
|
||||
:show-tooltip="true"
|
||||
:show-staged-icon="true"
|
||||
class="prepend-top-5 pull-right"
|
||||
:force-modified-icon="true"
|
||||
class="pull-right"
|
||||
/>
|
||||
</span>
|
||||
<new-dropdown
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export default {
|
|||
<changed-file-icon
|
||||
v-else
|
||||
:file="tab"
|
||||
:force-modified-icon="true"
|
||||
/>
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const toggleRightPanelCollapsed = (
|
||||
{ dispatch, state },
|
||||
e = undefined,
|
||||
) => {
|
||||
export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => {
|
||||
if (e) {
|
||||
$(e.currentTarget)
|
||||
.tooltip('hide')
|
||||
|
|
@ -77,7 +74,7 @@ export const createTempEntry = (
|
|||
}
|
||||
|
||||
worker.addEventListener('message', ({ data }) => {
|
||||
const { file } = data;
|
||||
const { file, parentPath } = data;
|
||||
|
||||
worker.terminate();
|
||||
|
||||
|
|
@ -93,6 +90,10 @@ export const createTempEntry = (
|
|||
dispatch('setFileActive', file.path);
|
||||
}
|
||||
|
||||
if (parentPath && !state.entries[parentPath].opened) {
|
||||
commit(types.TOGGLE_TREE_OPEN, parentPath);
|
||||
}
|
||||
|
||||
resolve(file);
|
||||
});
|
||||
|
||||
|
|
@ -137,6 +138,14 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
|
|||
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
|
||||
};
|
||||
|
||||
export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, tempFile }) => {
|
||||
commit(types.UPDATE_TEMP_FLAG, { path: file.path, tempFile });
|
||||
|
||||
if (file.parentPath) {
|
||||
dispatch('updateTempFlagForEntry', { file: state.entries[file.parentPath], tempFile });
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleFileFinder = ({ commit }, fileFindVisible) =>
|
||||
commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
|
|||
const file = state.entries[path];
|
||||
commit(types.TOGGLE_LOADING, { entry: file });
|
||||
return service
|
||||
.getFileData(file.url)
|
||||
.getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url}`)
|
||||
.then(res => {
|
||||
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
|
||||
setPageTitle(pageTitle);
|
||||
|
|
|
|||
|
|
@ -110,6 +110,17 @@ export const updateFilesAfterCommit = (
|
|||
{ root: true },
|
||||
);
|
||||
|
||||
commit(
|
||||
rootTypes.TOGGLE_FILE_CHANGED,
|
||||
{
|
||||
file,
|
||||
changed: false,
|
||||
},
|
||||
{ root: true },
|
||||
);
|
||||
|
||||
dispatch('updateTempFlagForEntry', { file, tempFile: false }, { root: true });
|
||||
|
||||
eventHub.$emit(`editor.update.model.content.${file.key}`, {
|
||||
content: file.content,
|
||||
changed: !!changedFile,
|
||||
|
|
|
|||
|
|
@ -59,4 +59,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
|
|||
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
|
||||
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
|
||||
|
||||
export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG';
|
||||
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request';
|
|||
import fileMutations from './mutations/file';
|
||||
import treeMutations from './mutations/tree';
|
||||
import branchMutations from './mutations/branch';
|
||||
import { sortTree } from './utils';
|
||||
|
||||
export default {
|
||||
[types.SET_INITIAL_DATA](state, data) {
|
||||
|
|
@ -73,7 +74,7 @@ export default {
|
|||
f => foundEntry.tree.find(e => e.path === f.path) === undefined,
|
||||
);
|
||||
Object.assign(foundEntry, {
|
||||
tree: foundEntry.tree.concat(tree),
|
||||
tree: sortTree(foundEntry.tree.concat(tree)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -86,10 +87,16 @@ export default {
|
|||
|
||||
if (!foundEntry) {
|
||||
Object.assign(state.trees[`${projectId}/${branchId}`], {
|
||||
tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList),
|
||||
tree: sortTree(state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList)),
|
||||
});
|
||||
}
|
||||
},
|
||||
[types.UPDATE_TEMP_FLAG](state, { path, tempFile }) {
|
||||
Object.assign(state.entries[path], {
|
||||
tempFile,
|
||||
changed: tempFile,
|
||||
});
|
||||
},
|
||||
[types.UPDATE_VIEWER](state, viewer) {
|
||||
Object.assign(state, {
|
||||
viewer,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const dataStructure = () => ({
|
|||
raw: '',
|
||||
content: '',
|
||||
parentTreeUrl: '',
|
||||
parentPath: '',
|
||||
renderError: false,
|
||||
base64: false,
|
||||
editorRow: 1,
|
||||
|
|
@ -65,6 +66,7 @@ export const decorateData = entity => {
|
|||
previewMode,
|
||||
file_lock,
|
||||
html,
|
||||
parentPath = '',
|
||||
} = entity;
|
||||
|
||||
return {
|
||||
|
|
@ -81,6 +83,7 @@ export const decorateData = entity => {
|
|||
opened,
|
||||
active,
|
||||
parentTreeUrl,
|
||||
parentPath,
|
||||
changed,
|
||||
renderError,
|
||||
content,
|
||||
|
|
@ -121,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => {
|
|||
} else if (a.type === 'blob' && b.type === 'tree') {
|
||||
return 1;
|
||||
}
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
|
||||
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ self.addEventListener('message', e => {
|
|||
|
||||
const treeList = [];
|
||||
let file;
|
||||
let parentPath;
|
||||
const entries = data.reduce((acc, path) => {
|
||||
const pathSplit = path.split('/');
|
||||
const blobName = pathSplit.pop().trim();
|
||||
|
|
@ -17,6 +18,8 @@ self.addEventListener('message', e => {
|
|||
const foundEntry = acc[folderPath];
|
||||
|
||||
if (!foundEntry) {
|
||||
parentPath = parentFolder ? parentFolder.path : null;
|
||||
|
||||
const tree = decorateData({
|
||||
projectId,
|
||||
branchId,
|
||||
|
|
@ -29,6 +32,7 @@ self.addEventListener('message', e => {
|
|||
tempFile,
|
||||
changed: tempFile,
|
||||
opened: tempFile,
|
||||
parentPath,
|
||||
});
|
||||
|
||||
Object.assign(acc, {
|
||||
|
|
@ -52,6 +56,8 @@ self.addEventListener('message', e => {
|
|||
|
||||
if (blobName !== '') {
|
||||
const fileFolder = acc[pathSplit.join('/')];
|
||||
parentPath = fileFolder ? fileFolder.path : null;
|
||||
|
||||
file = decorateData({
|
||||
projectId,
|
||||
branchId,
|
||||
|
|
@ -66,6 +72,7 @@ self.addEventListener('message', e => {
|
|||
content,
|
||||
base64,
|
||||
previewMode: viewerInformationForPath(blobName),
|
||||
parentPath,
|
||||
});
|
||||
|
||||
Object.assign(acc, {
|
||||
|
|
@ -86,5 +93,6 @@ self.addEventListener('message', e => {
|
|||
entries,
|
||||
treeList: sortTree(treeList),
|
||||
file,
|
||||
parentPath,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store';
|
|||
export default class MilestoneSelect {
|
||||
constructor(currentProject, els, options = {}) {
|
||||
if (currentProject !== null) {
|
||||
this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
|
||||
this.currentProject =
|
||||
typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
|
||||
}
|
||||
|
||||
this.init(els, options);
|
||||
|
|
@ -26,7 +27,10 @@ export default class MilestoneSelect {
|
|||
}
|
||||
|
||||
$els.each((i, dropdown) => {
|
||||
let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault;
|
||||
let milestoneLinkNoneTemplate,
|
||||
milestoneLinkTemplate,
|
||||
selectedMilestone,
|
||||
selectedMilestoneDefault;
|
||||
const $dropdown = $(dropdown);
|
||||
const projectId = $dropdown.data('projectId');
|
||||
const milestonesUrl = $dropdown.data('milestones');
|
||||
|
|
@ -46,45 +50,47 @@ export default class MilestoneSelect {
|
|||
const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
|
||||
const $value = $block.find('.value');
|
||||
const $loading = $block.find('.block-loading').fadeOut();
|
||||
selectedMilestoneDefault = (showAny ? '' : null);
|
||||
selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault);
|
||||
selectedMilestoneDefault = showAny ? '' : null;
|
||||
selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault;
|
||||
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
|
||||
|
||||
if (issueUpdateURL) {
|
||||
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
|
||||
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>';
|
||||
}
|
||||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: (term, callback) => axios.get(milestonesUrl)
|
||||
.then(({ data }) => {
|
||||
data: (term, callback) =>
|
||||
axios.get(milestonesUrl).then(({ data }) => {
|
||||
const extraOptions = [];
|
||||
if (showAny) {
|
||||
extraOptions.push({
|
||||
id: 0,
|
||||
name: '',
|
||||
title: 'Any Milestone'
|
||||
id: null,
|
||||
name: null,
|
||||
title: 'Any Milestone',
|
||||
});
|
||||
}
|
||||
if (showNo) {
|
||||
extraOptions.push({
|
||||
id: -1,
|
||||
name: 'No Milestone',
|
||||
title: 'No Milestone'
|
||||
title: 'No Milestone',
|
||||
});
|
||||
}
|
||||
if (showUpcoming) {
|
||||
extraOptions.push({
|
||||
id: -2,
|
||||
name: '#upcoming',
|
||||
title: 'Upcoming'
|
||||
title: 'Upcoming',
|
||||
});
|
||||
}
|
||||
if (showStarted) {
|
||||
extraOptions.push({
|
||||
id: -3,
|
||||
name: '#started',
|
||||
title: 'Started'
|
||||
title: 'Started',
|
||||
});
|
||||
}
|
||||
if (extraOptions.length) {
|
||||
|
|
@ -106,7 +112,7 @@ export default class MilestoneSelect {
|
|||
`,
|
||||
filterable: true,
|
||||
search: {
|
||||
fields: ['title']
|
||||
fields: ['title'],
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel: (selected, el, e) => {
|
||||
|
|
@ -119,7 +125,7 @@ export default class MilestoneSelect {
|
|||
defaultLabel: defaultLabel,
|
||||
fieldName: $dropdown.data('fieldName'),
|
||||
text: milestone => _.escape(milestone.title),
|
||||
id: (milestone) => {
|
||||
id: milestone => {
|
||||
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
|
||||
return milestone.name;
|
||||
} else {
|
||||
|
|
@ -131,7 +137,7 @@ export default class MilestoneSelect {
|
|||
// display:block overrides the hide-collapse rule
|
||||
return $value.css('display', '');
|
||||
},
|
||||
opened: (e) => {
|
||||
opened: e => {
|
||||
const $el = $(e.currentTarget);
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
|
||||
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
|
||||
|
|
@ -140,7 +146,7 @@ export default class MilestoneSelect {
|
|||
$(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
|
||||
},
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: (clickEvent) => {
|
||||
clicked: clickEvent => {
|
||||
const { $el, e } = clickEvent;
|
||||
let selected = clickEvent.selectedObj;
|
||||
|
||||
|
|
@ -155,11 +161,14 @@ export default class MilestoneSelect {
|
|||
|
||||
const page = $('body').attr('data-page');
|
||||
const isIssueIndex = page === 'projects:issues:index';
|
||||
const isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
const isSelecting = (selected.name !== selectedMilestone);
|
||||
const isMRIndex = page === page && page === 'projects:merge_requests:index';
|
||||
const isSelecting = selected.name !== selectedMilestone;
|
||||
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
|
||||
|
||||
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
if (
|
||||
$dropdown.hasClass('js-filter-bulk-update') ||
|
||||
$dropdown.hasClass('js-issuable-form-dropdown')
|
||||
) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
|
@ -177,10 +186,13 @@ export default class MilestoneSelect {
|
|||
return $dropdown.closest('form').submit();
|
||||
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
if (selected.id !== -1 && isSelecting) {
|
||||
gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({
|
||||
id: selected.id,
|
||||
title: selected.name
|
||||
}));
|
||||
gl.issueBoards.boardStoreIssueSet(
|
||||
'milestone',
|
||||
new ListMilestone({
|
||||
id: selected.id,
|
||||
title: selected.name,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
gl.issueBoards.boardStoreIssueDelete('milestone');
|
||||
}
|
||||
|
|
@ -188,7 +200,8 @@ export default class MilestoneSelect {
|
|||
$dropdown.trigger('loading.gl.dropdown');
|
||||
$loading.removeClass('hidden').fadeIn();
|
||||
|
||||
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
|
||||
gl.issueBoards.BoardsStore.detail.issue
|
||||
.update($dropdown.attr('data-issue-update'))
|
||||
.then(() => {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
|
|
@ -203,7 +216,8 @@ export default class MilestoneSelect {
|
|||
data[abilityName].milestone_id = selected != null ? selected : null;
|
||||
$loading.removeClass('hidden').fadeIn();
|
||||
$dropdown.trigger('loading.gl.dropdown');
|
||||
return axios.put(issueUpdateURL, data)
|
||||
return axios
|
||||
.put(issueUpdateURL, data)
|
||||
.then(({ data }) => {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
|
|
@ -215,7 +229,10 @@ export default class MilestoneSelect {
|
|||
data.milestone.name = data.milestone.title;
|
||||
$value.html(milestoneLinkTemplate(data.milestone));
|
||||
return $sidebarCollapsedValue
|
||||
.attr('data-original-title', `${data.milestone.name}<br />${data.milestone.remaining}`)
|
||||
.attr(
|
||||
'data-original-title',
|
||||
`${data.milestone.name}<br />${data.milestone.remaining}`,
|
||||
)
|
||||
.find('span')
|
||||
.text(data.milestone.title);
|
||||
} else {
|
||||
|
|
@ -230,7 +247,7 @@ export default class MilestoneSelect {
|
|||
$loading.fadeOut();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export default {
|
|||
v-html="resolveSvg"
|
||||
></span>
|
||||
</span>
|
||||
<span class=".line-resolve-text">
|
||||
<span class="line-resolve-text">
|
||||
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,16 +52,15 @@
|
|||
text() {
|
||||
const keepContributionsText = s__(`AdminArea|
|
||||
You are about to permanently delete the user %{username}.
|
||||
This will delete all of the issues, merge requests, and groups linked to them.
|
||||
Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
|
||||
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
|
||||
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
|
||||
|
||||
const deleteContributionsText = s__(`AdminArea|
|
||||
You are about to permanently delete the user %{username}.
|
||||
Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
|
||||
This will delete all of the issues, merge requests, and groups linked to them.
|
||||
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
|
||||
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
|
||||
|
||||
return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText,
|
||||
{
|
||||
username: `<strong>${_.escape(this.username)}</strong>`,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import initCompareAutocomplete from '~/compare_autocomplete';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initCompareAutocomplete);
|
||||
document.addEventListener('DOMContentLoaded', () => initCompareAutocomplete());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import $ from 'jquery';
|
||||
import { localTimeAgo } from '~/lib/utils/datetime_utility';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import initCompareAutocomplete from '~/compare_autocomplete';
|
||||
import initTargetProjectDropdown from './target_project_dropdown';
|
||||
|
||||
const updateCommitList = (url, $loadingIndicator, $commitList, params) => {
|
||||
$loadingIndicator.show();
|
||||
$commitList.empty();
|
||||
|
||||
return axios
|
||||
.get(url, {
|
||||
params,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
$loadingIndicator.hide();
|
||||
$commitList.html(data);
|
||||
localTimeAgo($('.js-timeago', $commitList));
|
||||
});
|
||||
};
|
||||
|
||||
export default mrNewCompareNode => {
|
||||
const { sourceBranchUrl, targetBranchUrl } = mrNewCompareNode.dataset;
|
||||
initTargetProjectDropdown();
|
||||
|
||||
const updateSourceBranchCommitList = () =>
|
||||
updateCommitList(
|
||||
sourceBranchUrl,
|
||||
$(mrNewCompareNode).find('.js-source-loading'),
|
||||
$(mrNewCompareNode).find('.mr_source_commit'),
|
||||
{
|
||||
ref: $(mrNewCompareNode)
|
||||
.find("input[name='merge_request[source_branch]']")
|
||||
.val(),
|
||||
},
|
||||
);
|
||||
const updateTargetBranchCommitList = () =>
|
||||
updateCommitList(
|
||||
targetBranchUrl,
|
||||
$(mrNewCompareNode).find('.js-target-loading'),
|
||||
$(mrNewCompareNode).find('.mr_target_commit'),
|
||||
{
|
||||
target_project_id: $(mrNewCompareNode)
|
||||
.find("input[name='merge_request[target_project_id]']")
|
||||
.val(),
|
||||
ref: $(mrNewCompareNode)
|
||||
.find("input[name='merge_request[target_branch]']")
|
||||
.val(),
|
||||
},
|
||||
);
|
||||
initCompareAutocomplete('branches', $dropdown => {
|
||||
if ($dropdown.is('.js-target-branch')) {
|
||||
updateTargetBranchCommitList();
|
||||
} else if ($dropdown.is('.js-source-branch')) {
|
||||
updateSourceBranchCommitList();
|
||||
}
|
||||
});
|
||||
updateSourceBranchCommitList();
|
||||
updateTargetBranchCommitList();
|
||||
};
|
||||
|
|
@ -1,18 +1,15 @@
|
|||
import Compare from '~/compare';
|
||||
import MergeRequest from '~/merge_request';
|
||||
import initPipelines from '~/commit/pipelines/pipelines_bundle';
|
||||
import initCompare from './compare';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
|
||||
if (mrNewCompareNode) {
|
||||
new Compare({ // eslint-disable-line no-new
|
||||
targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
|
||||
sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
|
||||
targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
|
||||
});
|
||||
initCompare(mrNewCompareNode);
|
||||
} else {
|
||||
const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
|
||||
new MergeRequest({ // eslint-disable-line no-new
|
||||
// eslint-disable-next-line no-new
|
||||
new MergeRequest({
|
||||
action: mrNewSubmitNode.dataset.mrSubmitAction,
|
||||
});
|
||||
initPipelines();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export default () => {
|
||||
const $targetProjectDropdown = $('.js-target-project');
|
||||
$targetProjectDropdown.glDropdown({
|
||||
selectable: true,
|
||||
fieldName: $targetProjectDropdown.data('fieldName'),
|
||||
filterable: true,
|
||||
id(obj, $el) {
|
||||
return $el.data('id');
|
||||
},
|
||||
toggleLabel(obj, $el) {
|
||||
return $el.text().trim();
|
||||
},
|
||||
clicked({ $el }) {
|
||||
$('.mr_target_commit').empty();
|
||||
const $targetBranchDropdown = $('.js-target-branch');
|
||||
$targetBranchDropdown.data('refsUrl', $el.data('refsUrl'));
|
||||
$targetBranchDropdown.data('glDropdown').clearMenu();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -188,11 +188,11 @@ export default class ActivityCalendar {
|
|||
},
|
||||
{
|
||||
text: 'W',
|
||||
y: 29 + this.dayYPos(2),
|
||||
y: 29 + this.dayYPos(3),
|
||||
},
|
||||
{
|
||||
text: 'F',
|
||||
y: 29 + this.dayYPos(3),
|
||||
y: 29 + this.dayYPos(5),
|
||||
},
|
||||
];
|
||||
this.svg
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service';
|
|||
import detailedMetric from './detailed_metric.vue';
|
||||
import requestSelector from './request_selector.vue';
|
||||
import simpleMetric from './simple_metric.vue';
|
||||
import upstreamPerformanceBar from './upstream_performance_bar.vue';
|
||||
|
||||
import Flash from '../../flash';
|
||||
|
||||
|
|
@ -14,7 +13,6 @@ export default {
|
|||
detailedMetric,
|
||||
requestSelector,
|
||||
simpleMetric,
|
||||
upstreamPerformanceBar,
|
||||
},
|
||||
props: {
|
||||
store: {
|
||||
|
|
@ -128,9 +126,6 @@ export default {
|
|||
{{ currentRequest.details.host.hostname }}
|
||||
</span>
|
||||
</div>
|
||||
<upstream-performance-bar
|
||||
v-if="initialRequest && currentRequest.details"
|
||||
/>
|
||||
<detailed-metric
|
||||
v-for="metric in $options.detailedMetrics"
|
||||
:key="metric.metric"
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
const upstreamPerformanceBar = document
|
||||
.getElementById('peek-view-performance-bar')
|
||||
.cloneNode(true);
|
||||
|
||||
upstreamPerformanceBar.classList.remove('hidden');
|
||||
|
||||
this.$refs.wrapper.appendChild(upstreamPerformanceBar);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
id="peek-view-performance-bar-vue"
|
||||
class="view"
|
||||
ref="wrapper"
|
||||
></div>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import 'vendor/peek.performance_bar';
|
||||
|
||||
import Vue from 'vue';
|
||||
import performanceBarApp from './components/performance_bar_app.vue';
|
||||
import PerformanceBarStore from './stores/performance_bar_store';
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
export default {
|
||||
name: 'time-tracking-no-tracking-pane',
|
||||
template: `
|
||||
<div class="time-tracking-no-tracking-pane">
|
||||
<span class="no-value">
|
||||
{{ __('No estimate or time spent') }}
|
||||
</span>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'TimeTrackingNoTrackingPane',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="time-tracking-no-tracking-pane">
|
||||
<span class="no-value">
|
||||
{{ __('No estimate or time spent') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
|
||||
|
|
@ -10,14 +11,17 @@ import Mediator from '../../sidebar_mediator';
|
|||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IssuableTimeTracker,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mediator: new Mediator(),
|
||||
store: new Store(),
|
||||
};
|
||||
},
|
||||
components: {
|
||||
IssuableTimeTracker,
|
||||
mounted() {
|
||||
this.listenForQuickActions();
|
||||
},
|
||||
methods: {
|
||||
listenForQuickActions() {
|
||||
|
|
@ -41,18 +45,17 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.listenForQuickActions();
|
||||
},
|
||||
template: `
|
||||
<div class="block">
|
||||
<issuable-time-tracker
|
||||
:time_estimate="store.timeEstimate"
|
||||
:time_spent="store.totalTimeSpent"
|
||||
:human_time_estimate="store.humanTimeEstimate"
|
||||
:human_time_spent="store.humanTotalTimeSpent"
|
||||
:rootPath="store.rootPath"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block">
|
||||
<issuable-time-tracker
|
||||
:time_estimate="store.timeEstimate"
|
||||
:time_spent="store.totalTimeSpent"
|
||||
:human_time_estimate="store.humanTimeEstimate"
|
||||
:human_time_spent="store.humanTotalTimeSpent"
|
||||
:root-path="store.rootPath"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import TimeTrackingHelpState from './help_state.vue';
|
||||
import TimeTrackingCollapsedState from './collapsed_state.vue';
|
||||
import timeTrackingSpentOnlyPane from './spent_only_pane';
|
||||
import timeTrackingNoTrackingPane from './no_tracking_pane';
|
||||
import TimeTrackingNoTrackingPane from './no_tracking_pane.vue';
|
||||
import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
|
||||
import TimeTrackingComparisonPane from './comparison_pane.vue';
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ export default {
|
|||
TimeTrackingCollapsedState,
|
||||
TimeTrackingEstimateOnlyPane,
|
||||
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
|
||||
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
|
||||
TimeTrackingNoTrackingPane,
|
||||
TimeTrackingComparisonPane,
|
||||
TimeTrackingHelpState,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
|
||||
function isValidProjectId(id) {
|
||||
return id > 0;
|
||||
|
|
@ -43,7 +44,7 @@ class SidebarMoveIssue {
|
|||
renderRow: project => `
|
||||
<li>
|
||||
<a href="#" class="js-move-issue-dropdown-item">
|
||||
${project.name_with_namespace}
|
||||
${_.escape(project.name_with_namespace)}
|
||||
</a>
|
||||
</li>
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
|
||||
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
|
||||
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
|
||||
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
|
||||
import SidebarMoveIssue from './lib/sidebar_move_issue';
|
||||
|
|
|
|||
|
|
@ -1,66 +1,70 @@
|
|||
<script>
|
||||
import { n__ } from '~/locale';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
import { n__ } from '~/locale';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetFailedToMerge',
|
||||
export default {
|
||||
name: 'MRWidgetFailedToMerge',
|
||||
|
||||
components: {
|
||||
statusIcon,
|
||||
components: {
|
||||
statusIcon,
|
||||
},
|
||||
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: 10,
|
||||
isRefreshing: false,
|
||||
intervalId: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
timerText() {
|
||||
return n__(
|
||||
'Refreshing in a second to show the updated status...',
|
||||
'Refreshing in %d seconds to show the updated status...',
|
||||
this.timer,
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
timer: 10,
|
||||
isRefreshing: false,
|
||||
};
|
||||
mounted() {
|
||||
this.intervalId = setInterval(this.updateTimer, 1000);
|
||||
},
|
||||
|
||||
created() {
|
||||
eventHub.$emit('DisablePolling');
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
eventHub.$emit('EnablePolling');
|
||||
},
|
||||
updateTimer() {
|
||||
this.timer = this.timer - 1;
|
||||
|
||||
computed: {
|
||||
timerText() {
|
||||
return n__(
|
||||
'Refreshing in a second to show the updated status...',
|
||||
'Refreshing in %d seconds to show the updated status...',
|
||||
this.timer,
|
||||
);
|
||||
},
|
||||
if (this.timer === 0) {
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
setInterval(() => {
|
||||
this.updateTimer();
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
created() {
|
||||
eventHub.$emit('DisablePolling');
|
||||
},
|
||||
|
||||
methods: {
|
||||
refresh() {
|
||||
this.isRefreshing = true;
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
eventHub.$emit('EnablePolling');
|
||||
},
|
||||
updateTimer() {
|
||||
this.timer = this.timer - 1;
|
||||
|
||||
if (this.timer === 0) {
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetWIP',
|
||||
props: {
|
||||
mr: { type: Object, required: true },
|
||||
service: { type: Object, required: true },
|
||||
name: 'WorkInProgress',
|
||||
components: {
|
||||
statusIcon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
mr: { type: Object, required: true },
|
||||
service: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMakingRequest: false,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
statusIcon,
|
||||
},
|
||||
methods: {
|
||||
removeWIP() {
|
||||
this.isMakingRequest = true;
|
||||
|
|
@ -36,32 +37,40 @@ export default {
|
|||
});
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="warning" :show-disabled-button="Boolean(mr.removeWIPPath)" />
|
||||
<div class="media-body space-children">
|
||||
<span class="bold">
|
||||
This is a Work in Progress
|
||||
<i
|
||||
v-tooltip
|
||||
class="fa fa-question-circle"
|
||||
title="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged"
|
||||
aria-label="When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged">
|
||||
</i>
|
||||
</span>
|
||||
<button
|
||||
v-if="mr.removeWIPPath"
|
||||
@click="removeWIP"
|
||||
:disabled="isMakingRequest"
|
||||
type="button"
|
||||
class="btn btn-default btn-xs js-remove-wip">
|
||||
<i
|
||||
v-if="isMakingRequest"
|
||||
class="fa fa-spinner fa-spin"
|
||||
aria-hidden="true" />
|
||||
Resolve WIP status
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon
|
||||
status="warning"
|
||||
:show-disabled-button="Boolean(mr.removeWIPPath)"
|
||||
/>
|
||||
<div class="media-body space-children">
|
||||
<span class="bold">
|
||||
This is a Work in Progress
|
||||
<i
|
||||
v-tooltip
|
||||
class="fa fa-question-circle"
|
||||
title="When this merge request is ready,
|
||||
remove the WIP: prefix from the title to allow it to be merged"
|
||||
aria-label="When this merge request is ready,
|
||||
remove the WIP: prefix from the title to allow it to be merged">
|
||||
</i>
|
||||
</span>
|
||||
<button
|
||||
v-if="mr.removeWIPPath"
|
||||
@click="removeWIP"
|
||||
:disabled="isMakingRequest"
|
||||
type="button"
|
||||
class="btn btn-default btn-xs js-remove-wip">
|
||||
<i
|
||||
v-if="isMakingRequest"
|
||||
class="fa fa-spinner fa-spin"
|
||||
aria-hidden="true">
|
||||
</i>
|
||||
Resolve WIP status
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -21,7 +21,7 @@ export { default as MergedState } from './components/states/mr_widget_merged.vue
|
|||
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
|
||||
export { default as ClosedState } from './components/states/mr_widget_closed.vue';
|
||||
export { default as MergingState } from './components/states/mr_widget_merging.vue';
|
||||
export { default as WipState } from './components/states/mr_widget_wip';
|
||||
export { default as WorkInProgressState } from './components/states/work_in_progress.vue';
|
||||
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
|
||||
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
|
||||
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
ClosedState,
|
||||
MergingState,
|
||||
RebaseState,
|
||||
WipState,
|
||||
WorkInProgressState,
|
||||
ArchivedState,
|
||||
ConflictsState,
|
||||
NothingToMergeState,
|
||||
|
|
@ -220,7 +220,7 @@ export default {
|
|||
'mr-widget-closed': ClosedState,
|
||||
'mr-widget-merging': MergingState,
|
||||
'mr-widget-failed-to-merge': FailedToMerge,
|
||||
'mr-widget-wip': WipState,
|
||||
'mr-widget-wip': WorkInProgressState,
|
||||
'mr-widget-archived': ArchivedState,
|
||||
'mr-widget-conflicts': ConflictsState,
|
||||
'mr-widget-nothing-to-merge': NothingToMergeState,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import getIconForFile from './file_icon/file_icon_map';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
import getIconForFile from './file_icon/file_icon_map';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
|
||||
/* This is a re-usable vue component for rendering a svg sprite
|
||||
/* This is a re-usable vue component for rendering a svg sprite
|
||||
icon
|
||||
|
||||
Sample configuration:
|
||||
|
|
@ -15,60 +15,60 @@
|
|||
/>
|
||||
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
icon,
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
fileName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
fileName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
folder: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
opened: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
size: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 16,
|
||||
},
|
||||
|
||||
cssClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
folder: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
computed: {
|
||||
spriteHref() {
|
||||
const iconName = getIconForFile(this.fileName) || 'file';
|
||||
return `${gon.sprite_file_icons}#${iconName}`;
|
||||
},
|
||||
folderIconName() {
|
||||
return this.opened ? 'folder-open' : 'folder';
|
||||
},
|
||||
iconSizeClass() {
|
||||
return this.size ? `s${this.size}` : '';
|
||||
},
|
||||
|
||||
opened: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
size: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 16,
|
||||
},
|
||||
|
||||
cssClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
spriteHref() {
|
||||
const iconName = getIconForFile(this.fileName) || 'file';
|
||||
return `${gon.sprite_file_icons}#${iconName}`;
|
||||
},
|
||||
folderIconName() {
|
||||
return this.opened ? 'folder-open' : 'folder';
|
||||
},
|
||||
iconSizeClass() {
|
||||
return this.size ? `s${this.size}` : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span>
|
||||
|
|
@ -82,6 +82,7 @@
|
|||
v-if="!loading && folder"
|
||||
:name="folderIconName"
|
||||
:size="size"
|
||||
css-classes="folder-icon"
|
||||
/>
|
||||
<loading-icon
|
||||
v-if="loading"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
/**
|
||||
* This method is based on app/helpers/application_helper.rb#project_identicon
|
||||
* This method is based on app/helpers/avatars_helper.rb#project_identicon
|
||||
*/
|
||||
identiconStyles() {
|
||||
const allowedColors = [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
class ListLabel {
|
||||
export default class ListLabel {
|
||||
constructor(obj) {
|
||||
this.id = obj.id;
|
||||
this.title = obj.title;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,64 +1,63 @@
|
|||
@import "framework/variables";
|
||||
@import "framework/mixins";
|
||||
@import 'framework/variables';
|
||||
@import 'framework/mixins';
|
||||
@import 'framework/tw_bootstrap_variables';
|
||||
@import 'framework/tw_bootstrap';
|
||||
@import "framework/layout";
|
||||
@import 'framework/layout';
|
||||
|
||||
@import "framework/animations";
|
||||
@import "framework/vue_transitions";
|
||||
@import "framework/avatar";
|
||||
@import "framework/asciidoctor";
|
||||
@import "framework/banner";
|
||||
@import "framework/blocks";
|
||||
@import "framework/buttons";
|
||||
@import "framework/badges";
|
||||
@import "framework/calendar";
|
||||
@import "framework/callout";
|
||||
@import "framework/common";
|
||||
@import "framework/dropdowns";
|
||||
@import "framework/files";
|
||||
@import "framework/filters";
|
||||
@import "framework/flash";
|
||||
@import "framework/forms";
|
||||
@import "framework/gfm";
|
||||
@import "framework/gitlab_theme";
|
||||
@import "framework/header";
|
||||
@import "framework/highlight";
|
||||
@import "framework/issue_box";
|
||||
@import "framework/jquery";
|
||||
@import "framework/lists";
|
||||
@import "framework/logo";
|
||||
@import "framework/markdown_area";
|
||||
@import "framework/media_object";
|
||||
@import "framework/mobile";
|
||||
@import "framework/modal";
|
||||
@import "framework/pagination";
|
||||
@import "framework/panels";
|
||||
@import "framework/popup";
|
||||
@import "framework/secondary_navigation_elements";
|
||||
@import "framework/selects";
|
||||
@import "framework/sidebar";
|
||||
@import "framework/contextual_sidebar";
|
||||
@import "framework/tables";
|
||||
@import "framework/notes";
|
||||
@import "framework/tabs";
|
||||
@import "framework/timeline";
|
||||
@import "framework/tooltips";
|
||||
@import "framework/toggle";
|
||||
@import "framework/typography";
|
||||
@import "framework/zen";
|
||||
@import "framework/blank";
|
||||
@import "framework/wells";
|
||||
@import "framework/page_header";
|
||||
@import "framework/awards";
|
||||
@import "framework/images";
|
||||
@import "framework/broadcast_messages";
|
||||
@import "framework/emojis";
|
||||
@import "framework/emoji_sprites";
|
||||
@import "framework/icons";
|
||||
@import "framework/snippets";
|
||||
@import "framework/memory_graph";
|
||||
@import "framework/responsive_tables";
|
||||
@import "framework/stacked_progress_bar";
|
||||
@import "framework/ci_variable_list";
|
||||
@import "framework/feature_highlight";
|
||||
@import 'framework/animations';
|
||||
@import 'framework/vue_transitions';
|
||||
@import 'framework/avatar';
|
||||
@import 'framework/asciidoctor';
|
||||
@import 'framework/banner';
|
||||
@import 'framework/blocks';
|
||||
@import 'framework/buttons';
|
||||
@import 'framework/badges';
|
||||
@import 'framework/calendar';
|
||||
@import 'framework/callout';
|
||||
@import 'framework/common';
|
||||
@import 'framework/dropdowns';
|
||||
@import 'framework/files';
|
||||
@import 'framework/filters';
|
||||
@import 'framework/flash';
|
||||
@import 'framework/forms';
|
||||
@import 'framework/gfm';
|
||||
@import 'framework/gitlab_theme';
|
||||
@import 'framework/header';
|
||||
@import 'framework/highlight';
|
||||
@import 'framework/issue_box';
|
||||
@import 'framework/jquery';
|
||||
@import 'framework/lists';
|
||||
@import 'framework/logo';
|
||||
@import 'framework/markdown_area';
|
||||
@import 'framework/media_object';
|
||||
@import 'framework/mobile';
|
||||
@import 'framework/modal';
|
||||
@import 'framework/pagination';
|
||||
@import 'framework/panels';
|
||||
@import 'framework/popup';
|
||||
@import 'framework/secondary_navigation_elements';
|
||||
@import 'framework/selects';
|
||||
@import 'framework/sidebar';
|
||||
@import 'framework/contextual_sidebar';
|
||||
@import 'framework/tables';
|
||||
@import 'framework/notes';
|
||||
@import 'framework/tabs';
|
||||
@import 'framework/timeline';
|
||||
@import 'framework/tooltips';
|
||||
@import 'framework/toggle';
|
||||
@import 'framework/typography';
|
||||
@import 'framework/zen';
|
||||
@import 'framework/blank';
|
||||
@import 'framework/wells';
|
||||
@import 'framework/page_header';
|
||||
@import 'framework/awards';
|
||||
@import 'framework/images';
|
||||
@import 'framework/broadcast_messages';
|
||||
@import 'framework/emojis';
|
||||
@import 'framework/icons';
|
||||
@import 'framework/snippets';
|
||||
@import 'framework/memory_graph';
|
||||
@import 'framework/responsive_tables';
|
||||
@import 'framework/stacked_progress_bar';
|
||||
@import 'framework/ci_variable_list';
|
||||
@import 'framework/feature_highlight';
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
}
|
||||
|
||||
&.middle-block {
|
||||
margin-top: 0;
|
||||
margin-top: $gl-padding-24;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
}
|
||||
|
||||
&.footer-block {
|
||||
margin-top: 0;
|
||||
margin-top: $gl-padding-24;
|
||||
border-bottom: 0;
|
||||
margin-bottom: -$gl-padding;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,6 +452,7 @@ img.emoji {
|
|||
|
||||
/** COMMON CLASSES **/
|
||||
.prepend-top-0 { margin-top: 0; }
|
||||
.prepend-top-2 { margin-top: 2px; }
|
||||
.prepend-top-5 { margin-top: 5px; }
|
||||
.prepend-top-8 { margin-top: $grid-size; }
|
||||
.prepend-top-10 { margin-top: 10px; }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -39,35 +39,10 @@
|
|||
svg {
|
||||
fill: currentColor;
|
||||
|
||||
&.s8 {
|
||||
@include svg-size(8px);
|
||||
}
|
||||
|
||||
&.s12 {
|
||||
@include svg-size(12px);
|
||||
}
|
||||
|
||||
&.s16 {
|
||||
@include svg-size(16px);
|
||||
}
|
||||
|
||||
&.s18 {
|
||||
@include svg-size(18px);
|
||||
}
|
||||
|
||||
&.s24 {
|
||||
@include svg-size(24px);
|
||||
}
|
||||
|
||||
&.s32 {
|
||||
@include svg-size(32px);
|
||||
}
|
||||
|
||||
&.s48 {
|
||||
@include svg-size(48px);
|
||||
}
|
||||
|
||||
&.s72 {
|
||||
@include svg-size(72px);
|
||||
$svg-sizes: 8 12 16 18 24 32 48 72;
|
||||
@each $svg-size in $svg-sizes {
|
||||
&.s#{$svg-size} {
|
||||
@include svg-size(#{$svg-size}px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,16 @@
|
|||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.referenced-commands {
|
||||
background: $blue-50;
|
||||
padding: $gl-padding-8 $gl-padding;
|
||||
border-radius: $border-radius-default;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.md-preview-holder {
|
||||
min-height: 167px;
|
||||
padding: 10px 0;
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ $tooltip-font-size: 12px;
|
|||
/*
|
||||
* Padding
|
||||
*/
|
||||
$gl-padding-24: 24px;
|
||||
$gl-padding: 16px;
|
||||
$gl-padding-8: 8px;
|
||||
$gl-padding-4: 4px;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
.fork-svg {
|
||||
margin-right: 4px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
}
|
||||
|
||||
.branch-info .commit-icon {
|
||||
margin-right: 3px;
|
||||
margin-right: 8px;
|
||||
|
||||
svg {
|
||||
top: 3px;
|
||||
|
|
|
|||
|
|
@ -772,7 +772,3 @@ ul.notes {
|
|||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.line-resolve-text {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: inherit;
|
||||
line-height: 22px;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
|
|
@ -67,6 +68,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ide-file-icon-holder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ide-file-changed-icon {
|
||||
margin-left: auto;
|
||||
|
||||
|
|
@ -77,7 +83,6 @@
|
|||
|
||||
.ide-new-btn {
|
||||
display: none;
|
||||
margin-bottom: -4px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
|
|
@ -90,10 +95,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.folder {
|
||||
svg {
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
.folder-icon {
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +114,7 @@
|
|||
.file-col-commit-message {
|
||||
display: flex;
|
||||
overflow: visible;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
|
|
@ -438,7 +442,7 @@
|
|||
.projects-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
.context-header {
|
||||
width: auto;
|
||||
|
|
@ -967,3 +971,7 @@
|
|||
background: transparent;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.ide-new-modal-label {
|
||||
line-height: 34px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,786 +0,0 @@
|
|||
.project-refs-form,
|
||||
.project-refs-target-form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.commit-message {
|
||||
@include str-truncated(250px);
|
||||
}
|
||||
|
||||
.editable-mode {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ide-view {
|
||||
display: flex;
|
||||
height: calc(100vh - #{$header-height});
|
||||
margin-top: 40px;
|
||||
color: $almost-black;
|
||||
border-top: 1px solid $white-dark;
|
||||
border-bottom: 1px solid $white-dark;
|
||||
|
||||
&.is-collapsed {
|
||||
.ide-file-list {
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-file-list {
|
||||
flex: 1;
|
||||
|
||||
.file {
|
||||
cursor: pointer;
|
||||
|
||||
&.file-open {
|
||||
background: $white-normal;
|
||||
}
|
||||
|
||||
.ide-file-name {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
margin-right: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-file-changed-icon {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.ide-new-btn {
|
||||
display: none;
|
||||
margin-bottom: -4px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ide-new-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.folder {
|
||||
svg {
|
||||
fill: $gl-text-color-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.file-name,
|
||||
.file-col-commit-message {
|
||||
display: flex;
|
||||
overflow: visible;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.multi-file-loading-container {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
|
||||
.animation-container {
|
||||
background: $gray-light;
|
||||
|
||||
div {
|
||||
background: $gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-table-col-commit-message {
|
||||
white-space: nowrap;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.multi-file-edit-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
border-left: 1px solid $white-dark;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.multi-file-tabs {
|
||||
display: flex;
|
||||
background-color: $white-normal;
|
||||
box-shadow: inset 0 -1px $white-dark;
|
||||
|
||||
> ul {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
margin-bottom: 1px;
|
||||
padding: 0 $grid-size;
|
||||
border-left: 1px solid $white-dark;
|
||||
background-color: $white-light;
|
||||
|
||||
&.shadow {
|
||||
box-shadow: 0 0 10px $dropdown-shadow-color;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-tab {
|
||||
@include str-truncated(150px);
|
||||
padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding;
|
||||
background-color: $gray-normal;
|
||||
border-right: 1px solid $white-dark;
|
||||
border-bottom: 1px solid $white-dark;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $white-light;
|
||||
border-bottom-color: $white-light;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-tab-close {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: $border-radius-default;
|
||||
color: $theme-gray-900;
|
||||
transform: translateY(-50%);
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $theme-gray-200;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: $blue-500;
|
||||
color: $white-light;
|
||||
outline: 0;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-edit-pane-content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.blob-editor-container {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.vertical-center {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.monaco-editor .lines-content .cigr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-diff-editor.vs {
|
||||
.editor.modified {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.diagonal-fill {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.diffOverview {
|
||||
background-color: $white-light;
|
||||
border-left: 1px solid $white-dark;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.diffViewport {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.char-insert {
|
||||
background-color: $line-added-dark;
|
||||
}
|
||||
|
||||
.char-delete {
|
||||
background-color: $line-removed-dark;
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
color: $black-transparent;
|
||||
}
|
||||
|
||||
.view-overlays {
|
||||
.line-insert {
|
||||
background-color: $line-added;
|
||||
}
|
||||
|
||||
.line-delete {
|
||||
background-color: $line-removed;
|
||||
}
|
||||
}
|
||||
|
||||
.margin {
|
||||
background-color: $gray-light;
|
||||
border-right: 1px solid $white-normal;
|
||||
|
||||
.line-insert {
|
||||
border-right: 1px solid $line-added-dark;
|
||||
}
|
||||
|
||||
.line-delete {
|
||||
border-right: 1px solid $line-removed-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-view-overlays .insert-sign,
|
||||
.margin-view-overlays .delete-sign {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.cursors-layer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-editor-holder {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.multi-file-editor-btn-group {
|
||||
padding: $gl-bar-padding $gl-padding;
|
||||
border-top: 1px solid $white-dark;
|
||||
border-bottom: 1px solid $white-dark;
|
||||
background: $white-light;
|
||||
}
|
||||
|
||||
.ide-status-bar {
|
||||
padding: $gl-bar-padding $gl-padding;
|
||||
background: $white-light;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
// Not great, but this is to deal with our current output
|
||||
.multi-file-preview-holder {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
.file-content.code {
|
||||
display: flex;
|
||||
|
||||
i {
|
||||
margin-left: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.file-content,
|
||||
.line-numbers,
|
||||
.blob-content,
|
||||
.code {
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.file-content.blob-no-preview {
|
||||
a {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-panel {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
width: 340px;
|
||||
padding: 0;
|
||||
background-color: $gray-light;
|
||||
padding-right: 3px;
|
||||
|
||||
.projects-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.context-header {
|
||||
width: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-inner {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-inner-scroll {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
width: 60px;
|
||||
|
||||
.multi-file-commit-list {
|
||||
padding-top: $gl-padding;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.multi-file-context-bar-icon {
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.branch-container {
|
||||
border-left: 4px solid $indigo-700;
|
||||
margin-bottom: $gl-bar-padding;
|
||||
}
|
||||
|
||||
.branch-header {
|
||||
background: $white-dark;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.branch-header-title {
|
||||
flex: 1;
|
||||
padding: $grid-size $gl-padding;
|
||||
color: $indigo-700;
|
||||
font-weight: $gl-font-weight-bold;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.branch-header-btns {
|
||||
padding: $gl-vert-padding $gl-padding;
|
||||
}
|
||||
|
||||
.left-collapse-btn {
|
||||
display: none;
|
||||
background: $gray-light;
|
||||
text-align: left;
|
||||
border-top: 1px solid $white-dark;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-context-bar-icon {
|
||||
padding: 10px;
|
||||
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.multi-file-commit-empty-state-container {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid $white-dark;
|
||||
padding: $gl-btn-padding 0;
|
||||
|
||||
&.is-collapsed {
|
||||
border-bottom: 1px solid $white-dark;
|
||||
|
||||
svg {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-collapse-btn {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-header-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 0 $gl-btn-padding;
|
||||
|
||||
svg {
|
||||
margin-right: $gl-btn-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-collapse-btn {
|
||||
border-left: 1px solid $white-dark;
|
||||
}
|
||||
|
||||
.multi-file-commit-list {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: $gl-padding 0;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.multi-file-commit-list-item {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
|
||||
.multi-file-discard-btn {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
color: $gl-link-color;
|
||||
padding: 0 2px;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $white-normal;
|
||||
|
||||
.multi-file-discard-btn {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-addition {
|
||||
fill: $green-500;
|
||||
}
|
||||
|
||||
.multi-file-modified {
|
||||
fill: $orange-500;
|
||||
}
|
||||
|
||||
.multi-file-commit-list-collapsed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> svg {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.file-status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-list-path {
|
||||
padding: $grid-size / 2;
|
||||
padding-left: $gl-padding;
|
||||
background: none;
|
||||
border: 0;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
svg {
|
||||
min-width: 16px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-list-file-path {
|
||||
@include str-truncated(100%);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-form {
|
||||
padding: $gl-padding;
|
||||
border-top: 1px solid $white-dark;
|
||||
|
||||
.btn {
|
||||
font-size: $gl-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-commit-message.form-control {
|
||||
height: 160px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.dirty-diff {
|
||||
// !important need to override monaco inline style
|
||||
width: 4px !important;
|
||||
left: 0 !important;
|
||||
|
||||
&-modified {
|
||||
background-color: $blue-500;
|
||||
}
|
||||
|
||||
&-added {
|
||||
background-color: $green-600;
|
||||
}
|
||||
|
||||
&-removed {
|
||||
height: 0 !important;
|
||||
width: 0 !important;
|
||||
bottom: -2px;
|
||||
border-style: solid;
|
||||
border-width: 5px;
|
||||
border-color: transparent transparent transparent $red-500;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100px;
|
||||
height: 1px;
|
||||
background-color: rgba($red-500, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ide-loading {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ide-empty-state {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ide-new-btn {
|
||||
.dropdown-toggle svg {
|
||||
margin-top: -2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
|
||||
label {
|
||||
font-weight: $gl-font-weight-normal;
|
||||
padding: 5px 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ide {
|
||||
overflow: hidden;
|
||||
|
||||
&.nav-only {
|
||||
.flash-container {
|
||||
margin-top: $header-height;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.alert-wrapper .flash-container .flash-alert:last-child,
|
||||
.alert-wrapper .flash-container .flash-notice:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
margin-top: $header-height;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&.flash-shown {
|
||||
.content-wrapper {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ide-view {
|
||||
height: calc(100vh - #{$header-height + $flash-height});
|
||||
}
|
||||
}
|
||||
|
||||
.projects-sidebar {
|
||||
.multi-file-commit-panel-inner-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.with-performance-bar .ide.nav-only {
|
||||
.flash-container {
|
||||
margin-top: #{$header-height + $performance-bar-height};
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
margin-top: #{$header-height + $performance-bar-height};
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ide-view {
|
||||
height: calc(100vh - #{$header-height + $performance-bar-height});
|
||||
}
|
||||
|
||||
&.flash-shown {
|
||||
.content-wrapper {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ide-view {
|
||||
height: calc(
|
||||
100vh - #{$header-height + $performance-bar-height + $flash-height}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background-color: $white-dark;
|
||||
|
||||
&.dragright {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.dragleft {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-commit-radios {
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.help-block {
|
||||
margin-top: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-commit-new-branch {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.ide-external-links {
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-sidebar-link {
|
||||
padding: $gl-padding-8 $gl-padding;
|
||||
background: $indigo-700;
|
||||
color: $white-light;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: $white-light;
|
||||
text-decoration: underline;
|
||||
background: $indigo-500;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $indigo-800;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
@import 'framework/variables';
|
||||
@import 'peek/views/performance_bar';
|
||||
@import 'peek/views/rblineprof';
|
||||
|
||||
#js-peek {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ class ApplicationController < ActionController::Base
|
|||
def log_exception(exception)
|
||||
Raven.capture_exception(exception) if sentry_enabled?
|
||||
|
||||
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
|
||||
backtrace_cleaner = Gitlab.rails5? ? env["action_dispatch.backtrace_cleaner"] : env
|
||||
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
|
||||
application_trace.map! { |t| " #{t}\n" }
|
||||
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ module IssuableCollections
|
|||
out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
if out_of_range
|
||||
redirect_to(url_for(params.merge(page: total_pages, only_path: true)))
|
||||
redirect_to(url_for(safe_params.merge(page: total_pages, only_path: true)))
|
||||
end
|
||||
|
||||
out_of_range
|
||||
|
|
|
|||
|
|
@ -33,6 +33,6 @@ class Groups::ApplicationController < ApplicationController
|
|||
def build_canonical_path(group)
|
||||
params[:group_id] = group.to_param
|
||||
|
||||
url_for(params)
|
||||
url_for(safe_params)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
omniauth_flow(Gitlab::Auth::OAuth)
|
||||
end
|
||||
|
||||
Gitlab.config.omniauth.providers.each do |provider|
|
||||
alias_method provider['name'], :handle_omniauth
|
||||
AuthHelper.providers_for_base_controller.each do |provider|
|
||||
alias_method provider, :handle_omniauth
|
||||
end
|
||||
|
||||
# Extend the standard implementation to also increment
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
class Profiles::ActiveSessionsController < Profiles::ApplicationController
|
||||
def index
|
||||
@sessions = ActiveSession.list(current_user)
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveSession.destroy(current_user, params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to profile_active_sessions_url, status: 302 }
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -25,7 +25,7 @@ class Projects::ApplicationController < ApplicationController
|
|||
params[:namespace_id] = project.namespace.to_param
|
||||
params[:project_id] = project.to_param
|
||||
|
||||
url_for(params)
|
||||
url_for(safe_params)
|
||||
end
|
||||
|
||||
def repository
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
|
|||
|
||||
def link_to_project!(object)
|
||||
if object && !object.projects.exists?(storage_project.id)
|
||||
object.projects << storage_project
|
||||
object.save!
|
||||
object.lfs_objects_projects.create!(project: storage_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -83,13 +83,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
|||
render layout: false
|
||||
end
|
||||
|
||||
def update_branches
|
||||
@target_project = selected_target_project
|
||||
@target_branches = @target_project ? @target_project.repository.branch_names : []
|
||||
|
||||
render layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_merge_request
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
def resolve
|
||||
return render_404 unless note.resolvable?
|
||||
|
||||
note.resolve!(current_user)
|
||||
|
||||
MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(note.noteable)
|
||||
Notes::ResolveService.new(project, current_user).execute(note)
|
||||
|
||||
discussion = note.discussion
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder
|
|||
|
||||
def all_groups
|
||||
return [owned_groups] if params[:owned]
|
||||
return [Group.all] if current_user&.full_private_access?
|
||||
return [Group.all] if current_user&.full_private_access? && all_available?
|
||||
|
||||
groups = []
|
||||
groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user
|
||||
|
|
@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder
|
|||
end
|
||||
|
||||
def include_public_groups?
|
||||
current_user.nil? || params.fetch(:all_available, true)
|
||||
current_user.nil? || all_available?
|
||||
end
|
||||
|
||||
def all_available?
|
||||
params.fetch(:all_available, true)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class PipelinesFinder
|
|||
items = by_scope(items)
|
||||
items = by_status(items)
|
||||
items = by_ref(items)
|
||||
items = by_sha(items)
|
||||
items = by_name(items)
|
||||
items = by_username(items)
|
||||
items = by_yaml_errors(items)
|
||||
|
|
@ -69,6 +70,14 @@ class PipelinesFinder
|
|||
end
|
||||
end
|
||||
|
||||
def by_sha(items)
|
||||
if params[:sha].present?
|
||||
items.where(sha: params[:sha])
|
||||
else
|
||||
items
|
||||
end
|
||||
end
|
||||
|
||||
def by_name(items)
|
||||
if params[:name].present?
|
||||
items.joins(:user).where(users: { name: params[:name] })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
module ActiveSessionsHelper
|
||||
# Maps a device type as defined in `ActiveSession` to an svg icon name and
|
||||
# outputs the icon html.
|
||||
#
|
||||
# see `DeviceDetector::Device::DEVICE_NAMES` about the available device types
|
||||
def active_session_device_type_icon(active_session)
|
||||
icon_name =
|
||||
case active_session.device_type
|
||||
when 'smartphone', 'feature phone', 'phablet'
|
||||
'mobile'
|
||||
when 'tablet'
|
||||
'tablet'
|
||||
when 'tv', 'smart display', 'camera', 'portable media player', 'console'
|
||||
'media'
|
||||
when 'car browser'
|
||||
'car'
|
||||
else
|
||||
'monitor-o'
|
||||
end
|
||||
|
||||
sprite_icon(icon_name, size: 16, css_class: 'prepend-top-2')
|
||||
end
|
||||
end
|
||||
|
|
@ -32,80 +32,6 @@ module ApplicationHelper
|
|||
args.any? { |v| v.to_s.downcase == action_name }
|
||||
end
|
||||
|
||||
def project_icon(project_id, options = {})
|
||||
project =
|
||||
if project_id.respond_to?(:avatar_url)
|
||||
project_id
|
||||
else
|
||||
Project.find_by_full_path(project_id)
|
||||
end
|
||||
|
||||
if project.avatar_url
|
||||
image_tag project.avatar_url, options
|
||||
else # generated icon
|
||||
project_identicon(project, options)
|
||||
end
|
||||
end
|
||||
|
||||
def project_identicon(project, options = {})
|
||||
allowed_colors = {
|
||||
red: 'FFEBEE',
|
||||
purple: 'F3E5F5',
|
||||
indigo: 'E8EAF6',
|
||||
blue: 'E3F2FD',
|
||||
teal: 'E0F2F1',
|
||||
orange: 'FBE9E7',
|
||||
gray: 'EEEEEE'
|
||||
}
|
||||
|
||||
options[:class] ||= ''
|
||||
options[:class] << ' identicon'
|
||||
bg_key = project.id % 7
|
||||
style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
|
||||
|
||||
content_tag(:div, class: options[:class], style: style) do
|
||||
project.name[0, 1].upcase
|
||||
end
|
||||
end
|
||||
|
||||
# Takes both user and email and returns the avatar_icon by
|
||||
# user (preferred) or email.
|
||||
def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)
|
||||
if user
|
||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||
elsif email
|
||||
avatar_icon_for_email(email, size, scale, only_path: only_path)
|
||||
else
|
||||
default_avatar
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true)
|
||||
user = User.find_by_any_email(email.try(:downcase))
|
||||
if user
|
||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||
else
|
||||
gravatar_icon(email, size, scale)
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true)
|
||||
if user
|
||||
user.avatar_url(size: size, only_path: only_path) || default_avatar
|
||||
else
|
||||
gravatar_icon(nil, size, scale)
|
||||
end
|
||||
end
|
||||
|
||||
def gravatar_icon(user_email = '', size = nil, scale = 2)
|
||||
GravatarService.new.execute(user_email, size, scale) ||
|
||||
default_avatar
|
||||
end
|
||||
|
||||
def default_avatar
|
||||
asset_path('no_avatar.png')
|
||||
end
|
||||
|
||||
def last_commit(project)
|
||||
if project.repo_exists?
|
||||
time_ago_with_tooltip(project.repository.commit.committed_date)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
module AuthHelper
|
||||
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
|
||||
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
|
||||
LDAP_PROVIDER = /\Aldap/
|
||||
|
||||
def ldap_enabled?
|
||||
Gitlab::Auth::LDAP::Config.enabled?
|
||||
|
|
@ -23,7 +23,7 @@ module AuthHelper
|
|||
end
|
||||
|
||||
def form_based_provider?(name)
|
||||
FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s }
|
||||
[LDAP_PROVIDER, 'crowd'].any? { |pattern| pattern === name.to_s }
|
||||
end
|
||||
|
||||
def form_based_providers
|
||||
|
|
@ -38,6 +38,10 @@ module AuthHelper
|
|||
auth_providers.reject { |provider| form_based_provider?(provider) }
|
||||
end
|
||||
|
||||
def providers_for_base_controller
|
||||
auth_providers.reject { |provider| LDAP_PROVIDER === provider }
|
||||
end
|
||||
|
||||
def enabled_button_based_providers
|
||||
disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || []
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,78 @@
|
|||
module AvatarsHelper
|
||||
def project_icon(project_id, options = {})
|
||||
project =
|
||||
if project_id.respond_to?(:avatar_url)
|
||||
project_id
|
||||
else
|
||||
Project.find_by_full_path(project_id)
|
||||
end
|
||||
|
||||
if project.avatar_url
|
||||
image_tag project.avatar_url, options
|
||||
else # generated icon
|
||||
project_identicon(project, options)
|
||||
end
|
||||
end
|
||||
|
||||
def project_identicon(project, options = {})
|
||||
allowed_colors = {
|
||||
red: 'FFEBEE',
|
||||
purple: 'F3E5F5',
|
||||
indigo: 'E8EAF6',
|
||||
blue: 'E3F2FD',
|
||||
teal: 'E0F2F1',
|
||||
orange: 'FBE9E7',
|
||||
gray: 'EEEEEE'
|
||||
}
|
||||
|
||||
options[:class] ||= ''
|
||||
options[:class] << ' identicon'
|
||||
bg_key = project.id % 7
|
||||
style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
|
||||
|
||||
content_tag(:div, class: options[:class], style: style) do
|
||||
project.name[0, 1].upcase
|
||||
end
|
||||
end
|
||||
|
||||
# Takes both user and email and returns the avatar_icon by
|
||||
# user (preferred) or email.
|
||||
def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)
|
||||
if user
|
||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||
elsif email
|
||||
avatar_icon_for_email(email, size, scale, only_path: only_path)
|
||||
else
|
||||
default_avatar
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true)
|
||||
user = User.find_by_any_email(email.try(:downcase))
|
||||
if user
|
||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||
else
|
||||
gravatar_icon(email, size, scale)
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true)
|
||||
if user
|
||||
user.avatar_url(size: size, only_path: only_path) || default_avatar
|
||||
else
|
||||
gravatar_icon(nil, size, scale)
|
||||
end
|
||||
end
|
||||
|
||||
def gravatar_icon(user_email = '', size = nil, scale = 2)
|
||||
GravatarService.new.execute(user_email, size, scale) ||
|
||||
default_avatar
|
||||
end
|
||||
|
||||
def default_avatar
|
||||
ActionController::Base.helpers.image_path('no_avatar.png')
|
||||
end
|
||||
|
||||
def author_avatar(commit_or_event, options = {})
|
||||
user_avatar(options.merge({
|
||||
user: commit_or_event.author,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module BlobHelper
|
|||
end
|
||||
|
||||
def ide_edit_path(project = @project, ref = @ref, path = @path, options = {})
|
||||
"#{ide_path}/project#{edit_blob_path(project, ref, path, options)}"
|
||||
"#{ide_path}/project#{url_for([project, "edit", "blob", id: [ref, path], script_name: "/"])}"
|
||||
end
|
||||
|
||||
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ module CommitsHelper
|
|||
# Returns a link formatted as a commit branch link
|
||||
def commit_branch_link(url, text)
|
||||
link_to(url, class: 'label label-gray ref-name branch-link') do
|
||||
sprite_icon('fork', size: 16, css_class: 'fork-svg') + "#{text}"
|
||||
sprite_icon('fork', size: 12, css_class: 'fork-svg') + "#{text}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module DropdownsHelper
|
|||
|
||||
def dropdown_toggle(toggle_text, data_attr, options = {})
|
||||
default_label = data_attr[:default_label]
|
||||
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
|
||||
content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
|
||||
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
|
||||
output << icon('chevron-down')
|
||||
output.html_safe
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ module ProjectsHelper
|
|||
visibilityHelpPath: help_page_path('public_access/public_access'),
|
||||
registryAvailable: Gitlab.config.registry.enabled,
|
||||
registryHelpPath: help_page_path('user/project/container_registry'),
|
||||
lfsAvailable: Gitlab.config.lfs.enabled && current_user.admin?,
|
||||
lfsAvailable: Gitlab.config.lfs.enabled,
|
||||
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
module SystemNoteHelper
|
||||
ICON_NAMES_BY_ACTION = {
|
||||
'commit' => 'commit',
|
||||
'description' => 'pencil',
|
||||
'description' => 'pencil-square',
|
||||
'merge' => 'git-merge',
|
||||
'merged' => 'git-merge',
|
||||
'opened' => 'issue-open',
|
||||
'closed' => 'issue-close',
|
||||
'time_tracking' => 'timer',
|
||||
'assignee' => 'user',
|
||||
'title' => 'pencil',
|
||||
'title' => 'pencil-square',
|
||||
'task' => 'task-done',
|
||||
'label' => 'label',
|
||||
'cross_reference' => 'comment-dots',
|
||||
|
|
@ -18,7 +18,7 @@ module SystemNoteHelper
|
|||
'milestone' => 'clock',
|
||||
'discussion' => 'comment',
|
||||
'moved' => 'arrow-right',
|
||||
'outdated' => 'pencil',
|
||||
'outdated' => 'pencil-square',
|
||||
'duplicate' => 'issue-duplicate',
|
||||
'locked' => 'lock',
|
||||
'unlocked' => 'lock-open'
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class Notify < BaseMailer
|
|||
helper BlobHelper
|
||||
helper EmailsHelper
|
||||
helper MembersHelper
|
||||
helper AvatarsHelper
|
||||
helper GitlabRoutingHelper
|
||||
|
||||
def test_email(recipient_email, subject, body)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
class ActiveSession
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :created_at, :updated_at,
|
||||
:session_id, :ip_address,
|
||||
:browser, :os, :device_name, :device_type
|
||||
|
||||
def current?(session)
|
||||
return false if session_id.nil? || session.id.nil?
|
||||
|
||||
session_id == session.id
|
||||
end
|
||||
|
||||
def human_device_type
|
||||
device_type&.titleize
|
||||
end
|
||||
|
||||
def self.set(user, request)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
session_id = request.session.id
|
||||
client = DeviceDetector.new(request.user_agent)
|
||||
timestamp = Time.current
|
||||
|
||||
active_user_session = new(
|
||||
ip_address: request.ip,
|
||||
browser: client.name,
|
||||
os: client.os_name,
|
||||
device_name: client.device_name,
|
||||
device_type: client.device_type,
|
||||
created_at: user.current_sign_in_at || timestamp,
|
||||
updated_at: timestamp,
|
||||
session_id: session_id
|
||||
)
|
||||
|
||||
redis.pipelined do
|
||||
redis.setex(
|
||||
key_name(user.id, session_id),
|
||||
Settings.gitlab['session_expire_delay'] * 60,
|
||||
Marshal.dump(active_user_session)
|
||||
)
|
||||
|
||||
redis.sadd(
|
||||
lookup_key_name(user.id),
|
||||
session_id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.list(user)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
cleaned_up_lookup_entries(redis, user.id).map do |entry|
|
||||
# rubocop:disable Security/MarshalLoad
|
||||
Marshal.load(entry)
|
||||
# rubocop:enable Security/MarshalLoad
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.destroy(user, session_id)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.srem(lookup_key_name(user.id), session_id)
|
||||
|
||||
deleted_keys = redis.del(key_name(user.id, session_id))
|
||||
|
||||
# only allow deleting the devise session if we could actually find a
|
||||
# related active session. this prevents another user from deleting
|
||||
# someone else's session.
|
||||
if deleted_keys > 0
|
||||
redis.del("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.cleanup(user)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
cleaned_up_lookup_entries(redis, user.id)
|
||||
end
|
||||
end
|
||||
|
||||
def self.key_name(user_id, session_id = '*')
|
||||
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
|
||||
end
|
||||
|
||||
def self.lookup_key_name(user_id)
|
||||
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
|
||||
end
|
||||
|
||||
def self.cleaned_up_lookup_entries(redis, user_id)
|
||||
lookup_key = lookup_key_name(user_id)
|
||||
|
||||
session_ids = redis.smembers(lookup_key)
|
||||
|
||||
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
|
||||
return [] if entry_keys.empty?
|
||||
|
||||
entries = redis.mget(entry_keys)
|
||||
|
||||
session_ids_and_entries = session_ids.zip(entries)
|
||||
|
||||
# remove expired keys.
|
||||
# only the single key entries are automatically expired by redis, the
|
||||
# lookup entries in the set need to be removed manually.
|
||||
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
|
||||
redis.srem(lookup_key, session_id)
|
||||
end
|
||||
|
||||
session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry }
|
||||
end
|
||||
end
|
||||
|
|
@ -13,7 +13,7 @@ module Ci
|
|||
after_save :update_project_statistics_after_save, if: :size_changed?
|
||||
after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
|
||||
|
||||
after_save :update_file_store
|
||||
after_save :update_file_store, if: :file_changed?
|
||||
|
||||
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
|
||||
|
||||
|
|
|
|||
|
|
@ -530,6 +530,17 @@ module Ci
|
|||
@latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
|
||||
end
|
||||
|
||||
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
|
||||
# They always return `false`.
|
||||
# These methods overwrite autogenerated ones to return correct results.
|
||||
def unknown?
|
||||
Gitlab.rails5? ? source.nil? : super
|
||||
end
|
||||
|
||||
def unknown_source?
|
||||
Gitlab.rails5? ? config_source.nil? : super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ci_yaml_from_repo
|
||||
|
|
|
|||
|
|
@ -13,14 +13,27 @@ module Ci
|
|||
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
|
||||
has_many :builds, foreign_key: :stage_id
|
||||
|
||||
validates :project, presence: true, unless: :importing?
|
||||
validates :pipeline, presence: true, unless: :importing?
|
||||
validates :name, presence: true, unless: :importing?
|
||||
with_options unless: :importing? do
|
||||
validates :project, presence: true
|
||||
validates :pipeline, presence: true
|
||||
validates :name, presence: true
|
||||
validates :position, presence: true
|
||||
end
|
||||
|
||||
after_initialize do |stage|
|
||||
after_initialize do
|
||||
self.status = DEFAULT_STATUS if self.status.nil?
|
||||
end
|
||||
|
||||
before_validation unless: :importing? do
|
||||
next if position.present?
|
||||
|
||||
self.position = statuses.select(:stage_idx)
|
||||
.where('stage_idx IS NOT NULL')
|
||||
.group(:stage_idx)
|
||||
.order('COUNT(*) DESC')
|
||||
.first&.stage_idx.to_i
|
||||
end
|
||||
|
||||
state_machine :status, initial: :created do
|
||||
event :enqueue do
|
||||
transition created: :pending
|
||||
|
|
|
|||
|
|
@ -105,6 +105,10 @@ class Commit
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parent_class
|
||||
::Project
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :raw
|
||||
|
|
@ -420,6 +424,12 @@ class Commit
|
|||
# no-op but needs to be defined since #persisted? is defined
|
||||
end
|
||||
|
||||
def touch_later
|
||||
# No-op.
|
||||
# This method is called by ActiveRecord.
|
||||
# We don't want to do anything for `Commit` model, so this is empty.
|
||||
end
|
||||
|
||||
WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze
|
||||
|
||||
def work_in_progress?
|
||||
|
|
|
|||
|
|
@ -189,4 +189,11 @@ class CommitStatus < ActiveRecord::Base
|
|||
v =~ /\d+/ ? v.to_i : v
|
||||
end
|
||||
end
|
||||
|
||||
# Rails 5.0 autogenerated question mark enum methods return wrong result if enum value is nil.
|
||||
# They always return `false`.
|
||||
# This method overwrites the autogenerated one to return correct result.
|
||||
def unknown_failure?
|
||||
Gitlab.rails5? ? failure_reason.nil? : super
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,20 @@ class DiffNote < Note
|
|||
end
|
||||
|
||||
def diff_file
|
||||
@diff_file ||= self.original_position.diff_file(self.project.repository)
|
||||
@diff_file ||=
|
||||
begin
|
||||
if created_at_diff?(noteable.diff_refs)
|
||||
# We're able to use the already persisted diffs (Postgres) if we're
|
||||
# presenting a "current version" of the MR discussion diff.
|
||||
# So no need to make an extra Gitaly diff request for it.
|
||||
# As an extra benefit, the returned `diff_file` already
|
||||
# has `highlighted_diff_lines` data set from Redis on
|
||||
# `Diff::FileCollection::MergeRequestDiff`.
|
||||
noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first
|
||||
else
|
||||
original_position.diff_file(self.project.repository)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def diff_line
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class LfsObject < ActiveRecord::Base
|
|||
|
||||
mount_uploader :file, LfsObjectUploader
|
||||
|
||||
after_save :update_file_store
|
||||
after_save :update_file_store, if: :file_changed?
|
||||
|
||||
def update_file_store
|
||||
# The file.object_store is set during `uploader.store!`
|
||||
|
|
|
|||
|
|
@ -37,20 +37,20 @@ class GroupMember < Member
|
|||
private
|
||||
|
||||
def send_invite
|
||||
notification_service.invite_group_member(self, @raw_invite_token)
|
||||
run_after_commit_or_now { notification_service.invite_group_member(self, @raw_invite_token) }
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def post_create_hook
|
||||
notification_service.new_group_member(self)
|
||||
run_after_commit_or_now { notification_service.new_group_member(self) }
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def post_update_hook
|
||||
if access_level_changed?
|
||||
notification_service.update_group_member(self)
|
||||
run_after_commit { notification_service.update_group_member(self) }
|
||||
end
|
||||
|
||||
super
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class ProjectMember < Member
|
|||
private
|
||||
|
||||
def send_invite
|
||||
notification_service.invite_project_member(self, @raw_invite_token)
|
||||
run_after_commit_or_now { notification_service.invite_project_member(self, @raw_invite_token) }
|
||||
|
||||
super
|
||||
end
|
||||
|
|
@ -100,7 +100,7 @@ class ProjectMember < Member
|
|||
def post_create_hook
|
||||
unless owner?
|
||||
event_service.join_project(self.project, self.user)
|
||||
notification_service.new_project_member(self)
|
||||
run_after_commit_or_now { notification_service.new_project_member(self) }
|
||||
end
|
||||
|
||||
super
|
||||
|
|
@ -108,7 +108,7 @@ class ProjectMember < Member
|
|||
|
||||
def post_update_hook
|
||||
if access_level_changed?
|
||||
notification_service.update_project_member(self)
|
||||
run_after_commit { notification_service.update_project_member(self) }
|
||||
end
|
||||
|
||||
super
|
||||
|
|
|
|||
|
|
@ -910,7 +910,7 @@ class User < ActiveRecord::Base
|
|||
|
||||
def delete_async(deleted_by:, params: {})
|
||||
block if params[:hard_delete]
|
||||
DeleteUserWorker.perform_async(deleted_by.id, id, params)
|
||||
DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
|
||||
end
|
||||
|
||||
def notification_service
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ module Ci
|
|||
|
||||
def create_stage
|
||||
Ci::Stage.create!(name: @build.stage,
|
||||
position: @build.stage_idx,
|
||||
pipeline: @build.pipeline,
|
||||
project: @build.project)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module Issues
|
|||
issue.update(closed_by: current_user)
|
||||
event_service.close_issue(issue, current_user)
|
||||
create_note(issue, commit) if system_note
|
||||
notification_service.close_issue(issue, current_user) if notifications
|
||||
notification_service.async.close_issue(issue, current_user) if notifications
|
||||
todo_service.close_issue(issue, current_user)
|
||||
execute_hooks(issue, 'close')
|
||||
invalidate_cache_counts(issue, users: issue.assignees)
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ module Issues
|
|||
end
|
||||
|
||||
def notify_participants
|
||||
notification_service.issue_moved(@old_issue, @new_issue, @current_user)
|
||||
notification_service.async.issue_moved(@old_issue, @new_issue, @current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue