Commit bbfbcebd authored by Ruben Davila's avatar Ruben Davila

Merge branch 'master' into 28433-internationalise-cycle-analytics-page

parents 3b82444e 920d55b9

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

......@@ -14,7 +14,8 @@
"plugins": [
"filenames",
"import",
"html"
"html",
"promise"
],
"settings": {
"html/html-extensions": [".html", ".html.raw", ".vue"],
......@@ -26,6 +27,7 @@
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+$"],
"no-multiple-empty-lines": ["error", { "max": 1 }]
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error"
}
}
......@@ -47,11 +47,12 @@ eslint-report.html
/public/uploads.*
/public/uploads/
/shared/artifacts/
/spec/javascripts/fixtures/blob/pdf/
/rails_best_practices_output.html
/tags
/tmp/*
/vendor/bundle/*
/builds/*
/builds*
/shared/*
/.gitlab_workhorse_secret
/webpack-report/
......
This diff is collapsed.
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "regression" or "bug" label:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug
and verify the issue you're about to submit isn't a duplicate.
Please remove this notice if you're confident your issue isn't a duplicate.
------
### Summary
(Summarize the bug encountered concisely)
......@@ -26,6 +40,7 @@ logs, and code as it's very hard to read otherwise.)
#### Results of GitLab environment info
<details>
<pre>
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)
......@@ -33,11 +48,13 @@ logs, and code as it's very hard to read otherwise.)
(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
</pre>
</details>
#### Results of GitLab application Check
<details>
<pre>
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:check SANITIZE=true`)
......@@ -47,8 +64,11 @@ logs, and code as it's very hard to read otherwise.)
(we will only investigate if the tests are passing)
</pre>
</details>
### Possible fixes
(If you can, link to the line of code that might be responsible for the problem)
/label ~bug
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "feature proposal" label:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
and verify the issue you're about to submit isn't a duplicate.
Please remove this notice if you're confident your issue isn't a duplicate.
------
### Description
(Include problem, use cases, benefits, and/or goals)
......@@ -15,3 +28,5 @@
3. How does someone use this
During implementation, this can then be copied and used as a starter for the documentation.)
/label ~"feature proposal"
......@@ -543,7 +543,7 @@ Style/Proc:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 60
Max: 57.08
# This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength:
......@@ -562,7 +562,7 @@ Metrics/ClassLength:
# of test cases needed to validate a method.
Metrics/CyclomaticComplexity:
Enabled: true
Max: 17
Max: 16
# Limit lines to 80 characters.
Metrics/LineLength:
......@@ -983,10 +983,12 @@ RSpec/ExpectActual:
# Checks the file and folder naming of the spec file.
RSpec/FilePath:
Enabled: false
CustomTransform:
RuboCop: rubocop
RSpec: rspec
Enabled: true
IgnoreMethods: true
Exclude:
- 'qa/**/*'
- 'spec/javascripts/fixtures/*'
- 'spec/requests/api/v3/*'
# Checks if there are focused specs.
RSpec/Focus:
......
This diff is collapsed.
......@@ -17,6 +17,8 @@ gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.25.1.1'
gem 'faraday', '~> 0.11.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
......@@ -83,14 +85,14 @@ gem 'kaminari', '~> 0.17.0'
gem 'hamlit', '~> 2.6.1'
# Files attachments
gem 'carrierwave', '~> 0.11.0'
gem 'carrierwave', '~> 1.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
gem 'fog-aws', '~> 0.9'
gem 'fog-core', '~> 1.40'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
......@@ -142,7 +144,7 @@ gem 'after_commit_queue', '~> 1.3.0'
gem 'acts-as-taggable-on', '~> 4.0'
# Background jobs
gem 'sidekiq', '~> 4.2.7'
gem 'sidekiq', '~> 5.0'
gem 'sidekiq-cron', '~> 0.4.4'
gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
......@@ -186,7 +188,7 @@ gem 'gemnasium-gitlab-service', '~> 0.2'
gem 'slack-notifier', '~> 1.5.1'
# Asana integration
gem 'asana', '~> 0.4.0'
gem 'asana', '~> 0.6.0'
# FogBugz integration
gem 'ruby-fogbugz', '~> 0.2.1'
......@@ -296,6 +298,7 @@ group :development, :test do
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
......@@ -350,7 +353,7 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.16.2'
# OAuth
gem 'oauth2', '~> 1.2.0'
gem 'oauth2', '~> 1.3.0'
# Soft deletion
gem 'paranoia', '~> 2.2'
......
......@@ -47,7 +47,7 @@ GEM
akismet (2.0.0)
allocations (1.0.5)
arel (6.0.4)
asana (0.4.0)
asana (0.6.0)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
faraday_middleware-multi_json (~> 0.0)
......@@ -105,12 +105,10 @@ GEM
capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3)
launchy
carrierwave (0.11.2)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
carrierwave (1.0.0)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
mimemagic (>= 0.3.0)
cause (0.1)
charlock_holmes (0.7.3)
chronic (0.10.2)
......@@ -184,7 +182,7 @@ GEM
erubis (2.7.0)
escape_utils (1.1.1)
eventmachine (1.0.8)
excon (0.52.0)
excon (0.55.0)
execjs (2.6.0)
expression_parser (0.9.0)
extlib (0.9.16)
......@@ -193,10 +191,10 @@ GEM
factory_girl_rails (4.7.0)
factory_girl (~> 4.7.0)
railties (>= 3.0.0)
faraday (0.9.2)
faraday (0.11.0)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.10.0)
faraday (>= 0.7.4, < 0.10)
faraday_middleware (0.11.0.1)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
......@@ -211,12 +209,12 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
fog-aws (0.11.0)
fog-aws (0.13.0)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
fog-core (1.42.0)
fog-core (1.44.1)
builder
excon (~> 0.49)
formatador (~> 0.2)
......@@ -238,9 +236,9 @@ GEM
fog-json (>= 1.0)
fog-xml (>= 0.1)
ipaddress (>= 0.8)
fog-xml (0.1.2)
fog-xml (0.1.3)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
......@@ -341,7 +339,7 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grpc (1.1.2)
grpc (1.2.5)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
......@@ -441,7 +439,7 @@ GEM
multi_json (~> 1.10)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.4)
mail (2.6.5)
mime-types (>= 1.16, < 4)
mail_room (0.9.1)
memoist (0.15.0)
......@@ -466,15 +464,15 @@ GEM
mini_portile2 (~> 2.1.0)
numerizer (0.1.1)
oauth (0.5.1)
oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
oauth2 (1.3.1)
faraday (>= 0.8, < 0.12)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4)
oj (2.17.5)
omniauth (1.4.2)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
......@@ -617,7 +615,7 @@ GEM
json
recursive-open-struct (1.0.0)
redcarpet (3.4.0)
redis (3.2.2)
redis (3.3.3)
redis-actionpack (5.0.1)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
......@@ -673,6 +671,7 @@ GEM
rspec-support (~> 3.5.0)
rspec-retry (0.4.5)
rspec-core
rspec-set (0.1.3)
rspec-support (3.5.0)
rspec_profiling (0.0.5)
activerecord
......@@ -730,11 +729,11 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (4.2.7)
sidekiq (5.0.0)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1)
redis (~> 3.3, >= 3.3.3)
sidekiq-cron (0.4.4)
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
......@@ -868,7 +867,7 @@ DEPENDENCIES
after_commit_queue (~> 1.3.0)
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.4.0)
asana (~> 0.6.0)
asciidoctor (~> 1.5.2)
asciidoctor-plantuml (= 0.0.7)
attr_encrypted (~> 3.0.0)
......@@ -885,7 +884,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.11.0)
carrierwave (~> 1.0)
charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
......@@ -906,10 +905,11 @@ DEPENDENCIES
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
faraday (~> 0.11.0)
ffaker (~> 2.4)
flay (~> 2.8.0)
fog-aws (~> 0.9)
fog-core (~> 1.40)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
......@@ -961,7 +961,7 @@ DEPENDENCIES
mysql2 (~> 0.3.16)
net-ssh (~> 3.0.1)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0)
oauth2 (~> 1.3.0)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
......@@ -1006,6 +1006,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
rubocop (~> 0.47.1)
rubocop-rspec (~> 1.15.0)
......@@ -1022,7 +1023,7 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.2.7)
sidekiq (~> 5.0)
sidekiq-cron (~> 0.4.4)
sidekiq-limit_fetch (~> 3.4)
simplecov (~> 0.14.0)
......
......@@ -4,6 +4,7 @@
[![Overall test coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg)](https://gitlab.com/gitlab-org/gitlab-ce/pipelines)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
[![Gitter](https://badges.gitter.im/gitlabhq/gitlabhq.svg)](https://gitter.im/gitlabhq/gitlabhq?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## Test coverage
......@@ -73,7 +74,7 @@ One small thing you also have to do when installing it yourself is to copy the e
cp config/unicorn.rb.example.development config/unicorn.rb
Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
Instructions on how to start GitLab and how to run the tests can be found in the [getting started section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#getting-started).
## Software stack
......
9.1.0-pre
9.2.0-pre
app/assets/images/ci_favicons/favicon_status_canceled.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_canceled.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_canceled.ico
app/assets/images/ci_favicons/favicon_status_canceled.ico
app/assets/images/ci_favicons/favicon_status_canceled.ico
app/assets/images/ci_favicons/favicon_status_canceled.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_created.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_created.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_created.ico
app/assets/images/ci_favicons/favicon_status_created.ico
app/assets/images/ci_favicons/favicon_status_created.ico
app/assets/images/ci_favicons/favicon_status_created.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_failed.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_failed.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_failed.ico
app/assets/images/ci_favicons/favicon_status_failed.ico
app/assets/images/ci_favicons/favicon_status_failed.ico
app/assets/images/ci_favicons/favicon_status_failed.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_manual.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_manual.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_manual.ico
app/assets/images/ci_favicons/favicon_status_manual.ico
app/assets/images/ci_favicons/favicon_status_manual.ico
app/assets/images/ci_favicons/favicon_status_manual.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_not_found.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_not_found.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_not_found.ico
app/assets/images/ci_favicons/favicon_status_not_found.ico
app/assets/images/ci_favicons/favicon_status_not_found.ico
app/assets/images/ci_favicons/favicon_status_not_found.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_pending.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_pending.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_pending.ico
app/assets/images/ci_favicons/favicon_status_pending.ico
app/assets/images/ci_favicons/favicon_status_pending.ico
app/assets/images/ci_favicons/favicon_status_pending.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_running.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_running.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_running.ico
app/assets/images/ci_favicons/favicon_status_running.ico
app/assets/images/ci_favicons/favicon_status_running.ico
app/assets/images/ci_favicons/favicon_status_running.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_skipped.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_skipped.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_skipped.ico
app/assets/images/ci_favicons/favicon_status_skipped.ico
app/assets/images/ci_favicons/favicon_status_skipped.ico
app/assets/images/ci_favicons/favicon_status_skipped.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_success.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_success.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_success.ico
app/assets/images/ci_favicons/favicon_status_success.ico
app/assets/images/ci_favicons/favicon_status_success.ico
app/assets/images/ci_favicons/favicon_status_success.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_warning.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_warning.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_warning.ico
app/assets/images/ci_favicons/favicon_status_warning.ico
app/assets/images/ci_favicons/favicon_status_warning.ico
app/assets/images/ci_favicons/favicon_status_warning.ico
  • 2-up
  • Swipe
  • Onion skin
......@@ -239,6 +239,9 @@ AwardsHandler
if (menu) {
menu.dispatchEvent(new CustomEvent('build-emoji-menu-finish'));
}
}).catch((err) => {
emojiContentElement.insertAdjacentHTML('beforeend', '<p>We encountered an error while adding the remaining categories</p>');
throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`);
});
};
......
function BlobForkSuggestion(openButton, cancelButton, suggestionSection) {
if (openButton) {
openButton.addEventListener('click', () => {
suggestionSection.classList.remove('hidden');
});
const defaults = {
// Buttons that will show the `suggestionSections`
// has `data-fork-path`, and `data-action`
openButtons: [],
// Update the href(from `openButton` -> `data-fork-path`)
// whenever a `openButton` is clicked
forkButtons: [],
// Buttons to hide the `suggestionSections`
cancelButtons: [],
// Section to show/hide
suggestionSections: [],
// Pieces of text that need updating depending on the action, `edit`, `replace`, `delete`
actionTextPieces: [],
};
class BlobForkSuggestion {
constructor(options) {
this.elementMap = Object.assign({}, defaults, options);
this.onOpenButtonClick = this.onOpenButtonClick.bind(this);
this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
}
init() {
this.bindEvents();
return this;
}
bindEvents() {
$(this.elementMap.openButtons).on('click', this.onOpenButtonClick);
$(this.elementMap.cancelButtons).on('click', this.onCancelButtonClick);
}
showSuggestionSection(forkPath, action = 'edit') {
$(this.elementMap.suggestionSections).removeClass('hidden');
$(this.elementMap.forkButtons).attr('href', forkPath);
$(this.elementMap.actionTextPieces).text(action);
}
hideSuggestionSection() {
$(this.elementMap.suggestionSections).addClass('hidden');
}
onOpenButtonClick(e) {
const forkPath = $(e.currentTarget).attr('data-fork-path');
const action = $(e.currentTarget).attr('data-action');
this.showSuggestionSection(forkPath, action);
}
onCancelButtonClick() {
this.hideSuggestionSection();
}
if (cancelButton) {
cancelButton.addEventListener('click', () => {
suggestionSection.classList.add('hidden');
});
destroy() {
$(this.elementMap.openButtons).off('click', this.onOpenButtonClick);
$(this.elementMap.cancelButtons).off('click', this.onCancelButtonClick);
}
}
......
/* eslint-disable no-new */
import Vue from 'vue';
import VueResource from 'vue-resource';
import NotebookLab from 'vendor/notebooklab';
import notebookLab from '../../notebook/index.vue';
Vue.use(VueResource);
Vue.use(NotebookLab);
export default () => {
const el = document.getElementById('js-notebook-viewer');
......@@ -19,6 +18,9 @@ export default () => {
json: {},
};
},
components: {
notebookLab,
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
<div
......
/* eslint-disable no-new */
import Vue from 'vue';
import PDFLab from 'vendor/pdflab';
import workerSrc from 'vendor/pdf.worker';
Vue.use(PDFLab, {
workerSrc,
});
import pdfLab from '../../pdf/index.vue';
export default () => {
const el = document.getElementById('js-pdf-viewer');
......@@ -20,6 +15,9 @@ export default () => {
pdf: el.dataset.endpoint,
};
},
components: {
pdfLab,
},
methods: {
onLoad() {
this.loading = false;
......@@ -31,7 +29,7 @@ export default () => {
},
},
template: `
<div class="container-fluid md prepend-top-default append-bottom-default">
<div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default">
<div
class="text-center loading"
v-if="loading && !error">
......
/* global Flash */
export default class BlobViewer {
constructor() {
this.switcher = document.querySelector('.js-blob-viewer-switcher');
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
this.$fileHolder = $('.file-holder');
let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
this.initBindings();
if (this.switcher && location.hash.indexOf('#L') === 0) {
initialViewerName = 'simple';
}
this.switchToViewer(initialViewerName);
}
initBindings() {
if (this.switcherBtns.length) {
Array.from(this.switcherBtns)
.forEach((el) => {
el.addEventListener('click', this.switchViewHandler.bind(this));
});
}
if (this.copySourceBtn) {
this.copySourceBtn.addEventListener('click', () => {
if (this.copySourceBtn.classList.contains('disabled')) return;
this.switchToViewer('simple');
});
}
}
switchViewHandler(e) {
const target = e.currentTarget;
e.preventDefault();
this.switchToViewer(target.getAttribute('data-viewer'));
}
toggleCopyButtonState() {
if (!this.copySourceBtn) return;
if (this.simpleViewer.getAttribute('data-loaded')) {
this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute('title', 'Wait for the source to load to copy it to the clipboard');
this.copySourceBtn.classList.add('disabled');
} else {
this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
this.copySourceBtn.classList.add('disabled');
}
$(this.copySourceBtn).tooltip('fixTitle');
}
loadViewer(viewerParam) {
const viewer = viewerParam;
const url = viewer.getAttribute('data-url');
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
return;
}
viewer.setAttribute('data-loading', 'true');
$.ajax({
url,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading source view'))
.done((data) => {
viewer.innerHTML = data.html;
$(viewer).syntaxHighlight();
viewer.setAttribute('data-loaded', 'true');
this.$fileHolder.trigger('highlight:line');
this.toggleCopyButtonState();
});
}
switchToViewer(name) {
const newViewer = document.querySelector(`.blob-viewer[data-type='${name}']`);
if (this.activeViewer === newViewer) return;
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`);
const oldViewer = document.querySelector(`.blob-viewer:not([data-type='${name}'])`);
if (oldButton) {
oldButton.classList.remove('active');
}
if (newButton) {
newButton.classList.add('active');
newButton.blur();
}
if (oldViewer) {
oldViewer.classList.add('hidden');
}
newViewer.classList.remove('hidden');
this.activeViewer = newViewer;
this.toggleCopyButtonState();
this.loadViewer(newViewer);
}
}
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global BoardService */
/* global Flash */
import Vue from 'vue';
import VueResource from 'vue-resource';
......@@ -93,7 +94,7 @@ $(() => {
Store.addBlankState();
this.loading = false;
});
}).catch(() => new Flash('An error occurred. Please try again.'));
},
methods: {
updateTokens() {
......
......@@ -57,12 +57,15 @@ export default {
},
loadNextPage() {
const getIssues = this.list.nextPage();
const loadingDone = () => {
this.list.loadingMore = false;
};
if (getIssues) {
this.list.loadingMore = true;
getIssues.then(() => {
this.list.loadingMore = false;
});
getIssues
.then(loadingDone)
.catch(loadingDone);
}
},
toggleForm() {
......
......@@ -51,11 +51,13 @@ gl.issueBoards.IssuesModal = Vue.extend({
showAddIssuesModal() {
if (this.showAddIssuesModal && !this.issues.length) {
this.loading = true;
const loadingDone = () => {
this.loading = false;
};
this.loadIssues()
.then(() => {
this.loading = false;
});
.then(loadingDone)
.catch(loadingDone);
} else if (!this.showAddIssuesModal) {
this.issues = [];
this.selectedIssues = [];
......@@ -67,11 +69,13 @@ gl.issueBoards.IssuesModal = Vue.extend({
if (this.$el.tagName) {
this.page = 1;
this.filterLoading = true;
const loadingDone = () => {
this.filterLoading = false;
};
this.loadIssues(true)
.then(() => {
this.filterLoading = false;
});
.then(loadingDone)
.catch(loadingDone);
}
},
deep: true,
......
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var,
promise/catch-or-return */
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......
......@@ -36,6 +36,9 @@ gl.issueBoards.BoardsStore = {
.save()
.then(() => {
this.state.lists = _.sortBy(this.state.lists, 'position');
})
.catch(() => {
// https://gitlab.com/gitlab-org/gitlab-ce/issues/30821
});
this.removeBlankState();
},
......
......@@ -55,7 +55,15 @@ export default Vue.component('pipelines-table', {
},
shouldRenderEmptyState() {
return !this.state.pipelines.length && !this.isLoading;
return !this.state.pipelines.length &&
!this.isLoading &&
!this.hasError;
},
shouldRenderTable() {
return !this.isLoading &&
this.state.pipelines.length > 0 &&
!this.hasError;
},
},
......@@ -98,15 +106,6 @@ export default Vue.component('pipelines-table', {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
......@@ -145,8 +144,12 @@ export default Vue.component('pipelines-table', {
template: `
<div class="content-list pipelines">
<div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
<div
class="realtime-loading"
v-if="isLoading">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</div>
<empty-state
......@@ -155,8 +158,9 @@ export default Vue.component('pipelines-table', {
<error-state v-if="shouldRenderErrorState" />
<div class="table-holder"
v-if="!isLoading && state.pipelines.length > 0">
<div
class="table-holder"
v-if="shouldRenderTable">
<pipelines-table-component
:pipelines="state.pipelines"
:service="service" />
......
// ECMAScript polyfills
import 'core-js/fn/array/find';
import 'core-js/fn/array/from';
import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
......
......@@ -64,6 +64,8 @@ const ResolveBtn = Vue.extend({
});
},
resolve: function () {
const errorFlashMsg = 'An error occurred when trying to resolve a comment. Please try again.';
if (!this.canResolve) return;
let promise;
......@@ -87,10 +89,12 @@ const ResolveBtn = Vue.extend({
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
this.discussion.updateHeadline(data);
} else {
new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
new Flash(errorFlashMsg);
}
this.updateTooltip();
}).catch(() => {
new Flash(errorFlashMsg);
});
}
},
......
......@@ -51,8 +51,10 @@ class ResolveServiceClass {
discussion.updateHeadline(data);
} else {
new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert');
throw new Error('An error occurred when trying to resolve discussion.');
}
}).catch(() => {
new Flash('An error occurred when trying to resolve a discussion. Please try again.');
});
}
......
......@@ -44,10 +44,12 @@ import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
import ShortcutsWiki from './shortcuts_wiki';
import BlobViewer from './blob/viewer/index';
const ShortcutsBlob = require('./shortcuts_blob');
......@@ -91,11 +93,14 @@ const ShortcutsBlob = require('./shortcuts_blob');
fileBlobPermalinkUrl,
});
new BlobForkSuggestion(
document.querySelector('.js-edit-blob-link-fork-toggler'),
document.querySelector('.js-cancel-fork-suggestion'),
document.querySelector('.js-file-fork-suggestion-section'),
);
new BlobForkSuggestion({
openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
})
.init();
}
switch (page) {
......@@ -144,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob');
new ProjectsList();
break;
case 'dashboard:groups:index':
new GroupsList();
break;
case 'explore:groups:index':
new GroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break;
const exploreGroupsLanding = new Landing(
landingElement,
landingElement.querySelector('.dismiss-button'),
'explore_groups_landing_dismissed',
);
exploreGroupsLanding.toggle();
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
......@@ -296,6 +312,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:blob:show':
new BlobViewer();
gl.TargetBranchDropDown.bootstrap();
initBlob();
break;
......@@ -351,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'users:show':
new UserCallout();
break;
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
break;
}
switch (path.first()) {
case 'sessions':
......@@ -429,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') {
new ZenMode();
new LineHighlighter();
new BlobViewer();
}
break;
case 'labels':
......
......@@ -115,11 +115,13 @@ class DueDateSelect {
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
this.$value.css('display', '');
const fadeOutLoader = () => {
this.$loading.fadeOut();
};
gl.issueBoards.BoardsStore.detail.issue.update(this.$dropdown.attr('data-issue-update'))
.then(() => {
this.$loading.fadeOut();
});
.then(fadeOutLoader)
.catch(fadeOutLoader);
}
submitSelectedDate(isDropdown) {
......@@ -168,8 +170,9 @@ class DueDateSelectors {
const $datePicker = $(this);
const calendar = new Pikaday({
field: $datePicker.get(0),
theme: 'gitlab-theme',
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: $datePicker.parent().get(0),
onSelect(dateText) {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
......
<script>
/* eslint-disable no-new */
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
import eventHub from '../event_hub';
export default Vue.component('environment-component', {
export default {
components: {
'environment-table': EnvironmentTable,
......@@ -140,76 +141,90 @@ export default Vue.component('environment-component', {
});
},
},
template: `
<div :class="cssContainerClass">
<div class="top-area">
<ul v-if="!isLoading" class="nav-links">
<li v-bind:class="{ 'active': scope === null || scope === 'available' }">
<a :href="projectEnvironmentsPath">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li v-bind:class="{ 'active' : scope === 'stopped' }">
<a :href="projectStoppedEnvironmentsPath">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</li>
</ul>
<div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls">
<a :href="newEnvironmentPath" class="btn btn-create">
New environment
};
</script>
<template>
<div :class="cssContainerClass">
<div class="top-area">
<ul
v-if="!isLoading"
class="nav-links">
<li :class="{ active: scope === null || scope === 'available' }">
<a :href="projectEnvironmentsPath">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li :class="{ active : scope === 'stopped' }">
<a :href="projectStoppedEnvironmentsPath">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</div>
</li>
</ul>
<div
v-if="canCreateEnvironmentParsed && !isLoading"
class="nav-controls">
<a
:href="newEnvironmentPath"
class="btn btn-create">
New environment
</a>
</div>
</div>
<div class="content-list environments-container">
<div
class="environments-list-loading text-center"
v-if="isLoading">
<div class="content-list environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.environments.length === 0">
<h2 class="blank-state-title js-blank-state-title">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
Environments are places where code gets deployed, such as staging or production.
<br />
<a :href="helpPagePath">
Read more about environments
</a>
</p>
<a v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New Environment
<i
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</div>
<div
class="blank-state blank-state-no-icon"
v-if="!isLoading && state.environments.length === 0">
<h2 class="blank-state-title js-blank-state-title">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
Environments are places where code gets deployed, such as staging or production.
<br />
<a :href="helpPagePath">
Read more about environments
</a>
</div>
<div class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:service="service"
:is-loading-folder-content="isLoadingFolderContent" />
</div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation">
</table-pagination>
</p>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New Environment
</a>
</div>
<div
class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:service="service"
:is-loading-folder-content="isLoadingFolderContent" />
</div>
<table-pagination
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation" />
</div>
`,
});
</div>
</template>
<script>
/* global Flash */
/* eslint-disable no-new */
......@@ -56,45 +57,47 @@ export default {
return !action.playable;
},
},
};
</script>
<template>
<div
class="btn-group"
role="group">
<button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body"
data-toggle="dropdown"
ref="tooltip"
:title="title"
:aria-label="title"
:disabled="isLoading">
<span>
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true"/>
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</span>
</button>
template: `
<div class="btn-group" role="group">
<button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body"
data-toggle="dropdown"
ref="tooltip"
:title="title"
:aria-label="title"
:disabled="isLoading">
<span>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true"/>
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</span>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ 'disabled': isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg}
<span>
{{action.name}}
</span>
</button>
</li>
</ul>
<span>
{{action.name}}
</span>
</button>
</li>
</ul>
</div>
`,
};
</template>
<script>
/**
* Renders the external url link in environments table.
*/
......@@ -5,7 +6,7 @@ export default {
props: {
externalUrl: {
type: String,
default: '',
required: true,
},
},
......@@ -14,17 +15,19 @@ export default {
return 'Open';
},
},
template: `
<a
class="btn external-url has-tooltip"
data-container="body"
:href="externalUrl"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
`,
};
</script>
<template>
<a
class="btn external-url has-tooltip"
data-container="body"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title"
:href="externalUrl">
<i
class="fa fa-external-link"
aria-hidden="true" />
</a>
</template>
<script>
import Timeago from 'timeago.js';
import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
import StopComponent from './environment_stop';
import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import MonitoringButtonComponent from './environment_monitoring';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
import RollbackComponent from './environment_rollback.vue';
import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit';
import eventHub from '../event_hub';
......@@ -434,117 +435,140 @@ export default {
eventHub.$emit('toggleFolder', this.model, this.folderUrl);
},
},
template: `
<tr :class="{ 'js-child-row': model.isChildren }">
<td>
<a v-if="!model.isFolder"
class="environment-name"
:class="{ 'prepend-left-default': model.isChildren }"
:href="environmentPath">
{{model.name}}
</a>
<span v-else
class="folder-name"
@click="onClickFolder"
role="button">
<span class="folder-icon">
<i
v-show="model.isOpen"
class="fa fa-caret-down"
aria-hidden="true" />
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
aria-hidden="true"/>
</span>
<span class="folder-icon">
<i class="fa fa-folder" aria-hidden="true"></i>
</span>
<span>
{{model.folderName}}
</span>
<span class="badge">
{{model.size}}
</span>
};
</script>
<template>
<tr :class="{ 'js-child-row': model.isChildren }">
<td>
<a
v-if="!model.isFolder"
class="environment-name"
:class="{ 'prepend-left-default': model.isChildren }"
:href="environmentPath">
{{model.name}}
</a>
<span
v-else
class="folder-name"
@click="onClickFolder"
role="button">
<span class="folder-icon">
<i
v-show="model.isOpen"
class="fa fa-caret-down"
aria-hidden="true" />
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
aria-hidden="true"/>
</span>
</td>
<td class="deployment-column">
<span v-if="shouldRenderDeploymentID">
{{deploymentInternalId}}
<span class="folder-icon">
<i
class="fa fa-folder"
aria-hidden="true" />
</span>
<span v-if="!model.isFolder && deploymentHasUser">
by
<a :href="deploymentUser.web_url" class="js-deploy-user-container">
<img class="avatar has-tooltip s20"
:src="deploymentUser.avatar_url"
:alt="userImageAltDescription"
:title="deploymentUser.username" />
</a>
<span>
{{model.folderName}}
</span>
</td>
<td class="environments-build-cell">
<a v-if="shouldRenderBuildName"
class="build-link"
:href="buildPath">
{{buildName}}
</a>
</td>
<td>
<div v-if="!model.isFolder && hasLastDeploymentKey" class="js-commit-component">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"/>
</div>
<p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title">
No deployments yet
</p>
</td>
<td>
<span v-if="!model.isFolder && canShowDate"
class="environment-created-date-timeago">
{{createdDate}}
<span class="badge">
{{model.size}}
</span>
</td>
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<monitoring-button-component v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"
:service="service"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
`,
};
</span>
</td>
<td class="deployment-column">
<span v-if="shouldRenderDeploymentID">
{{deploymentInternalId}}
</span>
<span v-if="!model.isFolder && deploymentHasUser">
by
<a
:href="deploymentUser.web_url"
class="js-deploy-user-container">
<img
class="avatar has-tooltip s20"
:src="deploymentUser.avatar_url"
:alt="userImageAltDescription"
:title="deploymentUser.username" />
</a>
</span>
</td>
<td class="environments-build-cell">
<a
v-if="shouldRenderBuildName"
class="build-link"
:href="buildPath">
{{buildName}}
</a>
</td>
<td>
<div
v-if="!model.isFolder && hasLastDeploymentKey"
class="js-commit-component">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"/>
</div>
<p
v-if="!model.isFolder && !hasLastDeploymentKey"
class="commit-title">
No deployments yet
</p>
</td>
<td>
<span
v-if="!model.isFolder && canShowDate"
class="environment-created-date-timeago">
{{createdDate}}
</span>
</td>
<td class="environments-actions">
<div
v-if="!model.isFolder"
class="btn-group pull-right"
role="group">
<actions-component
v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component
v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<monitoring-button-component
v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"/>
<terminal-button-component
v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<stop-component
v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"
:service="service"/>
<rollback-component
v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
</template>
<script>
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
......@@ -5,7 +6,6 @@ export default {
props: {
monitoringUrl: {
type: String,
default: '',
required: true,
},
},
......@@ -15,16 +15,19 @@ export default {
return 'Monitoring';
},
},
template: `
<a
class="btn monitoring-url has-tooltip"
data-container="body"
:href="monitoringUrl"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title">
<i class="fa fa-area-chart" aria-hidden="true"></i>
</a>
`,
};
</script>
<template>
<a
class="btn monitoring-url has-tooltip"
data-container="body"
target="_blank"
rel="noopener noreferrer nofollow"
:href="monitoringUrl"
:title="title"
:aria-label="title">
<i
class="fa fa-area-chart"
aria-hidden="true" />
</a>
</template>
<script>
/* global Flash */
/* eslint-disable no-new */
/**
......@@ -49,21 +50,25 @@ export default {
});
},
},
};
</script>
<template>
<button
type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
template: `
<button type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
};
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button>
</template>
<script>
/* global Flash */
/* eslint-disable no-new, no-alert */
/**
......@@ -50,17 +51,23 @@ export default {
}
},
},
template: `
<button type="button"
class="btn stop-env-link has-tooltip"
data-container="body"
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
};
</script>
<template>
<button
type="button"
class="btn stop-env-link has-tooltip"
data-container="body"
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
<i
class="fa fa-stop stop-env-icon"
aria-hidden="true" />
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button>
</template>
<script>
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
......@@ -24,14 +25,15 @@ export default {
return 'Terminal';
},
},
template: `
<a class="btn terminal-button has-tooltip"
data-container="body"
:title="title"
:aria-label="title"
:href="terminalPath">
${terminalIconSvg}
</a>
`,
};
</script>
<template>
<a
class="btn terminal-button has-tooltip"
data-container="body"
:title="title"
:aria-label="title"
:href="terminalPath"
v-html="terminalIconSvg">
</a>
</template>
<script>
/**
* Render environments table.
*/
import EnvironmentTableRowComponent from './environment_item';
import EnvironmentTableRowComponent from './environment_item.vue';
export default {
components: {
......@@ -44,54 +45,73 @@ export default {
return `${window.location.pathname}/folders/${model.folderName}`;
},
},
};
</script>
<template>
<table class="table ci-table">
<thead>
<tr>
<th class="environments-name">
Environment
</th>
<th class="environments-deploy">
Last deployment
</th>
<th class="environments-build">
Job
</th>
<th class="environments-commit">
Commit
</th>
<th class="environments-date">
Updated
</th>
<th class="environments-actions"></th>
</tr>
</thead>
<tbody>
<template
v-for="model in environments"
v-bind:model="model">
<tr
is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service" />
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<tr v-if="isLoadingFolderContent">
<td colspan="6" class="text-center">
<i
class="fa fa-spin fa-spinner fa-2x"
aria-hidden="true" />
</td>
</tr>
template: `
<table class="table ci-table">
<thead>
<tr>
<th class="environments-name">Environment</th>
<th class="environments-deploy">Last deployment</th>
<th class="environments-build">Job</th>
<th class="environments-commit">Commit</th>
<th class="environments-date">Updated</th>
<th class="environments-actions"></th>
</tr>
</thead>
<tbody>
<template v-for="model in environments"
v-bind:model="model">
<tr is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"></tr>
<template v-else>
<tr
is="environment-item"
v-for="children in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service" />
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<tr v-if="isLoadingFolderContent">
<td colspan="6" class="text-center">
<i class="fa fa-spin fa-spinner fa-2x" aria-hidden="true"/>
<tr>
<td
colspan="6"
class="text-center">
<a
:href="folderUrl(model)"
class="btn btn-default">
Show all
</a>
</td>
</tr>
<template v-else>
<tr is="environment-item"
v-for="children in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"></tr>
<tr>
<td colspan="6" class="text-center">
<a :href="folderUrl(model)" class="btn btn-default">
Show all
</a>
</td>
</tr>
</template>
</template>
</template>
</tbody>
</table>
`,
};
</template>
</tbody>
</table>
</template>
import EnvironmentsComponent from './components/environment';
import Vue from 'vue';
import EnvironmentsComponent from './components/environment.vue';
$(() => {
window.gl = window.gl || {};
if (gl.EnvironmentsListApp) {
gl.EnvironmentsListApp.$destroy(true);
}
gl.EnvironmentsListApp = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
});
});
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#environments-list-view',
components: {
'environments-table-app': EnvironmentsComponent,
},
render: createElement => createElement('environments-table-app'),
}));
import EnvironmentsFolderComponent from './environments_folder_view';
import Vue from 'vue';
import EnvironmentsFolderComponent from './environments_folder_view.vue';
$(() => {
window.gl = window.gl || {};
if (gl.EnvironmentsListFolderApp) {
gl.EnvironmentsListFolderApp.$destroy(true);
}
gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({
el: document.querySelector('#environments-folder-list-view'),
});
});
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#environments-folder-list-view',
components: {
'environments-folder-app': EnvironmentsFolderComponent,
},
render: createElement => createElement('environments-folder-app'),
}));
<script>
/* eslint-disable no-new */
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
export default Vue.component('environment-folder-view', {
export default {
components: {
'environment-table': EnvironmentTable,
'table-pagination': TablePaginationComponent,
......@@ -116,54 +116,66 @@ export default Vue.component('environment-folder-view', {
return param;
},
},
};
</script>
<template>
<div :class="cssContainerClass">
<div
class="top-area"
v-if="!isLoading">
<h4 class="js-folder-name environments-folder-name">
Environments / <b>{{folderName}}</b>
</h4>
<ul class="nav-links">
<li :class="{ active: scope === null || scope === 'available' }">
<a
:href="availablePath"
class="js-available-environments-folder-tab">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li :class="{ active : scope === 'stopped' }">
<a
:href="stoppedPath"
class="js-stopped-environments-folder-tab">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</li>
</ul>
</div>
template: `
<div :class="cssContainerClass">
<div class="top-area" v-if="!isLoading">
<h4 class="js-folder-name environments-folder-name">
Environments / <b>{{folderName}}</b>
</h4>
<ul class="nav-links">
<li v-bind:class="{ 'active': scope === null || scope === 'available' }">
<a :href="availablePath" class="js-available-environments-folder-tab">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li v-bind:class="{ 'active' : scope === 'stopped' }">
<a :href="stoppedPath" class="js-stopped-environments-folder-tab">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</li>
</ul>
<div class="environments-container">
<div
class="environments-list-loading text-center"
v-if="isLoading">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</div>
<div class="environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<div
class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:service="service"/>
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation"/>
</div>
<table-pagination
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation"/>
</div>
</div>
`,
});
</div>
</template>
......@@ -77,13 +77,14 @@ class FilteredSearchManager {
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
this.removeTokenWrapper = this.removeToken.bind(this);
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
......@@ -96,12 +97,13 @@ class FilteredSearchManager {
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.addEventListener('click', this.removeTokenWrapper);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
document.addEventListener('keydown', this.removeSelectedTokenWrapper);
document.addEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
......@@ -117,12 +119,13 @@ class FilteredSearchManager {
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.removeEventListener('click', this.removeTokenWrapper);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
document.removeEventListener('keydown', this.removeSelectedTokenKeydownWrapper);
eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
......@@ -195,14 +198,28 @@ class FilteredSearchManager {
static selectToken(e) {
const button = e.target.closest('.selectable');
const removeButtonSelected = e.target.closest('.remove-token');
if (button) {
if (!removeButtonSelected && button) {
e.preventDefault();
e.stopPropagation();
gl.FilteredSearchVisualTokens.selectToken(button);
}
}
removeToken(e) {
const removeButtonSelected = e.target.closest('.remove-token');
if (removeButtonSelected) {
e.preventDefault();
e.stopPropagation();
const button = e.target.closest('.selectable');
gl.FilteredSearchVisualTokens.selectToken(button, true);
this.removeSelectedToken();
}
}
unselectEditTokens(e) {
const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
......@@ -248,16 +265,21 @@ class FilteredSearchManager {
}
}
removeSelectedToken(e) {
removeSelectedTokenKeydown(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
gl.FilteredSearchVisualTokens.removeSelectedToken();
this.handleInputPlaceholder();
this.toggleClearSearchButton();
this.removeSelectedToken();
}
}
removeSelectedToken() {
gl.FilteredSearchVisualTokens.removeSelectedToken();
this.handleInputPlaceholder();
this.toggleClearSearchButton();
this.dropdownManager.updateCurrentDropdownOffset();
}
onClearSearch(e) {
e.preventDefault();
this.clearSearch();
......@@ -343,6 +365,8 @@ class FilteredSearchManager {
const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
this.recentSearchesService.save(resultantSearches);
}
}).catch(() => {
// https://gitlab.com/gitlab-org/gitlab-ce/issues/30821
});
}
......
......@@ -16,11 +16,11 @@ class FilteredSearchVisualTokens {
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
static selectToken(tokenButton) {
static selectToken(tokenButton, forceSelection = false) {
const selected = tokenButton.classList.contains('selected');
FilteredSearchVisualTokens.unselectTokens();
if (!selected) {
if (!selected || forceSelection) {
tokenButton.classList.add('selected');
}
}
......@@ -38,7 +38,12 @@ class FilteredSearchVisualTokens {
return `
<div class="selectable" role="button">
<div class="name"></div>
<div class="value"></div>
<div class="value-container">
<div class="value"></div>
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
</div>
</div>
`;
}
......@@ -122,7 +127,8 @@ class FilteredSearchVisualTokens {
if (value) {
const button = lastVisualToken.querySelector('.selectable');
button.removeChild(value);
const valueContainer = lastVisualToken.querySelector('.value-container');
button.removeChild(valueContainer);
lastVisualToken.innerHTML = button.innerHTML;
} else {
lastVisualToken.closest('.tokens-container').removeChild(lastVisualToken);
......
......@@ -3,6 +3,7 @@
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from '~/behaviors/gl_emoji';
import glRegexp from '~/lib/utils/regexp';
// Creates the variables for setting up GFM auto-completion
window.gl = window.gl || {};
......@@ -127,7 +128,15 @@ window.gl.GfmAutoComplete = {
callbacks: {
sorter: this.DefaultOptions.sorter,
beforeInsert: this.DefaultOptions.beforeInsert,
filter: this.DefaultOptions.filter
filter: this.DefaultOptions.filter,
matcher: (flag, subtext) => {
const relevantText = subtext.trim().split(/\s/).pop();
const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
const match = regexp.exec(relevantText);
return match && match.length ? match[1] : null;
}
}
});
// Team Members
......
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, max-len */
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var,
camelcase, one-var-declaration-per-line, quotes, object-shorthand,
prefer-arrow-callback, comma-dangle, consistent-return, yoda,
prefer-rest-params, prefer-spread, no-unused-vars, prefer-template,
promise/catch-or-return */
/* global Api */
var slice = [].slice;
......
......@@ -39,8 +39,9 @@
if ($issuableDueDate.length) {
calendar = new Pikaday({
field: $issuableDueDate.get(0),
theme: 'gitlab-theme',
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: $issuableDueDate.parent().get(0),
onSelect: function(dateText) {
$issuableDueDate.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
......
......@@ -34,17 +34,6 @@ export default {
};
},
methods: {
fetch() {
this.poll.makeRequest();
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
},
renderResponse(res) {
const body = JSON.parse(res.body);
this.triggerAnimation(body);
......@@ -71,7 +60,17 @@ export default {
},
},
created() {
this.fetch();
if (!Visibility.hidden()) {
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
},
};
</script>
......
......@@ -332,6 +332,9 @@
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e, isMarking) {
var isIssueIndex, isMRIndex, page, boardsModel;
var fadeOutLoader = () => {
$loading.fadeOut();
};
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
......@@ -396,9 +399,8 @@
$loading.fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
$loading.fadeOut();
});
.then(fadeOutLoader)
.catch(fadeOutLoader);
}
else {
if ($dropdown.hasClass('js-multiselect')) {
......
import Cookies from 'js-cookie';
class Landing {
constructor(landingElement, dismissButton, cookieName) {
this.landingElement = landingElement;
this.cookieName = cookieName;
this.dismissButton = dismissButton;
this.eventWrapper = {};
}
toggle() {
const isDismissed = this.isDismissed();
this.landingElement.classList.toggle('hidden', isDismissed);
if (!isDismissed) this.addEvents();
}
addEvents() {
this.eventWrapper.dismissLanding = this.dismissLanding.bind(this);
this.dismissButton.addEventListener('click', this.eventWrapper.dismissLanding);
}
removeEvents() {
this.dismissButton.removeEventListener('click', this.eventWrapper.dismissLanding);
}
dismissLanding() {
this.landingElement.classList.add('hidden');
Cookies.set(this.cookieName, 'true', { expires: 365 });
}
isDismissed() {
return Cookies.get(this.cookieName) === 'true';
}
}
export default Landing;
......@@ -169,7 +169,10 @@
w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection();
if (selection.rangeCount === 0) return null;
const documentFragment = selection.getRangeAt(0).cloneContents();
const documentFragment = document.createDocumentFragment();
for (let i = 0; i < selection.rangeCount; i += 1) {
documentFragment.appendChild(selection.getRangeAt(i).cloneContents());
}
if (documentFragment.textContent.length === 0) return null;
return documentFragment;
......
/**
* Regexp utility for the convenience of working with regular expressions.
*
*/
// Inspired by https://github.com/mishoo/UglifyJS/blob/2bc1d02363db3798d5df41fb5059a19edca9b7eb/lib/parse-js.js#L203
// Unicode 6.1
const unicodeLetters = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
export default { unicodeLetters };
......@@ -41,7 +41,6 @@ require('vendor/jquery.scrollTo');
LineHighlighter.prototype._hash = '';
function LineHighlighter(hash) {
var range;
if (hash == null) {
// Initialize a LineHighlighter object
//
......@@ -51,10 +50,22 @@ require('vendor/jquery.scrollTo');
this.setHash = bind(this.setHash, this);
this.highlightLine = bind(this.highlightLine, this);
this.clickHandler = bind(this.clickHandler, this);
this.highlightHash = this.highlightHash.bind(this);
this._hash = hash;
this.bindEvents();
if (hash !== '') {
range = this.hashToRange(hash);
this.highlightHash();
}
LineHighlighter.prototype.bindEvents = function() {
const $fileHolder = $('.file-holder');
$fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
$fileHolder.on('highlight:line', this.highlightHash);
};
LineHighlighter.prototype.highlightHash = function() {
var range;
if (this._hash !== '') {
range = this.hashToRange(this._hash);
if (range[0]) {
this.highlightRange(range);
$.scrollTo("#L" + range[0], {
......@@ -64,10 +75,6 @@ require('vendor/jquery.scrollTo');
});
}
}
}
LineHighlighter.prototype.bindEvents = function() {
$('#blob-content-holder').on('click', 'a[data-line-number]', this.clickHandler);
};
LineHighlighter.prototype.clickHandler = function(event) {
......
......@@ -18,9 +18,10 @@
const calendar = new Pikaday({
field: $input.get(0),
theme: 'gitlab-theme',
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
minDate: new Date(),
container: $input.parent().get(0),
onSelect(dateText) {
$input.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
......
......@@ -5,6 +5,7 @@
import Cookies from 'js-cookie';
import './breakpoints';
import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
/* eslint-disable max-len */
// MergeRequestTabs
......@@ -266,6 +267,17 @@ import './flash';
new gl.Diff();
this.scrollToElement('#diffs');
$('.diff-file').each((i, el) => {
new BlobForkSuggestion({
openButtons: $(el).find('.js-edit-blob-link-fork-toggler'),
forkButtons: $(el).find('.js-fork-suggestion-button'),
cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
})
.init();
});
},
});
}
......
......@@ -157,7 +157,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
$('.ci-widget-fetching').show();
return $.getJSON(this.opts.ci_status_url, (function(_this) {
return function(data) {
var message, status, title;
var message, status, title, callback;
_this.status = data.status;
_this.hasCi = data.has_ci;
_this.updateMergeButton(_this.status, _this.hasCi);
......@@ -179,6 +179,12 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
_this.opts.ci_sha = data.sha;
_this.updateCommitUrls(data.sha);
}
if (data.status === "success" || data.status === "failed") {
callback = function() {
return _this.getMergeStatus();
};
return setTimeout(callback, 2000);
}
if (showNotification && data.status) {
status = _this.ciLabelForStatus(data.status);
if (status === "preparing") {
......
......@@ -164,6 +164,9 @@
.then(function () {
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
})
.catch(() => {
$loading.fadeOut();
});
} else {
selected = $selectbox.find('input[type="hidden"]').val();
......
......@@ -28,7 +28,9 @@ export default class MiniPipelineGraph {
* All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
$(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
$(document)
.off('shown.bs.dropdown', this.container)
.on('shown.bs.dropdown', this.container, this.getBuildsList);
}
/**
......@@ -91,6 +93,9 @@ export default class MiniPipelineGraph {
},
error: () => {
this.toggleLoading(button);
if ($(button).parent().hasClass('open')) {
$(button).dropdown('toggle');
}
new Flash('An error occurred while fetching the builds.', 'alert');
},
});
......
import d3 from 'd3';
export const dateFormat = d3.time.format('%b %d, %Y');
export const timeFormat = d3.time.format('%H:%M%p');
/* global Flash */
import d3 from 'd3';
import {
dateFormat,
timeFormat,
} from './constants';
export default class Deployments {
constructor(width, height) {
this.width = width;
this.height = height;
this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
this.createGradientDef();
}
init(chartData) {
this.chartData = chartData;
this.x = d3.time.scale().range([0, this.width]);
this.x.domain(d3.extent(this.chartData, d => d.time));
this.charts = d3.selectAll('.prometheus-graph');
this.getData();
}
getData() {
$.ajax({
url: this.endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error getting deployment information.'))
.done((data) => {
this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
const xPos = Math.floor(this.x(time));
time.setSeconds(this.chartData[0].time.getSeconds());
if (xPos >= 0) {
deploymentDataArray.push({
id: deployment.id,
time,
sha: deployment.sha,
tag: deployment.tag,
ref: deployment.ref.name,
xPos,
});
}
return deploymentDataArray;
}, []);
this.plotData();
});
}
plotData() {
this.charts.each((d, i) => {
const svg = d3.select(this.charts[0][i]);
const chart = svg.select('.graph-container');
const key = svg.node().getAttribute('graph-type');
this.createLine(chart, key);
this.createDeployInfoBox(chart, key);
});
}
createGradientDef() {
const defs = d3.select('body')
.append('svg')
.attr({
height: 0,
width: 0,
})
.append('defs');
defs.append('linearGradient')
.attr({
id: 'shadow-gradient',
})
.append('stop')
.attr({
offset: '0%',
'stop-color': '#000',
'stop-opacity': 0.4,
})
.select(this.selectParentNode)
.append('stop')
.attr({
offset: '100%',
'stop-color': '#000',
'stop-opacity': 0,
});
}
createLine(chart, key) {
chart.append('g')
.attr({
class: 'deploy-info',
})
.selectAll('.deploy-info')
.data(this.data)
.enter()
.append('g')
.attr({
class: d => `deploy-info-${d.id}-${key}`,
transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
})
.append('rect')
.attr({
x: 1,
y: 0,
height: this.height + 1,
width: 3,
fill: 'url(#shadow-gradient)',
})
.select(this.selectParentNode)
.append('line')
.attr({
class: 'deployment-line',
x1: 0,
x2: 0,
y1: 0,
y2: this.height + 1,
});
}
createDeployInfoBox(chart, key) {
chart.selectAll('.deploy-info')
.selectAll('.js-deploy-info-box')
.data(this.data)
.enter()
.select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
.append('svg')
.attr({
class: 'js-deploy-info-box hidden',
x: 3,
y: 0,
width: 92,
height: 60,
})
.append('rect')
.attr({
class: 'rect-text-metric deploy-info-rect rect-metric',
x: 1,
y: 1,
rx: 2,
width: 90,
height: 58,
})
.select(this.selectParentNode)
.append('g')
.attr({
transform: 'translate(5, 2)',
})
.append('text')
.attr({
class: 'deploy-info-text text-metric-bold',
})
.text(Deployments.refText)
.select(this.selectParentNode)
.append('text')
.attr({
class: 'deploy-info-text',
y: 18,
})
.text(d => dateFormat(d.time))
.select(this.selectParentNode)
.append('text')
.attr({
class: 'deploy-info-text text-metric-bold',
y: 38,
})
.text(d => timeFormat(d.time));
}
static toggleDeployTextbox(deploy, key, showInfoBox) {
d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
.classed('hidden', !showInfoBox);
}
mouseOverDeployInfo(mouseXPos, key) {
if (!this.data) return false;
let dataFound = false;
this.data.forEach((d) => {
if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
dataFound = d.xPos + 1;
Deployments.toggleDeployTextbox(d, key, true);
} else {
Deployments.toggleDeployTextbox(d, key, false);
}
});
return dataFound;
}
/* `this` is bound to the D3 node */
selectParentNode() {
return this.parentNode;
}
static refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 6);
}
}
......@@ -3,16 +3,20 @@
import d3 from 'd3';
import statusCodes from '~/lib/utils/http_status';
import { formatRelevantDigits } from '~/lib/utils/number_utils';
import Deployments from './deployments';
import '../lib/utils/common_utils';
import { formatRelevantDigits } from '../lib/utils/number_utils';
import '../flash';
import {
dateFormat,
timeFormat,
} from './constants';
const prometheusContainer = '.prometheus-container';
const prometheusParentGraphContainer = '.prometheus-graphs';
const prometheusGraphsContainer = '.prometheus-graph';
const prometheusStatesContainer = '.prometheus-state';
const metricsEndpoint = 'metrics.json';
const timeFormat = d3.time.format('%H:%M');
const dayFormat = d3.time.format('%b %e, %a');
const bisectDate = d3.bisector(d => d.time).left;
const extraAddedWidthParent = 100;
......@@ -22,6 +26,7 @@ class PrometheusGraph {
const hasMetrics = $prometheusContainer.data('has-metrics');
this.docLink = $prometheusContainer.data('doc-link');
this.integrationLink = $prometheusContainer.data('prometheus-integration');
this.state = '';
$(document).ajaxError(() => {});
......@@ -35,11 +40,13 @@ class PrometheusGraph {
this.width = parentContainerWidth - this.margin.left - this.margin.right;
this.height = this.originalHeight - this.margin.top - this.margin.bottom;
this.backOffRequestCounter = 0;
this.deployments = new Deployments(this.width, this.height);
this.configureGraph();
this.init();
} else {
const prevState = this.state;
this.state = '.js-getting-started';
this.updateState();
this.updateState(prevState);
}
}
......@@ -53,23 +60,31 @@ class PrometheusGraph {
}
init() {
this.getData().then((metricsResponse) => {
return this.getData().then((metricsResponse) => {
let enoughData = true;
Object.keys(metricsResponse.metrics).forEach((key) => {
let currentKey;
if (key === 'cpu_values' || key === 'memory_values') {
currentKey = metricsResponse.metrics[key];
if (Object.keys(currentKey).length === 0) {
enoughData = false;
}
}
});
if (!enoughData) {
this.state = '.js-loading';
this.updateState();
if (typeof metricsResponse === 'undefined') {
enoughData = false;
} else {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const currentData = (metricsResponse.metrics[key])[0];
if (currentData.values.length <= 2) {
enoughData = false;
}
}
});
}
if (enoughData) {
$(prometheusStatesContainer).hide();
$(prometheusParentGraphContainer).show();
this.transformData(metricsResponse);
this.createGraph();
const firstMetricData = this.graphSpecificProperties[
Object.keys(this.graphSpecificProperties)[0]
].data;
this.deployments.init(firstMetricData);
}
});
}
......@@ -92,6 +107,7 @@ class PrometheusGraph {
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.bottom + this.margin.top)
.append('g')
.attr('class', 'graph-container')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`);
const axisLabelContainer = d3.select(prometheusGraphContainer)
......@@ -112,6 +128,7 @@ class PrometheusGraph {
.scale(y)
.ticks(this.commonGraphProperties.axis_no_ticks)
.tickSize(-this.width)
.outerTickSize(0)
.orient('left');
this.createAxisLabelContainers(axisLabelContainer, key);
......@@ -244,7 +261,8 @@ class PrometheusGraph {
const d1 = currentGraphProps.data[overlayIndex];
const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
const currentData = evalTime ? d1 : d0;
const currentTimeCoordinate = currentGraphProps.xScale(currentData.time);
const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
......@@ -252,13 +270,12 @@ class PrometheusGraph {
// Clear up all the pieces of the flag
d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .text-metric`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
currentChart.append('line')
.attr('class', 'selected-metric-line')
.attr({
class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
x1: currentTimeCoordinate,
y1: currentGraphProps.yScale(0),
x2: currentTimeCoordinate,
......@@ -268,33 +285,45 @@ class PrometheusGraph {
currentChart.append('circle')
.attr('class', 'circle-metric')
.attr('fill', currentGraphProps.line_color)
.attr('cx', currentTimeCoordinate)
.attr('cx', currentDeployXPos || currentTimeCoordinate)
.attr('cy', currentGraphProps.yScale(currentData.value))
.attr('r', this.commonGraphProperties.circle_radius_metric);
if (currentDeployXPos) return;
// The little box with text
const rectTextMetric = currentChart.append('g')
.attr('class', 'rect-text-metric')
.attr('translate', `(${currentTimeCoordinate}, ${currentGraphProps.yScale(currentData.value)})`);
const rectTextMetric = currentChart.append('svg')
.attr({
class: 'rect-text-metric',
x: currentTimeCoordinate,
y: 0,
});
rectTextMetric.append('rect')
.attr('class', 'rect-metric')
.attr('x', currentTimeCoordinate + 10)
.attr('y', maxMetricValue)
.attr('width', this.commonGraphProperties.rect_text_width)
.attr('height', this.commonGraphProperties.rect_text_height);
.attr({
class: 'rect-metric',
x: 4,
y: 1,
rx: 2,
width: this.commonGraphProperties.rect_text_width,
height: this.commonGraphProperties.rect_text_height,
});
rectTextMetric.append('text')
.attr('class', 'text-metric')
.attr('x', currentTimeCoordinate + 35)
.attr('y', maxMetricValue + 35)
.attr({
class: 'text-metric text-metric-bold',
x: 8,
y: 35,
})
.text(timeFormat(currentData.time));
rectTextMetric.append('text')
.attr('class', 'text-metric-date')
.attr('x', currentTimeCoordinate + 15)
.attr('y', maxMetricValue + 15)
.text(dayFormat(currentData.time));
.attr({
class: 'text-metric-date',
x: 8,
y: 15,
})
.text(dateFormat(currentData.time));
let currentMetricValue = formatRelevantDigits(currentData.value);
if (key === 'cpu_values') {
......@@ -340,6 +369,8 @@ class PrometheusGraph {
getData() {
const maxNumberOfRequests = 3;
this.state = '.js-loading';
this.updateState();
return gl.utils.backOff((next, stop) => {
$.ajax({
url: metricsEndpoint,
......@@ -350,12 +381,11 @@ class PrometheusGraph {
this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < maxNumberOfRequests) {
next();
} else {
stop({
status: resp.status,
metrics: data,
});
} else if (this.backOffRequestCounter >= maxNumberOfRequests) {
stop(new Error('loading'));
}
} else if (!data.success) {
stop(new Error('loading'));
} else {
stop({
status: resp.status,
......@@ -371,8 +401,9 @@ class PrometheusGraph {
return resp.metrics;
})
.catch(() => {
const prevState = this.state;
this.state = '.js-unable-to-connect';
this.updateState();
this.updateState(prevState);
});
}
......@@ -380,19 +411,20 @@ class PrometheusGraph {
Object.keys(metricsResponse.metrics).forEach((key) => {
if (key === 'cpu_values' || key === 'memory_values') {
const metricValues = (metricsResponse.metrics[key])[0];
if (metricValues !== undefined) {
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
}
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
time: new Date(metric[0] * 1000),
value: metric[1],
}));
}
});
}
updateState() {
updateState(prevState) {
const $statesContainer = $(prometheusStatesContainer);
$(prometheusParentGraphContainer).hide();
if (prevState) {
$(`${prevState}`, $statesContainer).addClass('hidden');
}
$(`${this.state}`, $statesContainer).removeClass('hidden');
$(prometheusStatesContainer).show();
}
......
<template>
<div class="cell">
<code-cell
type="input"
:raw-code="rawInputCode"
:count="cell.execution_count"
:code-css-class="codeCssClass" />
<output-cell
v-if="hasOutput"
:count="cell.execution_count"
:output="output"
:code-css-class="codeCssClass" />
</div>
</template>
<script>
import CodeCell from './code/index.vue';
import OutputCell from './output/index.vue';
export default {
components: {
'code-cell': CodeCell,
'output-cell': OutputCell,
},
props: {
cell: {
type: Object,
required: true,
},
codeCssClass: {
type: String,
required: false,
default: '',
},
},
computed: {
rawInputCode() {
if (this.cell.source) {
return this.cell.source.join('');
}
return '';
},
hasOutput() {
return this.cell.outputs.length;
},
output() {
return this.cell.outputs[0];
},
},
};
</script>
<style scoped>
.cell {
flex-direction: column;
}
</style>
<template>
<div :class="type">
<prompt
:type="promptType"
:count="count" />
<pre
class="language-python"
:class="codeCssClass"
ref="code"
v-text="code">
</pre>
</div>
</template>
<script>
import Prism from '../../lib/highlight';
import Prompt from '../prompt.vue';
export default {
components: {
prompt: Prompt,
},
props: {
count: {
type: Number,
required: false,
default: 0,
},
codeCssClass: {
type: String,
required: false,
default: '',
},
type: {
type: String,
required: true,
},
rawCode: {
type: String,
required: true,
},
},
computed: {
code() {
return this.rawCode;
},
promptType() {
const type = this.type.split('put')[0];
return type.charAt(0).toUpperCase() + type.slice(1);
},
},
mounted() {
Prism.highlightElement(this.$refs.code);
},
};
</script>
export { default as MarkdownCell } from './markdown.vue';
export { default as CodeCell } from './code.vue';
<template>
<div class="cell text-cell">
<prompt />
<div class="markdown" v-html="markdown"></div>
</div>
</template>
<script>
/* global katex */
import marked from 'marked';
import Prompt from './prompt.vue';
const renderer = new marked.Renderer();
/*
Regex to match KaTex blocks.
Supports the following:
\begin{equation}<math>\end{equation}
$$<math>$$
inline $<math>$
The matched text then goes through the KaTex renderer & then outputs the HTML
*/
const katexRegexString = `(
^\\\\begin{[a-zA-Z]+}\\s
|
^\\$\\$
|
\\s\\$(?!\\$)
)
(.+?)
(
\\s\\\\end{[a-zA-Z]+}$
|
\\$\\$$
|
\\$
)
`.replace(/\s/g, '').trim();
renderer.paragraph = (t) => {
let text = t;
let inline = false;
if (typeof katex !== 'undefined') {
const katexString = text.replace(/\\/g, '\\');
const matches = new RegExp(katexRegexString, 'gi').exec(katexString);
if (matches && matches.length > 0) {
if (matches[1].trim() === '$' && matches[3].trim() === '$') {
inline = true;
text = `${katexString.replace(matches[0], '')} ${katex.renderToString(matches[2])}`;
} else {
text = katex.renderToString(matches[2]);
}
}
}
return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
};
marked.setOptions({
sanitize: true,
renderer,
});
export default {
components: {
prompt: Prompt,
},
props: {
cell: {
type: Object,
required: true,
},
},
computed: {
markdown() {
return marked(this.cell.source.join(''));
},
},
};
</script>
<style>
.markdown .katex {
display: block;
text-align: center;
}
.markdown .inline-katex .katex {
display: inline;
text-align: initial;
}
</style>
<template>
<div class="output">
<prompt />
<div v-html="rawCode"></div>
</div>
</template>
<script>
import Prompt from '../prompt.vue';
export default {
props: {
rawCode: {
type: String,
required: true,
},
},
components: {
prompt: Prompt,
},
};
</script>
<template>
<div class="output">
<prompt />
<img
:src="'data:' + outputType + ';base64,' + rawCode" />
</div>
</template>
<script>
import Prompt from '../prompt.vue';
export default {
props: {
outputType: {
type: String,
required: true,
},
rawCode: {
type: String,
required: true,
},
},
components: {
prompt: Prompt,
},
};
</script>
<template>
<component :is="componentName"
type="output"
:outputType="outputType"
:count="count"
:raw-code="rawCode"
:code-css-class="codeCssClass" />
</template>
<script>
import CodeCell from '../code/index.vue';
import Html from './html.vue';
import Image from './image.vue';
export default {
props: {
codeCssClass: {
type: String,
required: false,
default: '',
},
count: {
type: Number,
required: false,
default: 0,
},
output: {
type: Object,
requred: true,
},
},
components: {
'code-cell': CodeCell,
'html-output': Html,
'image-output': Image,
},
data() {
return {
outputType: '',
};
},
computed: {
componentName() {
if (this.output.text) {
return 'code-cell';
} else if (this.output.data['image/png']) {
this.outputType = 'image/png';
return 'image-output';
} else if (this.output.data['text/html']) {
this.outputType = 'text/html';
return 'html-output';
} else if (this.output.data['image/svg+xml']) {
this.outputType = 'image/svg+xml';
return 'html-output';
}
this.outputType = 'text/plain';
return 'code-cell';
},
rawCode() {
if (this.output.text) {
return this.output.text.join('');
}
return this.dataForType(this.outputType);
},
},
methods: {
dataForType(type) {
let data = this.output.data[type];
if (typeof data === 'object') {
data = data.join('');
}
return data;
},
},
};
</script>
<template>
<div class="prompt">
<span v-if="type && count">
{{ type }} [{{ count }}]:
</span>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
required: false,
},
count: {
type: Number,
required: false,
},
},
};
</script>
<style scoped>
.prompt {
padding: 0 10px;
min-width: 7em;
font-family: monospace;
}
</style>
<template>
<div v-if="hasNotebook">
<component
v-for="(cell, index) in cells"
:is="cellType(cell.cell_type)"
:cell="cell"
:key="index"
:code-css-class="codeCssClass" />
</div>
</template>
<script>
import {
MarkdownCell,
CodeCell,
} from './cells';
export default {
components: {
'code-cell': CodeCell,
'markdown-cell': MarkdownCell,
},
props: {
notebook: {
type: Object,
required: true,
},
codeCssClass: {
type: String,
required: false,
default: '',
},
},
methods: {
cellType(type) {
return `${type}-cell`;
},
},
computed: {
cells() {
if (this.notebook.worksheets) {
const data = {
cells: [],
};
return this.notebook.worksheets.reduce((cellData, sheet) => {
const cellDataCopy = cellData;
cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
return cellDataCopy;
}, data).cells;
}
return this.notebook.cells;
},
hasNotebook() {
return Object.keys(this.notebook).length;
},
},
};
</script>
<style>
.cell,
.input,
.output {
display: flex;
width: 100%;
margin-bottom: 10px;
}
.cell pre {
margin: 0;
width: 100%;
}
</style>
import Prism from 'prismjs';
import 'prismjs/components/prism-python';
import 'prismjs/plugins/custom-class/prism-custom-class';
Prism.plugins.customClass.map({
comment: 'c',
error: 'err',
operator: 'o',
constant: 'kc',
namespace: 'kn',
keyword: 'k',
string: 's',
number: 'm',
'attr-name': 'na',
builtin: 'nb',
entity: 'ni',
function: 'nf',
tag: 'nt',
variable: 'nv',
});
export default Prism;
<template>
<div class="pdf-viewer" v-if="hasPDF">
<page v-for="(page, index) in pages"
:key="index"
:v-if="!loading"
:page="page"
:number="index + 1" />
</div>
</template>
<script>
import pdfjsLib from 'pdfjs-dist';
import workerSrc from 'vendor/pdf.worker';
import page from './page/index.vue';
export default {
props: {
pdf: {
type: [String, Uint8Array],
required: true,
},
},
data() {
return {
loading: false,
pages: [],
};
},
components: { page },
watch: { pdf: 'load' },
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
hasPDF() {
return this.pdf && this.pdf.length > 0;
},
},
methods: {
load() {
this.pages = [];
return pdfjsLib.getDocument(this.document)
.then(this.renderPages)
.then(() => this.$emit('pdflabload'))
.catch(error => this.$emit('pdflaberror', error))
.then(() => { this.loading = false; });
},
renderPages(pdf) {
const pagePromises = [];
this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
pagePromises.push(
pdf.getPage(num).then(p => this.pages.push(p)),
);
}
return Promise.all(pagePromises);
},
},
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
};
</script>
<style>
.pdf-viewer {
background: url('./assets/img/bg.gif');
display: flex;
flex-flow: column nowrap;
}
</style>
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<script>
export default {
props: {
page: {
type: Object,
required: true,
},
number: {
type: Number,
required: true,
},
},
data() {
return {
scale: 4,
rendering: false,
};
},
computed: {
viewport() {
return this.page.getViewport(this.scale);
},
context() {
return this.$refs.canvas.getContext('2d');
},
renderContext() {
return {
canvasContext: this.context,
viewport: this.viewport,
};
},
},
mounted() {
this.$refs.canvas.height = this.viewport.height;
this.$refs.canvas.width = this.viewport.width;
this.rendering = true;
this.page.render(this.renderContext)
.then(() => { this.rendering = false; })
.catch(error => this.$emit('pdflaberror', error));
},
};
</script>
<style>
.pdf-page {
margin: 8px auto 0 auto;
border-top: 1px #ddd solid;
border-bottom: 1px #ddd solid;
width: 100%;
}
.pdf-page:first-child {
margin-top: 0px;
border-top: 0px;
}
.pdf-page:last-child {
margin-bottom: 0px;
border-bottom: 0px;
}
</style>
......@@ -13,7 +13,7 @@ export default {
</script>
<template>
<div class="row empty-state">
<div class="row empty-state js-empty-state">
<div class="col-xs-12">
<div class="svg-content" v-html="pipelinesEmptyStateSVG" />
</div>
......
......@@ -2,13 +2,6 @@
import StatusIconEntityMap from '../../ci_status_icons';
export default {
data() {
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
};
},
props: {
stage: {
type: Object,
......@@ -16,6 +9,13 @@ export default {
},
},
data() {
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
};
},
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
......@@ -31,7 +31,13 @@ export default {
return this.$http.get(this.stage.dropdown_path)
.then((response) => {
this.builds = JSON.parse(response.body).html;
}, () => {
})
.catch(() => {
// If dropdown is opened we'll close it.
if (this.$el.classList.contains('open')) {
$(this.$refs.dropdown).dropdown('toggle');
}
const flash = new Flash('Something went wrong on our end.');
return flash;
});
......@@ -46,9 +52,10 @@ export default {
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
e.stopPropagation();
});
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
.on('click', (e) => {
e.stopPropagation();
});
},
},
computed: {
......@@ -81,12 +88,22 @@ export default {
data-placement="top"
data-toggle="dropdown"
type="button"
:aria-label="stage.title">
<span v-html="svgHTML" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
:aria-label="stage.title"
ref="dropdown">
<span
v-html="svgHTML"
aria-hidden="true">
</span>
<i
class="fa fa-caret-down"
aria-hidden="true" />
</button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div>
<ul
ref="dropdown-content"
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div
class="arrow-up"
aria-hidden="true"></div>
<div
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
......
......@@ -2,68 +2,95 @@ import iconTimerSvg from 'icons/_icon_timer.svg';
import '../../lib/utils/datetime_utility';
export default {
props: {
finishedTime: {
type: String,
required: true,
},
duration: {
type: Number,
required: true,
},
},
data() {
return {
currentTime: new Date(),
iconTimerSvg,
};
},
props: ['pipeline'],
updated() {
$(this.$refs.tooltip).tooltip('fixTitle');
},
computed: {
timeAgo() {
return gl.utils.getTimeago();
hasDuration() {
return this.duration > 0;
},
localTimeFinished() {
return gl.utils.formatDate(this.pipeline.details.finished_at);
hasFinishedTime() {
return this.finishedTime !== '';
},
timeStopped() {
const changeTime = this.currentTime;
const options = {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric',
};
options.timeZoneName = 'short';
const finished = this.pipeline.details.finished_at;
if (!finished && changeTime) return false;
return ({ words: this.timeAgo.format(finished) });
localTimeFinished() {
return gl.utils.formatDate(this.finishedTime);
},
duration() {
const { duration } = this.pipeline.details;
const date = new Date(duration * 1000);
durationFormated() {
const date = new Date(this.duration * 1000);
let hh = date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
if (hh < 10) hh = `0${hh}`;
if (mm < 10) mm = `0${mm}`;
if (ss < 10) ss = `0${ss}`;
// left pad
if (hh < 10) {
hh = `0${hh}`;
}
if (mm < 10) {
mm = `0${mm}`;
}
if (ss < 10) {
ss = `0${ss}`;
}
if (duration !== null) return `${hh}:${mm}:${ss}`;
return false;
return `${hh}:${mm}:${ss}`;
},
},
methods: {
changeTime() {
this.currentTime = new Date();
finishedTimeFormated() {
const timeAgo = gl.utils.getTimeago();
return timeAgo.format(this.finishedTime);
},
},
template: `
<td class="pipelines-time-ago">
<p class="duration" v-if='duration'>
<span v-html="iconTimerSvg"></span>
{{duration}}
<p
class="duration"
v-if="hasDuration">
<span
v-html="iconTimerSvg">
</span>
{{durationFormated}}
</p>
<p class="finished-at" v-if='timeStopped'>
<i class="fa fa-calendar"></i>
<p
class="finished-at"
v-if="hasFinishedTime">
<i
class="fa fa-calendar"
aria-hidden="true" />
<time
ref="tooltip"
data-toggle="tooltip"
data-placement="top"
data-container="body"
:data-original-title='localTimeFinished'>
{{timeStopped.words}}
:title="localTimeFinished">
{{finishedTimeFormated}}
</time>
</p>
</td>
......
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
......@@ -161,15 +160,6 @@ export default {
eventHub.$on('refreshPipelines', this.fetchPipelines);
},
beforeUpdate() {
if (this.state.pipelines.length &&
this.$children &&
!this.isMakingRequest &&
!this.isLoading) {
this.store.startTimeAgoLoops.call(this, Vue);
}
},
beforeDestroyed() {
eventHub.$off('refreshPipelines');
},
......
/* eslint-disable no-underscore-dangle*/
import VueRealtimeListener from '../../vue_realtime_listener';
export default class PipelinesStore {
constructor() {
this.state = {};
......@@ -30,32 +27,4 @@ export default class PipelinesStore {
this.state.pageInfo = paginationInfo;
}
/**
* FIXME: Move this inside the component.
*
* Once the data is received we will start the time ago loops.
*
* Everytime a request is made like retry or cancel a pipeline, every 10 seconds we
* update the time to show how long as passed.
*
*/
startTimeAgoLoops() {
const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => {
const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0];
acc.push(timeAgoComponent);
return acc;
}, []).forEach(e => e.changeTime());
}, 10000);
};
startTimeLoops();
const removeIntervals = () => clearInterval(this.timeLoopInterval);
const startIntervals = () => startTimeLoops();
VueRealtimeListener(removeIntervals, startIntervals);
}
}
......@@ -57,8 +57,11 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
Shortcuts.prototype.toggleMarkdownPreview = function(e) {
// Check if short-cut was triggered while in Write Mode
if ($(e.target).hasClass('js-note-text')) {
$('.js-md-preview-button').focus();
const $target = $(e.target);
const $form = $target.closest('form');
if ($target.hasClass('js-note-text')) {
$('.js-md-preview-button', $form).focus();
}
return $(document).triggerHandler('markdown-preview:toggle', [e]);
};
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment