Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file
* upstream/master: (657 commits) Use single quote for strings Ue svg from SVGs object Dont trigger CI builds [ci skip] Revert "Test only migrations" Add custom copy for each empty stage Fetch only one revision Highlight nav item on hover Test only migrations Fix migration paths tests Scroll CA stage panel on mobile Fix CSS declaration administer to administrator Move SVGs to JS objects for easy reuse Improve deploy command message No enough data to Not enough data Keep the cookie name as before Fix variable usage Evalute time_ago method instead of printing it Removed button styling from restricted visibility levels and added checkboxes with icons Do not show overview message if there’s already CA data ...
This commit is contained in:
commit
d4d138ee70
|
@ -23,7 +23,9 @@
|
|||
"spyOn": false,
|
||||
"spyOnEvent": false,
|
||||
"Turbolinks": false,
|
||||
"window": false
|
||||
"window": false,
|
||||
"Vue": false,
|
||||
"Flash": false,
|
||||
"Cookies": false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
CHANGELOG.md merge=union
|
||||
*.js.es6 gitlab-language=javascript
|
||||
|
|
|
@ -71,11 +71,23 @@ update-knapsack:
|
|||
- mysql:latest
|
||||
- redis:alpine
|
||||
|
||||
setup-test-env:
|
||||
<<: *use-db
|
||||
stage: prepare
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
|
||||
artifacts:
|
||||
expire_in: 7d
|
||||
paths:
|
||||
- public/assets
|
||||
- tmp/tests
|
||||
|
||||
|
||||
.rspec-knapsack: &rspec-knapsack
|
||||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- JOB_NAME=( $CI_BUILD_NAME )
|
||||
- export CI_NODE_INDEX=${JOB_NAME[1]}
|
||||
- export CI_NODE_TOTAL=${JOB_NAME[2]}
|
||||
|
@ -93,7 +105,6 @@ update-knapsack:
|
|||
stage: test
|
||||
<<: *use-db
|
||||
script:
|
||||
- bundle exec rake assets:precompile 2>/dev/null
|
||||
- JOB_NAME=( $CI_BUILD_NAME )
|
||||
- export CI_NODE_INDEX=${JOB_NAME[1]}
|
||||
- export CI_NODE_TOTAL=${JOB_NAME[2]}
|
||||
|
@ -308,12 +319,11 @@ migration paths:
|
|||
- master@gitlab/gitlabhq
|
||||
- master@gitlab/gitlab-ee
|
||||
script:
|
||||
- git checkout HEAD .
|
||||
- git fetch --tags
|
||||
- git checkout v8.5.9
|
||||
- git fetch origin v8.5.9
|
||||
- git checkout -f FETCH_HEAD
|
||||
- cp config/resque.yml.example config/resque.yml
|
||||
- sed -i 's/localhost/redis/g' config/resque.yml
|
||||
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" --retry=3
|
||||
- bundle install --without postgres production --jobs $(nproc) ${FLAGS[@]} --retry=3
|
||||
- rake db:drop db:create db:schema:load db:seed_fu
|
||||
- git checkout $CI_BUILD_REF
|
||||
- source scripts/prepare_build.sh
|
||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -4,37 +4,6 @@ entry.
|
|||
|
||||
## 8.14.0 (2016-11-22)
|
||||
|
||||
- Use separate email-token for incoming email and revert back the inactive feature. !5914
|
||||
- Replace jQuery.timeago with timeago.js. !6274 (ClemMakesApps)
|
||||
- Add CI notifications. Who triggered a pipeline would receive an email after the pipeline is succeeded or failed. Users could also update notification settings accordingly. !6342
|
||||
- Finer-grained Git gargage collection. !6588
|
||||
- Introduce better credential and error checking to `rake gitlab:ldap:check`. !6601
|
||||
- Process commits using a dedicated Sidekiq worker. !6802
|
||||
- Fix showing pipeline status for a given commit from correct branch. !7034
|
||||
- Add query param to filter users by external & blocked type. !7109 (Yatish Mehta)
|
||||
- Issues atom feed url reflect filters on dashboard. !7114 (Lucas Deschamps)
|
||||
- Add setting to only allow merge requests to be merged when all discussions are resolved. !7125 (Rodolfo Arruda)
|
||||
- Remove an extra leading space from diff paste data. !7133 (Hiroyuki Sato)
|
||||
- Fix 404 on network page when entering non-existent git revision. !7172 (Hiroyuki Sato)
|
||||
- Rewrite git blame spinach feature tests to rspec feature tests. !7197 (Lisanne Fellinger)
|
||||
- Only skip group when it's actually a group in the "Share with group" select. !7262
|
||||
- Introduce round-robin project creation to spread load over multiple shards. !7266
|
||||
- Ensure merge request's "remove branch" accessors return booleans. !7267
|
||||
- Expose label IDs in API. !7275 (Rares Sfirlogea)
|
||||
- Fix invalid filename validation on eslint. !7281
|
||||
- API: Ability to retrieve version information. !7286 (Robert Schilling)
|
||||
- Set default Sidekiq retries to 3. !7294
|
||||
- Return 400 when creating a system hook fails. !7350 (Robert Schilling)
|
||||
- Use the Gitlab Workhorse HTTP header in the admin dashboard. (Chris Wright)
|
||||
- Add an index for project_id in project_import_data to improve performance.
|
||||
- Fix broken link to observatory cli on Frontend Dev Guide. (Sam Rose)
|
||||
- Faster search inside Project.
|
||||
- Clicking "force remove source branch" label now toggles the checkbox again.
|
||||
- Allow to test JIRA service settings without having a repository.
|
||||
- Fix: Guest sees some repository details and gets 404.
|
||||
- Bump omniauth-gitlab to 1.0.2 to fix incompatibility with omniauth-oauth2.
|
||||
- Fix: Todos Filter Shows All Users.
|
||||
- Fix broken commits search.
|
||||
- Show correct environment log in admin/logs (@duk3luk3 !7191)
|
||||
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
|
||||
- Diff collapse won't shift when collapsing.
|
||||
|
@ -63,6 +32,7 @@ entry.
|
|||
- Fix sidekiq stats in admin area (blackst0ne)
|
||||
- Added label description as tooltip to issue board list title
|
||||
- Created cycle analytics bundle JavaScript file
|
||||
- Make the milestone page more responsive (yury-n)
|
||||
- Hides container registry when repository is disabled
|
||||
- API: Fix booleans not recognized as such when using the `to_boolean` helper
|
||||
- Removed delete branch tooltip !6954
|
||||
|
@ -104,8 +74,22 @@ entry.
|
|||
- Fix applying GitHub-imported labels when importing job is interrupted
|
||||
- Allow to search for user by secondary email address in the admin interface(/admin/users) !7115 (YarNayar)
|
||||
- Updated commit SHA styling on the branches page.
|
||||
- Fix "Without projects" filter. !6611 (Ben Bodenmiller)
|
||||
- Fix 404 when visit /projects page
|
||||
|
||||
## 8.13.6 (2016-11-17)
|
||||
|
||||
- Omniauth auto link LDAP user falls back to find by DN when user cannot be found by UID. !7002
|
||||
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option. !7117
|
||||
- Fix relative links in Markdown wiki when displayed in "Project" tab. !7218
|
||||
- Fix no "Register" tab if ldap auth is enabled (#24038). !7274 (Luc Didry)
|
||||
- Fix cache for commit status in commits list to respect branches. !7372
|
||||
- Fix issue causing Labels not to appear in sidebar on MR page. !7416 (Alex Sanford)
|
||||
- Limit labels returned for a specific project as an administrator. !7496
|
||||
- Clicking "force remove source branch" label now toggles the checkbox again.
|
||||
- Allow commit note to be visible if repo is visible.
|
||||
- Fix project Visibility Level selector not using default values.
|
||||
|
||||
## 8.13.5 (2016-11-08)
|
||||
|
||||
- Restore unauthenticated access to public container registries
|
||||
|
@ -133,7 +117,6 @@ entry.
|
|||
|
||||
- Removes any symlinks before importing a project export file. CVE-2016-9086
|
||||
- Fixed Import/Export foreign key issue to do with project members.
|
||||
- Fix relative links in Markdown wiki when displayed in "Project" tab !7218
|
||||
- Changed build dropdown list length to be 6,5 builds long in the pipeline graph
|
||||
|
||||
## 8.13.2 (2016-10-31)
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
- [Helping others](#helping-others)
|
||||
- [I want to contribute!](#i-want-to-contribute)
|
||||
- [Implement design & UI elements](#implement-design-ui-elements)
|
||||
- [Design reference](#design-reference)
|
||||
- [UI development kit](#ui-development-kit)
|
||||
- [Issue tracker](#issue-tracker)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
||||
|
@ -90,7 +88,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
|
|||
|
||||
## Implement design & UI elements
|
||||
|
||||
Please see the [UI Guide for building GitLab].
|
||||
Please see the [UX Guide for GitLab].
|
||||
|
||||
## Issue tracker
|
||||
|
||||
|
@ -218,7 +216,10 @@ associated with in the description of the issue.
|
|||
We welcome merge requests with fixes and improvements to GitLab code, tests,
|
||||
and/or documentation. The features we would really like a merge request for are
|
||||
listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
|
||||
and [EE][accepting-mrs-ee] but other improvements are also welcome.
|
||||
and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note
|
||||
that if an issue is marked for the current milestone either before or while you
|
||||
are working on it, a team member may take over the merge request in order to
|
||||
ensure the work is finished before the release date.
|
||||
|
||||
If you want to add a new feature that is not labeled it is best to first create
|
||||
a feedback issue (if there isn't one already) and leave a comment asking for it
|
||||
|
@ -469,5 +470,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
|
||||
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
|
||||
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
|
||||
[UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md
|
||||
[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
|
||||
[license-finder-doc]: doc/development/licensing.md
|
||||
|
|
5
Gemfile
5
Gemfile
|
@ -330,13 +330,10 @@ gem 'octokit', '~> 4.3.0'
|
|||
gem 'mail_room', '~> 0.9.0'
|
||||
|
||||
gem 'email_reply_parser', '~> 0.5.8'
|
||||
gem 'html2text'
|
||||
|
||||
gem 'ruby-prof', '~> 0.16.2'
|
||||
|
||||
## CI
|
||||
gem 'activerecord-session_store', '~> 1.0.0'
|
||||
gem 'nested_form', '~> 0.3.2'
|
||||
|
||||
# OAuth
|
||||
gem 'oauth2', '~> 1.2.0'
|
||||
|
||||
|
|
14
Gemfile.lock
14
Gemfile.lock
|
@ -32,12 +32,6 @@ GEM
|
|||
activemodel (= 4.2.7.1)
|
||||
activesupport (= 4.2.7.1)
|
||||
arel (~> 6.0)
|
||||
activerecord-session_store (1.0.0)
|
||||
actionpack (>= 4.0, < 5.1)
|
||||
activerecord (>= 4.0, < 5.1)
|
||||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 1.5.2, < 3)
|
||||
railties (>= 4.0, < 5.1)
|
||||
activerecord_sane_schema_dumper (0.2)
|
||||
rails (>= 4, < 5)
|
||||
activesupport (4.2.7.1)
|
||||
|
@ -345,6 +339,8 @@ GEM
|
|||
html-pipeline (1.11.0)
|
||||
activesupport (>= 2)
|
||||
nokogiri (~> 1.4)
|
||||
html2text (0.2.0)
|
||||
nokogiri (~> 1.6)
|
||||
htmlentities (4.3.4)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
|
@ -416,7 +412,6 @@ GEM
|
|||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
mysql2 (0.3.20)
|
||||
nested_form (0.3.2)
|
||||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
newrelic_rpm (3.16.0.318)
|
||||
|
@ -598,7 +593,7 @@ GEM
|
|||
railties (>= 4.2.0, < 5.1)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (2.0.6)
|
||||
rouge (2.0.7)
|
||||
rqrcode (0.7.0)
|
||||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
|
@ -809,7 +804,6 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
RedCloth (~> 4.3.2)
|
||||
ace-rails-ap (~> 4.1.0)
|
||||
activerecord-session_store (~> 1.0.0)
|
||||
activerecord_sane_schema_dumper (= 0.2)
|
||||
acts-as-taggable-on (~> 4.0)
|
||||
addressable (~> 2.3.8)
|
||||
|
@ -881,6 +875,7 @@ DEPENDENCIES
|
|||
health_check (~> 2.2.0)
|
||||
hipchat (~> 1.5.0)
|
||||
html-pipeline (~> 1.11.0)
|
||||
html2text
|
||||
httparty (~> 0.13.3)
|
||||
influxdb (~> 0.2)
|
||||
jira-ruby (~> 1.1.2)
|
||||
|
@ -901,7 +896,6 @@ DEPENDENCIES
|
|||
minitest (~> 5.7.0)
|
||||
mousetrap-rails (~> 1.4.6)
|
||||
mysql2 (~> 0.3.16)
|
||||
nested_form (~> 0.3.2)
|
||||
net-ssh (~> 3.0.1)
|
||||
newrelic_rpm (~> 3.16)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
/* eslint-disable */
|
||||
(function() {
|
||||
this.Activities = (function() {
|
||||
function Activities() {
|
||||
Pager.init(20, true, false, this.updateTooltips);
|
||||
$(".event-filter-link").on("click", (function(_this) {
|
||||
return function(event) {
|
||||
event.preventDefault();
|
||||
_this.toggleFilter($(event.currentTarget));
|
||||
return _this.reloadActivities();
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
Activities.prototype.updateTooltips = function() {
|
||||
gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
|
||||
};
|
||||
|
||||
Activities.prototype.reloadActivities = function() {
|
||||
$(".content_list").html('');
|
||||
Pager.init(20, true, false, this.updateTooltips);
|
||||
};
|
||||
|
||||
Activities.prototype.toggleFilter = function(sender) {
|
||||
var filter = sender.attr("id").split("_")[0];
|
||||
|
||||
$('.event-filter .active').removeClass("active");
|
||||
Cookies.set("event_filter", filter);
|
||||
|
||||
sender.closest('li').toggleClass("active");
|
||||
};
|
||||
|
||||
return Activities;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
|
@ -0,0 +1,36 @@
|
|||
/* eslint-disable no-param-reassign, class-methods-use-this */
|
||||
/* global Pager, Cookies */
|
||||
|
||||
((global) => {
|
||||
class Activities {
|
||||
constructor() {
|
||||
Pager.init(20, true, false, this.updateTooltips);
|
||||
$('.event-filter-link').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.toggleFilter(e.currentTarget);
|
||||
this.reloadActivities();
|
||||
});
|
||||
}
|
||||
|
||||
updateTooltips() {
|
||||
gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
|
||||
}
|
||||
|
||||
reloadActivities() {
|
||||
$('.content_list').html('');
|
||||
Pager.init(20, true, false, this.updateTooltips);
|
||||
}
|
||||
|
||||
toggleFilter(sender) {
|
||||
const $sender = $(sender);
|
||||
const filter = $sender.attr('id').split('_')[0];
|
||||
|
||||
$('.event-filter .active').removeClass('active');
|
||||
Cookies.set('event_filter', filter);
|
||||
|
||||
$sender.closest('li').toggleClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
global.Activities = Activities;
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, no-undef, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Admin = (function() {
|
||||
function Admin() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, no-undef, comma-dangle, prefer-arrow-callback, indent, object-curly-spacing, quote-props, no-param-reassign, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Api = {
|
||||
groupsPath: "/api/:version/groups.json",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, no-undef, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len */
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// Add new JavaScript code in separate files in this directory and they'll automatically
|
||||
// be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
|
@ -53,6 +53,7 @@
|
|||
/*= require_directory ./u2f */
|
||||
/*= require_directory . */
|
||||
/*= require fuzzaldrin-plus */
|
||||
/*= require es6-promise.auto */
|
||||
|
||||
(function () {
|
||||
document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Aside = (function() {
|
||||
function Aside() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Autosave = (function() {
|
||||
function Autosave(field, key) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, spaced-comment, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, radix, keyword-spacing, space-before-blocks, brace-style, no-underscore-dangle, no-undef, no-plusplus, no-return-assign, camelcase, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.AwardsHandler = (function() {
|
||||
var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, no-undef, padded-blocks, max-len */
|
||||
|
||||
/*= require jquery.ba-resize */
|
||||
/*= require autosize */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, padded-blocks, max-len */
|
||||
(function() {
|
||||
$(function() {
|
||||
$("body").on("click", ".js-details-target", function() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-undef, prefer-arrow-callback, camelcase, max-len, consistent-return, quotes, object-shorthand, comma-dangle, padded-blocks, max-len */
|
||||
// Quick Submit behavior
|
||||
//
|
||||
// When a child field of a form with a `js-quick-submit` class receives a
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, padded-blocks, max-len */
|
||||
// Requires Input behavior
|
||||
//
|
||||
// When called on a form with input fields with the `required` attribute, the
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
|
||||
(function(w) {
|
||||
$(function() {
|
||||
// Toggle button. Show/hide content inside parent container.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, no-undef, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.BlobFileDropzone = (function() {
|
||||
function BlobFileDropzone(form, method) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, no-undef, padded-blocks, max-len */
|
||||
|
||||
/*= require blob/template_selector */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, no-undef, comma-dangle, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.BlobGitignoreSelectors = (function() {
|
||||
function BlobGitignoreSelectors(opts) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle, no-undef, padded-blocks, max-len */
|
||||
|
||||
/*= require blob/template_selector */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-undef, no-new, padded-blocks, max-len */
|
||||
/*= require_tree . */
|
||||
|
||||
(function() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, no-undef, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, padded-blocks, max-len */
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable wrap-iife, func-names, strict, indent, no-tabs, no-var, vars-on-top, no-param-reassign, object-shorthand, no-shadow, comma-dangle, prefer-template, consistent-return, no-mixed-operators, no-unused-vars, object-curly-spacing, no-unused-expressions, prefer-arrow-callback, max-len */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, padded-blocks, no-return-assign, new-parens, no-param-reassign, no-undef, max-len */
|
||||
(function() {
|
||||
this.Breakpoints = (function() {
|
||||
var BreakpointInstance, instance;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, padded-blocks, max-len */
|
||||
(function() {
|
||||
$(function() {
|
||||
var previewPath;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, no-undef, quotes, yoda, no-else-return, consistent-return, comma-dangle, semi, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top, padded-blocks, max-len */
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
@ -172,7 +172,7 @@
|
|||
$date = $('.js-artifacts-remove');
|
||||
if ($date.length) {
|
||||
date = $date.text();
|
||||
return $date.text(gl.utils.timefor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
|
||||
return $date.text(gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.BuildArtifacts = (function() {
|
||||
function BuildArtifacts() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-undef, padded-blocks */
|
||||
(function() {
|
||||
this.Commit = (function() {
|
||||
function Commit() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, no-undef, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.CommitFile = (function() {
|
||||
function CommitFile(file) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.ImageFile = (function() {
|
||||
var prepareFrames;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.CommitsList = (function() {
|
||||
function CommitsList() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Compare = (function() {
|
||||
function Compare(opts) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.ConfirmDangerModal = (function() {
|
||||
function ConfirmDangerModal(form, text) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-undef, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, padded-blocks, max-len */
|
||||
|
||||
/*= require clipboard */
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StageCodeComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="mergeRequest in items" class="stage-event-item">
|
||||
<div class="item-details">
|
||||
<img class="avatar" :src="mergeRequest.author.avatarUrl">
|
||||
<h5 class="item-title merge-merquest-title">
|
||||
<a :href="mergeRequest.url">
|
||||
{{ mergeRequest.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
|
||||
·
|
||||
<span>
|
||||
Opened
|
||||
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
|
||||
</span>
|
||||
<span>
|
||||
by
|
||||
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="mergeRequest.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,45 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StageIssueComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="issue in items" class="stage-event-item">
|
||||
<div class="item-details">
|
||||
<img class="avatar" :src="issue.author.avatarUrl">
|
||||
<h5 class="item-title issue-title">
|
||||
<a class="issue-title" :href="issue.url">
|
||||
{{ issue.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
|
||||
·
|
||||
<span>
|
||||
Opened
|
||||
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
|
||||
</span>
|
||||
<span>
|
||||
by
|
||||
<a :href="issue.author.webUrl" class="issue-author-link">
|
||||
{{ issue.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="issue.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,42 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StagePlanComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="commit in items" class="stage-event-item">
|
||||
<div class="item-details item-conmmit-component">
|
||||
<img class="avatar" :src="commit.author.avatarUrl">
|
||||
<h5 class="item-title commit-title">
|
||||
<a :href="commit.commitUrl">
|
||||
{{ commit.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<span>
|
||||
First
|
||||
<span class="commit-icon">${global.cycleAnalytics.svgs.iconCommit}</span>
|
||||
<a :href="commit.commitUrl" class="commit-hash-link monospace">{{ commit.shortSha }}</a>
|
||||
pushed by
|
||||
<a :href="commit.author.webUrl" class="commit-author-link">
|
||||
{{ commit.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="commit.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,45 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StageProductionComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="issue in items" class="stage-event-item">
|
||||
<div class="item-details">
|
||||
<img class="avatar" :src="issue.author.avatarUrl">
|
||||
<h5 class="item-title issue-title">
|
||||
<a class="issue-title" :href="issue.url">
|
||||
{{ issue.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
|
||||
·
|
||||
<span>
|
||||
Opened
|
||||
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
|
||||
</span>
|
||||
<span>
|
||||
by
|
||||
<a :href="issue.author.webUrl" class="issue-author-link">
|
||||
{{ issue.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="issue.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,55 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StageReviewComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="mergeRequest in items" class="stage-event-item">
|
||||
<div class="item-details">
|
||||
<img class="avatar" :src="mergeRequest.author.avatarUrl">
|
||||
<h5 class="item-title merge-merquest-title">
|
||||
<a :href="mergeRequest.url">
|
||||
{{ mergeRequest.title }}
|
||||
</a>
|
||||
</h5>
|
||||
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
|
||||
·
|
||||
<span>
|
||||
Opened
|
||||
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
|
||||
</span>
|
||||
<span>
|
||||
by
|
||||
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
|
||||
</span>
|
||||
<template v-if="mergeRequest.state === 'closed'">
|
||||
<span class="merge-request-state">
|
||||
<i class="fa fa-ban"></i>
|
||||
{{ mergeRequest.state.toUpperCase() }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="merge-request-branch" v-if="mergeRequest.branch">
|
||||
<i class= "fa fa-code-fork"></i>
|
||||
<a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="mergeRequest.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,42 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StageStagingComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="build in items" class="stage-event-item item-build-component">
|
||||
<div class="item-details">
|
||||
<img class="avatar" :src="build.author.avatarUrl">
|
||||
<h5 class="item-title">
|
||||
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
|
||||
<i class="fa fa-code-fork"></i>
|
||||
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
|
||||
<span class="icon-branch">${global.cycleAnalytics.svgs.iconBranch}</span>
|
||||
<a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
|
||||
</h5>
|
||||
<span>
|
||||
<a :href="build.url" class="build-date">{{ build.date }}</a>
|
||||
by
|
||||
<a :href="build.author.webUrl" class="issue-author-link">
|
||||
{{ build.author.name }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="build.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,42 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.StageTestComponent = Vue.extend({
|
||||
props: {
|
||||
items: Array,
|
||||
stage: Object,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="events-description">
|
||||
{{ stage.description }}
|
||||
</div>
|
||||
<ul class="stage-event-list">
|
||||
<li v-for="build in items" class="stage-event-item item-build-component">
|
||||
<div class="item-details">
|
||||
<h5 class="item-title">
|
||||
<span class="icon-build-status">${global.cycleAnalytics.svgs.iconBuildStatus}</span>
|
||||
<a :href="build.url" class="item-build-name">{{ build.name }}</a>
|
||||
·
|
||||
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
|
||||
<i class="fa fa-code-fork"></i>
|
||||
<a :href="build.branch.url" class="branch-name monospace">{{ build.branch.name }}</a>
|
||||
<span class="icon-branch">${global.cycleAnalytics.svgs.iconBranch}</span>
|
||||
<a :href="build.commitUrl" class="short-sha monospace">{{ build.shortSha }}</a>
|
||||
</h5>
|
||||
<span>
|
||||
<a :href="build.url" class="issue-date">
|
||||
{{ build.date }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-time">
|
||||
<total-time :time="build.totalTime"></total-time>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,18 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
global.cycleAnalytics.TotalTimeComponent = Vue.extend({
|
||||
props: {
|
||||
time: Object,
|
||||
},
|
||||
template: `
|
||||
<span class="total-time">
|
||||
<template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template>
|
||||
<template v-if="time.hours">{{ time.hours }} <span>hr</span></template>
|
||||
<template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template>
|
||||
<template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template>
|
||||
</span>
|
||||
`,
|
||||
});
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,98 +1,121 @@
|
|||
/* eslint-disable */
|
||||
//= require vue
|
||||
//= require_tree ./svg
|
||||
//= require_tree .
|
||||
|
||||
((global) => {
|
||||
$(() => {
|
||||
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
|
||||
const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
|
||||
const cycleAnalyticsStore = gl.cycleAnalytics.CycleAnalyticsStore;
|
||||
const cycleAnalyticsService = new gl.cycleAnalytics.CycleAnalyticsService({
|
||||
requestPath: cycleAnalyticsEl.dataset.requestPath,
|
||||
});
|
||||
|
||||
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
|
||||
const store = gl.cycleAnalyticsStore = {
|
||||
isLoading: true,
|
||||
hasError: false,
|
||||
isHelpDismissed: Cookies.get(COOKIE_NAME),
|
||||
analytics: {}
|
||||
};
|
||||
gl.cycleAnalyticsApp = new Vue({
|
||||
el: '#cycle-analytics',
|
||||
name: 'CycleAnalytics',
|
||||
data: {
|
||||
state: cycleAnalyticsStore.state,
|
||||
isLoading: false,
|
||||
isLoadingStage: false,
|
||||
isEmptyStage: false,
|
||||
hasError: false,
|
||||
startDate: 30,
|
||||
isOverviewDialogDismissed: Cookies.get(OVERVIEW_DIALOG_COOKIE),
|
||||
},
|
||||
computed: {
|
||||
currentStage() {
|
||||
return cycleAnalyticsStore.currentActiveStage();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'stage-issue-component': gl.cycleAnalytics.StageIssueComponent,
|
||||
'stage-plan-component': gl.cycleAnalytics.StagePlanComponent,
|
||||
'stage-code-component': gl.cycleAnalytics.StageCodeComponent,
|
||||
'stage-test-component': gl.cycleAnalytics.StageTestComponent,
|
||||
'stage-review-component': gl.cycleAnalytics.StageReviewComponent,
|
||||
'stage-staging-component': gl.cycleAnalytics.StageStagingComponent,
|
||||
'stage-production-component': gl.cycleAnalytics.StageProductionComponent,
|
||||
},
|
||||
created() {
|
||||
this.fetchCycleAnalyticsData();
|
||||
},
|
||||
methods: {
|
||||
handleError() {
|
||||
cycleAnalyticsStore.setErrorState(true);
|
||||
return new Flash('There was an error while fetching cycle analytics data.');
|
||||
},
|
||||
initDropdown() {
|
||||
const $dropdown = $('.js-ca-dropdown');
|
||||
const $label = $dropdown.find('.dropdown-label');
|
||||
|
||||
gl.CycleAnalytics = class CycleAnalytics {
|
||||
constructor() {
|
||||
const that = this;
|
||||
$dropdown.find('li a').off('click').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const $target = $(e.currentTarget);
|
||||
this.startDate = $target.data('value');
|
||||
|
||||
this.vue = new Vue({
|
||||
el: '#cycle-analytics',
|
||||
name: 'CycleAnalytics',
|
||||
created: this.fetchData(),
|
||||
data: store,
|
||||
methods: {
|
||||
dismissLanding() {
|
||||
that.dismissLanding();
|
||||
}
|
||||
$label.text($target.text().trim());
|
||||
this.fetchCycleAnalyticsData({ startDate: this.startDate });
|
||||
});
|
||||
},
|
||||
fetchCycleAnalyticsData(options) {
|
||||
const fetchOptions = options || { startDate: this.startDate };
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
cycleAnalyticsService
|
||||
.fetchCycleAnalyticsData(fetchOptions)
|
||||
.done((response) => {
|
||||
cycleAnalyticsStore.setCycleAnalyticsData(response);
|
||||
this.selectDefaultStage();
|
||||
this.initDropdown();
|
||||
})
|
||||
.error(() => {
|
||||
this.handleError();
|
||||
})
|
||||
.always(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
selectDefaultStage() {
|
||||
const stage = this.state.stages.first();
|
||||
this.selectStage(stage);
|
||||
},
|
||||
selectStage(stage) {
|
||||
if (this.isLoadingStage) return;
|
||||
if (this.currentStage === stage) return;
|
||||
|
||||
if (!stage.isUserAllowed) {
|
||||
cycleAnalyticsStore.setActiveStage(stage);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetchData(options) {
|
||||
store.isLoading = true;
|
||||
options = options || { startDate: 30 };
|
||||
this.isLoadingStage = true;
|
||||
cycleAnalyticsStore.setStageEvents([]);
|
||||
cycleAnalyticsStore.setActiveStage(stage);
|
||||
|
||||
$.ajax({
|
||||
url: $('#cycle-analytics').data('request-path'),
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: {
|
||||
cycle_analytics: {
|
||||
start_date: options.startDate
|
||||
}
|
||||
}
|
||||
}).done((data) => {
|
||||
this.decorateData(data);
|
||||
this.initDropdown();
|
||||
})
|
||||
.error((data) => {
|
||||
this.handleError(data);
|
||||
})
|
||||
.always(() => {
|
||||
store.isLoading = false;
|
||||
})
|
||||
}
|
||||
cycleAnalyticsService
|
||||
.fetchStageData({
|
||||
stage,
|
||||
startDate: this.startDate,
|
||||
})
|
||||
.done((response) => {
|
||||
this.isEmptyStage = !response.events.length;
|
||||
cycleAnalyticsStore.setStageEvents(response.events);
|
||||
})
|
||||
.error(() => {
|
||||
this.isEmptyStage = true;
|
||||
})
|
||||
.always(() => {
|
||||
this.isLoadingStage = false;
|
||||
});
|
||||
},
|
||||
dismissOverviewDialog() {
|
||||
this.isOverviewDialogDismissed = true;
|
||||
Cookies.set(OVERVIEW_DIALOG_COOKIE, '1');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
decorateData(data) {
|
||||
data.summary = data.summary || [];
|
||||
data.stats = data.stats || [];
|
||||
|
||||
data.summary.forEach((item) => {
|
||||
item.value = item.value || '-';
|
||||
});
|
||||
|
||||
data.stats.forEach((item) => {
|
||||
item.value = item.value || '- - -';
|
||||
});
|
||||
|
||||
store.analytics = data;
|
||||
}
|
||||
|
||||
handleError(data) {
|
||||
store.hasError = true;
|
||||
new Flash('There was an error while fetching cycle analytics data.', 'alert');
|
||||
}
|
||||
|
||||
dismissLanding() {
|
||||
store.isHelpDismissed = true;
|
||||
Cookies.set(COOKIE_NAME, true);
|
||||
}
|
||||
|
||||
initDropdown() {
|
||||
const $dropdown = $('.js-ca-dropdown');
|
||||
const $label = $dropdown.find('.dropdown-label');
|
||||
|
||||
$dropdown.find('li a').off('click').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const $target = $(e.currentTarget);
|
||||
const value = $target.data('value');
|
||||
|
||||
$label.text($target.text().trim());
|
||||
this.fetchData({ startDate: value });
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
||||
// Register global components
|
||||
Vue.component('total-time', gl.cycleAnalytics.TotalTimeComponent);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
class CycleAnalyticsService {
|
||||
constructor(options) {
|
||||
this.requestPath = options.requestPath;
|
||||
}
|
||||
|
||||
fetchCycleAnalyticsData(options) {
|
||||
options = options || { startDate: 30 };
|
||||
|
||||
return $.ajax({
|
||||
url: this.requestPath,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: {
|
||||
cycle_analytics: {
|
||||
start_date: options.startDate,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fetchStageData(options) {
|
||||
const {
|
||||
stage,
|
||||
startDate,
|
||||
} = options;
|
||||
|
||||
return $.get(`${this.requestPath}/events/${stage.title.toLowerCase()}.json`, {
|
||||
cycle_analytics: {
|
||||
start_date: startDate,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,90 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
|
||||
const EMPTY_STAGE_TEXTS = {
|
||||
issue: 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
|
||||
plan: 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
|
||||
code: 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
|
||||
test: 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
|
||||
review: 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
|
||||
staging: 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
|
||||
production: 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
|
||||
};
|
||||
|
||||
global.cycleAnalytics.CycleAnalyticsStore = {
|
||||
state: {
|
||||
summary: '',
|
||||
stats: '',
|
||||
analytics: '',
|
||||
events: [],
|
||||
stages: [],
|
||||
},
|
||||
setCycleAnalyticsData(data) {
|
||||
this.state = Object.assign(this.state, this.decorateData(data));
|
||||
},
|
||||
decorateData(data) {
|
||||
const newData = {};
|
||||
|
||||
newData.stages = data.stats || [];
|
||||
newData.summary = data.summary || [];
|
||||
|
||||
newData.summary.forEach((item) => {
|
||||
item.value = item.value || '-';
|
||||
});
|
||||
|
||||
newData.stages.forEach((item) => {
|
||||
const stageName = item.title.toLowerCase();
|
||||
item.active = false;
|
||||
item.isUserAllowed = data.permissions[stageName];
|
||||
item.emptyStageText = EMPTY_STAGE_TEXTS[stageName];
|
||||
item.component = `stage-${stageName}-component`;
|
||||
});
|
||||
newData.analytics = data;
|
||||
return newData;
|
||||
},
|
||||
setLoadingState(state) {
|
||||
this.state.isLoading = state;
|
||||
},
|
||||
setErrorState(state) {
|
||||
this.state.hasError = state;
|
||||
},
|
||||
deactivateAllStages() {
|
||||
this.state.stages.forEach((stage) => {
|
||||
stage.active = false;
|
||||
});
|
||||
},
|
||||
setActiveStage(stage) {
|
||||
this.deactivateAllStages();
|
||||
stage.active = true;
|
||||
},
|
||||
setStageEvents(events) {
|
||||
this.state.events = this.decorateEvents(events);
|
||||
},
|
||||
decorateEvents(events) {
|
||||
const newEvents = events;
|
||||
|
||||
newEvents.forEach((item) => {
|
||||
item.totalTime = item.total_time;
|
||||
item.author.webUrl = item.author.web_url;
|
||||
item.author.avatarUrl = item.author.avatar_url;
|
||||
|
||||
if (item.created_at) item.createdAt = item.created_at;
|
||||
if (item.short_sha) item.shortSha = item.short_sha;
|
||||
if (item.commit_url) item.commitUrl = item.commit_url;
|
||||
|
||||
delete item.author.web_url;
|
||||
delete item.author.avatar_url;
|
||||
delete item.total_time;
|
||||
delete item.created_at;
|
||||
delete item.short_sha;
|
||||
delete item.commit_url;
|
||||
});
|
||||
|
||||
return newEvents;
|
||||
},
|
||||
currentActiveStage() {
|
||||
return this.state.stages.find(stage => stage.active);
|
||||
},
|
||||
};
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {};
|
||||
|
||||
global.cycleAnalytics.svgs.iconBranch = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><path fill="#8C8C8C" fill-rule="evenodd" d="M9.678 6.722C9.353 5.167 8.053 4 6.5 4S3.647 5.167 3.322 6.722h-2.6c-.397 0-.722.35-.722.778 0 .428.325.778.722.778h2.6C3.647 9.833 4.947 11 6.5 11s2.853-1.167 3.178-2.722h2.6c.397 0 .722-.35.722-.778 0-.428-.325-.778-.722-.778h-2.6zM4.694 7.5c0-1.09.795-1.944 1.806-1.944 1.01 0 1.806.855 1.806 1.944 0 1.09-.795 1.944-1.806 1.944-1.01 0-1.806-.855-1.806-1.944z"/></svg>';
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {};
|
||||
|
||||
global.cycleAnalytics.svgs.iconBuildStatus = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"><g fill="#31AF64" fill-rule="evenodd"><path d="M12.5 7c0-3.038-2.462-5.5-5.5-5.5S1.5 3.962 1.5 7s2.462 5.5 5.5 5.5 5.5-2.462 5.5-5.5zM0 7c0-3.866 3.134-7 7-7s7 3.134 7 7-3.134 7-7 7-7-3.134-7-7z"/><path d="M6.28 7.697L5.045 6.464c-.117-.117-.305-.117-.42-.002l-.614.614c-.11.113-.11.303.007.42l1.91 1.91c.19.19.51.197.703.004l.264-.265L9.997 6.04c.108-.107.107-.293-.01-.408l-.612-.614c-.114-.113-.298-.12-.41-.01L6.28 7.7z"/></g></svg>';
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
((global) => {
|
||||
global.cycleAnalytics = global.cycleAnalytics || {};
|
||||
global.cycleAnalytics.svgs = global.cycleAnalytics.svgs || {};
|
||||
|
||||
global.cycleAnalytics.svgs.iconCommit = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path fill="#8F8F8F" fill-rule="evenodd" d="M28.777 18c-.91-4.008-4.494-7-8.777-7-4.283 0-7.868 2.992-8.777 7H4.01C2.9 18 2 18.895 2 20c0 1.112.9 2 2.01 2h7.213c.91 4.008 4.494 7 8.777 7 4.283 0 7.868-2.992 8.777-7h7.214C37.1 22 38 21.105 38 20c0-1.112-.9-2-2.01-2h-7.213zM20 25c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z"/></svg>';
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, max-len, one-var, camelcase, one-var-declaration-per-line, no-unused-vars, no-unused-expressions, no-sequences, object-shorthand, comma-dangle, prefer-arrow-callback, semi, radix, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Diff = (function() {
|
||||
var UNFOLD_COUNT;
|
||||
|
|
|
@ -110,10 +110,10 @@
|
|||
Issuable.init();
|
||||
break;
|
||||
case 'dashboard:activity':
|
||||
new Activities();
|
||||
new gl.Activities();
|
||||
break;
|
||||
case 'dashboard:projects:starred':
|
||||
new Activities();
|
||||
new gl.Activities();
|
||||
break;
|
||||
case 'projects:commit:show':
|
||||
new Commit();
|
||||
|
@ -139,7 +139,7 @@
|
|||
new gl.Pipelines();
|
||||
break;
|
||||
case 'groups:activity':
|
||||
new Activities();
|
||||
new gl.Activities();
|
||||
break;
|
||||
case 'groups:show':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
|
@ -208,9 +208,6 @@
|
|||
new gl.ProtectedBranchCreate();
|
||||
new gl.ProtectedBranchEditList();
|
||||
break;
|
||||
case 'projects:cycle_analytics:show':
|
||||
new gl.CycleAnalytics();
|
||||
break;
|
||||
}
|
||||
switch (path.first()) {
|
||||
case 'admin':
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, no-undef, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, no-plusplus, prefer-arrow-callback, padded-blocks, max-len */
|
||||
|
||||
/*= require preview_markdown */
|
||||
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
//= require vue
|
||||
//= require vue-resource
|
||||
//= require_tree ../services/
|
||||
//= require ./environment_item
|
||||
|
||||
/* globals Vue, EnvironmentsService */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
(() => { // eslint-disable-line
|
||||
window.gl = window.gl || {};
|
||||
|
||||
/**
|
||||
* Given the visibility prop provided by the url query parameter and which
|
||||
* changes according to the active tab we need to filter which environments
|
||||
* should be visible.
|
||||
*
|
||||
* The environments array is a recursive tree structure and we need to filter
|
||||
* both root level environments and children environments.
|
||||
*
|
||||
* In order to acomplish that, both `filterState` and `filterEnvironmnetsByState`
|
||||
* functions work together.
|
||||
* The first one works as the filter that verifies if the given environment matches
|
||||
* the given state.
|
||||
* The second guarantees both root level and children elements are filtered as well.
|
||||
*/
|
||||
|
||||
const filterState = state => environment => environment.state === state && environment;
|
||||
/**
|
||||
* Given the filter function and the array of environments will return only
|
||||
* the environments that match the state provided to the filter function.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Array} array
|
||||
* @return {Array}
|
||||
*/
|
||||
const filterEnvironmnetsByState = (fn, arr) => arr.map((item) => {
|
||||
if (item.children) {
|
||||
const filteredChildren = filterEnvironmnetsByState(fn, item.children).filter(Boolean);
|
||||
if (filteredChildren.length) {
|
||||
item.children = filteredChildren;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return fn(item);
|
||||
}).filter(Boolean);
|
||||
|
||||
window.gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', {
|
||||
props: {
|
||||
store: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
'environment-item': window.gl.environmentsList.EnvironmentItem,
|
||||
},
|
||||
|
||||
data() {
|
||||
const environmentsData = document.querySelector('#environments-list-view').dataset;
|
||||
|
||||
return {
|
||||
state: this.store.state,
|
||||
visibility: 'available',
|
||||
isLoading: false,
|
||||
cssContainerClass: environmentsData.cssClass,
|
||||
endpoint: environmentsData.environmentsDataEndpoint,
|
||||
canCreateDeployment: environmentsData.canCreateDeployment,
|
||||
canReadEnvironment: environmentsData.canReadEnvironment,
|
||||
canCreateEnvironment: environmentsData.canCreateEnvironment,
|
||||
projectEnvironmentsPath: environmentsData.projectEnvironmentsPath,
|
||||
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
|
||||
newEnvironmentPath: environmentsData.newEnvironmentPath,
|
||||
helpPagePath: environmentsData.helpPagePath,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredEnvironments() {
|
||||
return filterEnvironmnetsByState(filterState(this.visibility), this.state.environments);
|
||||
},
|
||||
|
||||
scope() {
|
||||
return this.$options.getQueryParameter('scope');
|
||||
},
|
||||
|
||||
canReadEnvironmentParsed() {
|
||||
return this.$options.convertPermissionToBoolean(this.canReadEnvironment);
|
||||
},
|
||||
|
||||
canCreateDeploymentParsed() {
|
||||
return this.$options.convertPermissionToBoolean(this.canCreateDeployment);
|
||||
},
|
||||
|
||||
canCreateEnvironmentParsed() {
|
||||
return this.$options.convertPermissionToBoolean(this.canCreateEnvironment);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the environmnets and stores them.
|
||||
* Toggles loading property.
|
||||
*/
|
||||
created() {
|
||||
gl.environmentsService = new EnvironmentsService(this.endpoint);
|
||||
|
||||
const scope = this.$options.getQueryParameter('scope');
|
||||
if (scope) {
|
||||
this.visibility = scope;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
return gl.environmentsService.all()
|
||||
.then(resp => resp.json())
|
||||
.then((json) => {
|
||||
this.store.storeEnvironments(json);
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Transforms the url parameter into an object and
|
||||
* returns the one requested.
|
||||
*
|
||||
* @param {String} param
|
||||
* @returns {String} The value of the requested parameter.
|
||||
*/
|
||||
getQueryParameter(parameter) {
|
||||
return window.location.search.substring(1).split('&').reduce((acc, param) => {
|
||||
const paramSplited = param.split('=');
|
||||
acc[paramSplited[0]] = paramSplited[1];
|
||||
return acc;
|
||||
}, {})[parameter];
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts permission provided as strings to booleans.
|
||||
* @param {String} string
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
convertPermissionToBoolean(string) {
|
||||
return string === 'true';
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleRow(model) {
|
||||
return this.store.toggleFolder(model.name);
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<div :class="cssContainerClass">
|
||||
<div class="top-area">
|
||||
<ul v-if="!isLoading" class="nav-links">
|
||||
<li v-bind:class="{ 'active': scope === undefined }">
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="environments-container">
|
||||
<div class="environments-list-loading text-center" v-if="isLoading">
|
||||
<i class="fa fa-spinner spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="blank-state blank-state-no-icon"
|
||||
v-if="!isLoading && state.environments.length === 0">
|
||||
<h2 class="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">
|
||||
New Environment
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-holder"
|
||||
v-if="!isLoading && state.environments.length > 0">
|
||||
<table class="table ci-table environments">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Environment</th>
|
||||
<th>Last deployment</th>
|
||||
<th>Build</th>
|
||||
<th>Commit</th>
|
||||
<th></th>
|
||||
<th class="hidden-xs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="model in filteredEnvironments"
|
||||
v-bind:model="model">
|
||||
|
||||
<tr
|
||||
is="environment-item"
|
||||
:model="model"
|
||||
:toggleRow="toggleRow.bind(model)"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed"></tr>
|
||||
|
||||
<tr v-if="model.isOpen && model.children && model.children.length > 0"
|
||||
is="environment-item"
|
||||
v-for="children in model.children"
|
||||
:model="children"
|
||||
:toggleRow="toggleRow.bind(children)"
|
||||
:can-create-deployment="canCreateDeploymentParsed"
|
||||
:can-read-environment="canReadEnvironmentParsed">
|
||||
</tr>
|
||||
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,67 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
||||
window.gl.environmentsList.ActionsComponent = Vue.component('actions-component', {
|
||||
props: {
|
||||
actions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Appends the svg icon that were render in the index page.
|
||||
* In order to reuse the svg instead of copy and paste in this template
|
||||
* we need to render it outside this component using =custom_icon partial.
|
||||
*
|
||||
* TODO: Remove this when webpack is merged.
|
||||
*
|
||||
*/
|
||||
mounted() {
|
||||
const playIcon = document.querySelector('.play-icon-svg.hidden svg');
|
||||
|
||||
const dropdownContainer = this.$el.querySelector('.dropdown-play-icon-container');
|
||||
const actionContainers = this.$el.querySelectorAll('.action-play-icon-container');
|
||||
// Phantomjs does not have support to iterate a nodelist.
|
||||
const actionsArray = [].slice.call(actionContainers);
|
||||
|
||||
if (playIcon && actionsArray && dropdownContainer) {
|
||||
dropdownContainer.appendChild(playIcon.cloneNode(true));
|
||||
|
||||
actionsArray.forEach((element) => {
|
||||
element.appendChild(playIcon.cloneNode(true));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inline">
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-new btn btn-default" data-toggle="dropdown">
|
||||
<span class="dropdown-play-icon-container"></span>
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-align-right">
|
||||
<li v-for="action in actions">
|
||||
<a :href="action.play_path"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
class="js-manual-action-link">
|
||||
<span class="action-play-icon-container"></span>
|
||||
<span>
|
||||
{{action.name}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,22 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
||||
window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
|
||||
props: {
|
||||
external_url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn external_url" :href="external_url" target="_blank">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,496 @@
|
|||
/*= require lib/utils/timeago */
|
||||
/*= require lib/utils/text_utility */
|
||||
/*= require vue_common_component/commit */
|
||||
/*= require ./environment_actions */
|
||||
/*= require ./environment_external_url */
|
||||
/*= require ./environment_stop */
|
||||
/*= require ./environment_rollback */
|
||||
|
||||
/* globals Vue, timeago */
|
||||
|
||||
(() => {
|
||||
/**
|
||||
* Envrionment Item Component
|
||||
*
|
||||
* Used in a hierarchical structure to show folders with children
|
||||
* in a table.
|
||||
* Recursive component based on [Tree View](https://vuejs.org/examples/tree-view.html)
|
||||
*
|
||||
* See this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/22539)
|
||||
* for more information.15
|
||||
*/
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
||||
gl.environmentsList.EnvironmentItem = Vue.component('environment-item', {
|
||||
|
||||
components: {
|
||||
'commit-component': window.gl.CommitComponent,
|
||||
'actions-component': window.gl.environmentsList.ActionsComponent,
|
||||
'external-url-component': window.gl.environmentsList.ExternalUrlComponent,
|
||||
'stop-component': window.gl.environmentsList.StopComponent,
|
||||
'rollback-component': window.gl.environmentsList.RollbackComponent,
|
||||
},
|
||||
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
toggleRow: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
|
||||
canCreateDeployment: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
rowClass: {
|
||||
'children-row': this.model['vue-isChildren'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
/**
|
||||
* If an item has a `children` entry it means it is a folder.
|
||||
* Folder items have different behaviours - it is possible to toggle
|
||||
* them and show their children.
|
||||
*
|
||||
* @returns {Boolean|Undefined}
|
||||
*/
|
||||
isFolder() {
|
||||
return this.model.children && this.model.children.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* If an item is inside a folder structure will return true.
|
||||
* Used for css purposes.
|
||||
*
|
||||
* @returns {Boolean|undefined}
|
||||
*/
|
||||
isChildren() {
|
||||
return this.model['vue-isChildren'];
|
||||
},
|
||||
|
||||
/**
|
||||
* Counts the number of environments in each folder.
|
||||
* Used to show a badge with the counter.
|
||||
*
|
||||
* @returns {Number|Undefined} The number of environments for the current folder.
|
||||
*/
|
||||
childrenCounter() {
|
||||
return this.model.children && this.model.children.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if `last_deployment` key exists in the current Envrionment.
|
||||
* This key is required to render most of the html - this method works has
|
||||
* an helper.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasLastDeploymentKey() {
|
||||
if (this.model.last_deployment &&
|
||||
!this.$options.isObjectEmpty(this.model.last_deployment)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies is the given environment has manual actions.
|
||||
* Used to verify if we should render them or nor.
|
||||
*
|
||||
* @returns {Boolean|Undefined}
|
||||
*/
|
||||
hasManualActions() {
|
||||
return this.model.last_deployment && this.model.last_deployment.manual_actions &&
|
||||
this.model.last_deployment.manual_actions.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the value of the `stoppable?` key provided in the response.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isStoppable() {
|
||||
return this.model['stoppable?'];
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the `deployable` key is present in `last_deployment` key.
|
||||
* Used to verify whether we should or not render the rollback partial.
|
||||
*
|
||||
* @returns {Boolean|Undefined}
|
||||
*/
|
||||
canRetry() {
|
||||
return this.hasLastDeploymentKey &&
|
||||
this.model.last_deployment &&
|
||||
this.model.last_deployment.deployable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Human readable date.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
createdDate() {
|
||||
const timeagoInstance = new timeago(); // eslint-disable-line
|
||||
|
||||
return timeagoInstance.format(this.model.created_at);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the manual actions with the name parsed.
|
||||
*
|
||||
* @returns {Array.<Object>|Undefined}
|
||||
*/
|
||||
manualActions() {
|
||||
if (this.hasManualActions) {
|
||||
return this.model.last_deployment.manual_actions.map((action) => {
|
||||
const parsedAction = {
|
||||
name: gl.text.humanize(action.name),
|
||||
play_path: action.play_path,
|
||||
};
|
||||
return parsedAction;
|
||||
});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds the string used in the user image alt attribute.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
userImageAltDescription() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.user &&
|
||||
this.model.last_deployment.user.username) {
|
||||
return `${this.model.last_deployment.user.username}'s avatar'`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit tag.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitTag() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.tag) {
|
||||
return this.model.last_deployment.tag;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit ref.
|
||||
*
|
||||
* @returns {Object|Undefined}
|
||||
*/
|
||||
commitRef() {
|
||||
if (this.model.last_deployment && this.model.last_deployment.ref) {
|
||||
return this.model.last_deployment.ref;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit url.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitUrl() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.commit &&
|
||||
this.model.last_deployment.commit.commit_path) {
|
||||
return this.model.last_deployment.commit.commit_path;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit short sha.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitShortSha() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.commit &&
|
||||
this.model.last_deployment.commit.short_id) {
|
||||
return this.model.last_deployment.commit.short_id;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit title.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitTitle() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.commit &&
|
||||
this.model.last_deployment.commit.title) {
|
||||
return this.model.last_deployment.commit.title;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit tag.
|
||||
*
|
||||
* @returns {Object|Undefined}
|
||||
*/
|
||||
commitAuthor() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.commit &&
|
||||
this.model.last_deployment.commit.author) {
|
||||
return this.model.last_deployment.commit.author;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the `retry_path` key is present and returns its value.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
retryUrl() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.deployable &&
|
||||
this.model.last_deployment.deployable.retry_path) {
|
||||
return this.model.last_deployment.deployable.retry_path;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the `last?` key is present and returns its value.
|
||||
*
|
||||
* @returns {Boolean|Undefined}
|
||||
*/
|
||||
isLastDeployment() {
|
||||
return this.model.last_deployment && this.model.last_deployment['last?'];
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds the name of the builds needed to display both the name and the id.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
buildName() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.deployable) {
|
||||
return `${this.model.last_deployment.deployable.name} #${this.model.last_deployment.deployable.id}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Builds the needed string to show the internal id.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
deploymentInternalId() {
|
||||
if (this.model.last_deployment &&
|
||||
this.model.last_deployment.iid) {
|
||||
return `#${this.model.last_deployment.iid}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the user object is present under last_deployment object.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
deploymentHasUser() {
|
||||
return !this.$options.isObjectEmpty(this.model.last_deployment) &&
|
||||
!this.$options.isObjectEmpty(this.model.last_deployment.user);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the user object nested with the last_deployment object.
|
||||
* Used to render the template.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
deploymentUser() {
|
||||
if (!this.$options.isObjectEmpty(this.model.last_deployment) &&
|
||||
!this.$options.isObjectEmpty(this.model.last_deployment.user)) {
|
||||
return this.model.last_deployment.user;
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if the build name column should be rendered by verifing
|
||||
* if all the information needed is present
|
||||
* and if the environment is not a folder.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
shouldRenderBuildName() {
|
||||
return !this.isFolder &&
|
||||
!this.$options.isObjectEmpty(this.model.last_deployment) &&
|
||||
!this.$options.isObjectEmpty(this.model.last_deployment.deployable);
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies if deplyment internal ID should be rendered by verifing
|
||||
* if all the information needed is present
|
||||
* and if the environment is not a folder.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
shouldRenderDeploymentID() {
|
||||
return !this.isFolder &&
|
||||
!this.$options.isObjectEmpty(this.model.last_deployment) &&
|
||||
this.model.last_deployment.iid !== undefined;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to verify if certain given object are empty.
|
||||
* Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty
|
||||
* @param {Object} object
|
||||
* @returns {Bollean}
|
||||
*/
|
||||
isObjectEmpty(object) {
|
||||
for (const key in object) { // eslint-disable-line
|
||||
if (hasOwnProperty.call(object, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
template: `
|
||||
<tr>
|
||||
<td v-bind:class="{ 'children-row': isChildren}">
|
||||
<a v-if="!isFolder"
|
||||
class="environment-name"
|
||||
:href="model.environment_path">
|
||||
{{model.name}}
|
||||
</a>
|
||||
<span v-else v-on:click="toggleRow(model)" class="folder-name">
|
||||
<span class="folder-icon">
|
||||
<i v-show="model.isOpen" class="fa fa-caret-down"></i>
|
||||
<i v-show="!model.isOpen" class="fa fa-caret-right"></i>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{{model.name}}
|
||||
</span>
|
||||
|
||||
<span class="badge">
|
||||
{{childrenCounter}}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="deployment-column">
|
||||
<span v-if="shouldRenderDeploymentID">
|
||||
{{deploymentInternalId}}
|
||||
</span>
|
||||
|
||||
<span v-if="!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>
|
||||
<a v-if="shouldRenderBuildName"
|
||||
class="build-link"
|
||||
:href="model.last_deployment.deployable.build_path">
|
||||
{{buildName}}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component">
|
||||
<commit-component
|
||||
:tag="commitTag"
|
||||
:ref="commitRef"
|
||||
:commit_url="commitUrl"
|
||||
:short_sha="commitShortSha"
|
||||
:title="commitTitle"
|
||||
:author="commitAuthor">
|
||||
</commit-component>
|
||||
</div>
|
||||
<p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title">
|
||||
No deployments yet
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span
|
||||
v-if="!isFolder && model.last_deployment"
|
||||
class="environment-created-date-timeago">
|
||||
{{createdDate}}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="hidden-xs">
|
||||
<div v-if="!isFolder">
|
||||
<div v-if="hasManualActions && canCreateDeployment"
|
||||
class="inline js-manual-actions-container">
|
||||
<actions-component
|
||||
:actions="manualActions">
|
||||
</actions-component>
|
||||
</div>
|
||||
|
||||
<div v-if="model.external_url && canReadEnvironment"
|
||||
class="inline js-external-url-container">
|
||||
<external-url-component
|
||||
:external_url="model.external_url">
|
||||
</external_url-component>
|
||||
</div>
|
||||
|
||||
<div v-if="isStoppable && canCreateDeployment"
|
||||
class="inline js-stop-component-container">
|
||||
<stop-component
|
||||
:stop_url="model.stop_path">
|
||||
</stop-component>
|
||||
</div>
|
||||
|
||||
<div v-if="canRetry && canCreateDeployment"
|
||||
class="inline js-rollback-component-container">
|
||||
<rollback-component
|
||||
:is_last_deployment="isLastDeployment"
|
||||
:retry_url="retryUrl">
|
||||
</rollback-component>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,31 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
||||
window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
|
||||
props: {
|
||||
retry_url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
is_last_deployment: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn" :href="retry_url" data-method="post" rel="nofollow">
|
||||
<span v-if="is_last_deployment">
|
||||
Re-deploy
|
||||
</span>
|
||||
<span v-else>
|
||||
Rollback
|
||||
</span>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,26 @@
|
|||
/*= require vue */
|
||||
/* global Vue */
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
||||
window.gl.environmentsList.StopComponent = Vue.component('stop-component', {
|
||||
props: {
|
||||
stop_url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
template: `
|
||||
<a class="btn stop-env-link"
|
||||
:href="stop_url"
|
||||
data-confirm="Are you sure you want to stop this environment?"
|
||||
data-method="post"
|
||||
rel="nofollow">
|
||||
<i class="fa fa-stop stop-env-icon"></i>
|
||||
</a>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,21 @@
|
|||
//= require vue
|
||||
//= require_tree ./stores/
|
||||
//= require ./components/environment
|
||||
//= require ./vue_resource_interceptor
|
||||
|
||||
|
||||
$(() => {
|
||||
window.gl = window.gl || {};
|
||||
|
||||
if (window.gl.EnvironmentsListApp) {
|
||||
window.gl.EnvironmentsListApp.$destroy(true);
|
||||
}
|
||||
const Store = window.gl.environmentsList.EnvironmentsStore;
|
||||
|
||||
window.gl.EnvironmentsListApp = new window.gl.environmentsList.EnvironmentsComponent({
|
||||
el: document.querySelector('#environments-list-view'),
|
||||
propsData: {
|
||||
store: Store.create(),
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/* globals Vue */
|
||||
/* eslint-disable no-unused-vars, no-param-reassign */
|
||||
class EnvironmentsService {
|
||||
|
||||
constructor(root) {
|
||||
Vue.http.options.root = root;
|
||||
|
||||
this.environments = Vue.resource(root);
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
// needed in order to not break the tests.
|
||||
if ($.rails) {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.environments.get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.environmentsList = window.gl.environmentsList || {};
|
||||
|
||||
gl.environmentsList.EnvironmentsStore = {
|
||||
state: {},
|
||||
|
||||
create() {
|
||||
this.state.environments = [];
|
||||
this.state.stoppedCounter = 0;
|
||||
this.state.availableCounter = 0;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* In order to display a tree view we need to modify the received
|
||||
* data in to a tree structure based on `environment_type`
|
||||
* sorted alphabetically.
|
||||
* In each children a `vue-` property will be added. This property will be
|
||||
* used to know if an item is a children mostly for css purposes. This is
|
||||
* needed because the children row is a fragment instance and therfore does
|
||||
* not accept non-prop attributes.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* it will transform this:
|
||||
* [
|
||||
* { name: "environment", environment_type: "review" },
|
||||
* { name: "environment_1", environment_type: null }
|
||||
* { name: "environment_2, environment_type: "review" }
|
||||
* ]
|
||||
* into this:
|
||||
* [
|
||||
* { name: "review", children:
|
||||
* [
|
||||
* { name: "environment", environment_type: "review", vue-isChildren: true},
|
||||
* { name: "environment_2", environment_type: "review", vue-isChildren: true}
|
||||
* ]
|
||||
* },
|
||||
* {name: "environment_1", environment_type: null}
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* @param {Array} environments List of environments.
|
||||
* @returns {Array} Tree structured array with the received environments.
|
||||
*/
|
||||
storeEnvironments(environments = []) {
|
||||
this.state.stoppedCounter = this.countByState(environments, 'stopped');
|
||||
this.state.availableCounter = this.countByState(environments, 'available');
|
||||
|
||||
const environmentsTree = environments.reduce((acc, environment) => {
|
||||
if (environment.environment_type !== null) {
|
||||
const occurs = acc.filter(element => element.children &&
|
||||
element.name === environment.environment_type);
|
||||
|
||||
environment['vue-isChildren'] = true;
|
||||
|
||||
if (occurs.length) {
|
||||
acc[acc.indexOf(occurs[0])].children.push(environment);
|
||||
acc[acc.indexOf(occurs[0])].children.sort(this.sortByName);
|
||||
} else {
|
||||
acc.push({
|
||||
name: environment.environment_type,
|
||||
children: [environment],
|
||||
isOpen: false,
|
||||
'vue-isChildren': environment['vue-isChildren'],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
acc.push(environment);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []).sort(this.sortByName);
|
||||
|
||||
this.state.environments = environmentsTree;
|
||||
|
||||
return environmentsTree;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles folder open property given the environment type.
|
||||
*
|
||||
* @param {String} envType
|
||||
* @return {Array}
|
||||
*/
|
||||
toggleFolder(envType) {
|
||||
const environments = this.state.environments;
|
||||
|
||||
const environmentsCopy = environments.map((env) => {
|
||||
if (env['vue-isChildren'] && env.name === envType) {
|
||||
env.isOpen = !env.isOpen;
|
||||
}
|
||||
|
||||
return env;
|
||||
});
|
||||
|
||||
this.state.environments = environmentsCopy;
|
||||
|
||||
return environmentsCopy;
|
||||
},
|
||||
|
||||
/**
|
||||
* Given an array of environments, returns the number of environments
|
||||
* that have the given state.
|
||||
*
|
||||
* @param {Array} environments
|
||||
* @param {String} state
|
||||
* @returns {Number}
|
||||
*/
|
||||
countByState(environments, state) {
|
||||
return environments.filter(env => env.state === state).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sorts the two objects provided by their name.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
sortByName(a, b) {
|
||||
const nameA = a.name.toUpperCase();
|
||||
const nameB = b.name.toUpperCase();
|
||||
|
||||
return nameA < nameB ? -1 : nameA > nameB ? 1 : 0; // eslint-disable-line
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -0,0 +1,12 @@
|
|||
/* global Vue */
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
|
||||
|
||||
next((response) => {
|
||||
if (typeof response.data === 'string') {
|
||||
response.data = JSON.parse(response.data); // eslint-disable-line
|
||||
}
|
||||
|
||||
Vue.activeResources--; // eslint-disable-line
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */
|
||||
Array.prototype.first = function() {
|
||||
return this[0];
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, padded-blocks, max-len */
|
||||
// Disable an element and add the 'disabled' Bootstrap class
|
||||
(function() {
|
||||
$.fn.extend({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, padded-blocks, consistent-return, no-undef, max-len */
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.Flash = (function() {
|
||||
var hideFlash;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
// Team Members
|
||||
Members: {
|
||||
template: '<li>${username} <small>${title}</small></li>'
|
||||
template: '<li>${avatarTag} ${username} <small>${title}</small></li>'
|
||||
},
|
||||
Labels: {
|
||||
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
|
||||
|
@ -35,7 +35,7 @@
|
|||
DefaultOptions: {
|
||||
sorter: function(query, items, searchKey) {
|
||||
// Highlight first item only if at least one char was typed
|
||||
this.setting.highlightFirst = query.length > 0;
|
||||
this.setting.highlightFirst = this.setting.alwaysHighlightFirst || query.length > 0;
|
||||
if ((items[0].name != null) && items[0].name === 'loading') {
|
||||
return items;
|
||||
}
|
||||
|
@ -112,13 +112,14 @@
|
|||
insertTpl: '${atwho-at}${username}',
|
||||
searchKey: 'search',
|
||||
data: ['loading'],
|
||||
alwaysHighlightFirst: true,
|
||||
callbacks: {
|
||||
sorter: this.DefaultOptions.sorter,
|
||||
filter: this.DefaultOptions.filter,
|
||||
beforeInsert: this.DefaultOptions.beforeInsert,
|
||||
beforeSave: function(members) {
|
||||
return $.map(members, function(m) {
|
||||
var title;
|
||||
let title = '';
|
||||
if (m.username == null) {
|
||||
return m;
|
||||
}
|
||||
|
@ -126,8 +127,14 @@
|
|||
if (m.count) {
|
||||
title += " (" + m.count + ")";
|
||||
}
|
||||
|
||||
const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase();
|
||||
const imgAvatar = `<img src="${m.avatar_url}" alt="${m.username}" class="avatar avatar-inline center s26"/>`;
|
||||
const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`;
|
||||
|
||||
return {
|
||||
username: m.username,
|
||||
avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar,
|
||||
title: gl.utils.sanitize(title),
|
||||
search: gl.utils.sanitize(m.username + " " + m.name)
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, space-before-blocks, prefer-rest-params, max-len, vars-on-top, no-plusplus, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, semi, no-return-assign, no-else-return, camelcase, no-undef, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, padded-blocks, prefer-template, no-param-reassign, no-loop-func, no-extra-semi, keyword-spacing, no-mixed-operators, max-len */
|
||||
(function() {
|
||||
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
|
||||
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
|
@ -249,7 +249,7 @@
|
|||
_this.fullData = data;
|
||||
_this.parseData(_this.fullData);
|
||||
_this.focusTextInput();
|
||||
if (_this.options.filterable && _this.filter && _this.filter.input) {
|
||||
if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val().trim() !== '') {
|
||||
return _this.filter.input.trigger('input');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-undef, no-new, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.GLForm = (function() {
|
||||
function GLForm(form) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren */
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// Add new JavaScript code in separate files in this directory and they'll automatically
|
||||
// be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.StatGraph = (function() {
|
||||
function StatGraph() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, no-undef, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, padded-blocks, max-len */
|
||||
|
||||
/*= require d3 */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, space-before-blocks, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, padded-blocks, no-undef, newline-per-chained-call, no-else-return, max-len */
|
||||
|
||||
/*= require d3 */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, no-plusplus, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, padded-blocks, max-len */
|
||||
(function() {
|
||||
window.ContributorsStatGraphUtil = {
|
||||
parse_log: function(log) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.GroupAvatar = (function() {
|
||||
function GroupAvatar() {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/* eslint-disable */
|
||||
(function(global) {
|
||||
class GroupLabelSubscription {
|
||||
constructor(container) {
|
||||
const $container = $(container);
|
||||
this.$dropdown = $container.find('.dropdown');
|
||||
this.$subscribeButtons = $container.find('.js-subscribe-button');
|
||||
this.$unsubscribeButtons = $container.find('.js-unsubscribe-button');
|
||||
|
||||
this.$subscribeButtons.on('click', this.subscribe.bind(this));
|
||||
this.$unsubscribeButtons.on('click', this.unsubscribe.bind(this));
|
||||
}
|
||||
|
||||
unsubscribe(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const url = this.$unsubscribeButtons.attr('data-url');
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url
|
||||
}).done(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
this.$unsubscribeButtons.removeAttr('data-url');
|
||||
});
|
||||
}
|
||||
|
||||
subscribe(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const $btn = $(event.currentTarget);
|
||||
const url = $btn.attr('data-url');
|
||||
|
||||
this.$unsubscribeButtons.attr('data-url', url);
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url
|
||||
}).done(() => {
|
||||
this.toggleSubscriptionButtons();
|
||||
});
|
||||
}
|
||||
|
||||
toggleSubscriptionButtons() {
|
||||
this.$dropdown.toggleClass('hidden');
|
||||
this.$subscribeButtons.toggleClass('hidden');
|
||||
this.$unsubscribeButtons.toggleClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
global.GroupLabelSubscription = GroupLabelSubscription;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, no-undef, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, padded-blocks, max-len */
|
||||
(function() {
|
||||
var slice = [].slice;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable wrap-iife, func-names, space-before-function-paren, padded-blocks, prefer-arrow-callback, no-var, max-len */
|
||||
(function() {
|
||||
|
||||
$(document).on('todo:toggle', function(e, count) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, camelcase, no-var, one-var, one-var-declaration-per-line, prefer-template, quotes, object-shorthand, comma-dangle, no-unused-vars, prefer-arrow-callback, no-else-return, padded-blocks, vars-on-top, no-new, no-undef, max-len */
|
||||
(function() {
|
||||
this.ImporterStatus = (function() {
|
||||
function ImporterStatus(jobs_url, import_url) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, no-undef, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.IssuableContext = (function() {
|
||||
function IssuableContext(currentUser) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-undef, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, radix, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, padded-blocks, max-len */
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-undef, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, padded-blocks, max-len */
|
||||
|
||||
/*= require flash */
|
||||
/*= require jquery.waitforimages */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.IssueStatusSelect = (function() {
|
||||
function IssueStatusSelect() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, padded-blocks, max-len */
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */
|
||||
(function() {
|
||||
this.LabelsSelect = (function() {
|
||||
function LabelsSelect() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, indent, vars-on-top, padded-blocks, max-len */
|
||||
(function() {
|
||||
var hideEndFade;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren */
|
||||
|
||||
/*= require Chart */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren */
|
||||
|
||||
/*= require cropper */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren */
|
||||
|
||||
/*= require d3 */
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren */
|
||||
|
||||
/*= require raphael */
|
||||
/*= require g.raphael */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, padded-blocks, max-len */
|
||||
(function() {
|
||||
(function(w) {
|
||||
if (w.gl == null) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len */
|
||||
(function() {
|
||||
(function(w) {
|
||||
var base;
|
||||
|
@ -125,6 +125,11 @@
|
|||
// Close any open tooltips
|
||||
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
|
||||
};
|
||||
|
||||
gl.utils.isMetaKey = function(e) {
|
||||
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
|
||||
};
|
||||
|
||||
})(window);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, no-undef, comma-dangle, no-unused-expressions, prefer-template, padded-blocks, max-len */
|
||||
(function() {
|
||||
(function(w) {
|
||||
var base;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, no-undef, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, max-len */
|
||||
(function() {
|
||||
(function(w) {
|
||||
var notificationGranted, notifyMe, notifyPermissions;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
(() => {
|
||||
/*
|
||||
* TODO: Make these methods more configurable (e.g. parseSeconds timePeriodContstraints,
|
||||
* stringifyTime condensed or non-condensed, abbreviateTimelengths)
|
||||
* */
|
||||
|
||||
class PrettyTime {
|
||||
|
||||
/*
|
||||
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
|
||||
* Seconds can be negative or positive, zero or non-zero.
|
||||
*/
|
||||
static parseSeconds(seconds) {
|
||||
const DAYS_PER_WEEK = 5;
|
||||
const HOURS_PER_DAY = 8;
|
||||
const MINUTES_PER_HOUR = 60;
|
||||
const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
|
||||
const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
|
||||
|
||||
const timePeriodConstraints = {
|
||||
weeks: MINUTES_PER_WEEK,
|
||||
days: MINUTES_PER_DAY,
|
||||
hours: MINUTES_PER_HOUR,
|
||||
minutes: 1,
|
||||
};
|
||||
|
||||
let unorderedMinutes = PrettyTime.secondsToMinutes(seconds);
|
||||
|
||||
return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
|
||||
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
|
||||
|
||||
unorderedMinutes -= (periodCount * minutesPerPeriod);
|
||||
|
||||
return periodCount;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Accepts a timeObject and returns a condensed string representation of it
|
||||
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
|
||||
*/
|
||||
|
||||
static stringifyTime(timeObject) {
|
||||
const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
|
||||
const isNonZero = !!unitValue;
|
||||
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
|
||||
}, '').trim();
|
||||
return reducedTime.length ? reducedTime : '0m';
|
||||
}
|
||||
|
||||
/*
|
||||
* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
|
||||
* the first non-zero unit/value pair.
|
||||
*/
|
||||
|
||||
static abbreviateTime(timeStr) {
|
||||
return timeStr.split(' ')
|
||||
.filter(unitStr => unitStr.charAt(0) !== '0')[0];
|
||||
}
|
||||
|
||||
static secondsToMinutes(seconds) {
|
||||
return Math.abs(seconds / 60);
|
||||
}
|
||||
}
|
||||
|
||||
gl.PrettyTime = PrettyTime;
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, semi, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, padded-blocks, max-len */
|
||||
(function() {
|
||||
(function(w) {
|
||||
var base;
|
||||
|
@ -112,6 +112,9 @@
|
|||
gl.text.removeListeners = function(form) {
|
||||
return $('.js-md', form).off();
|
||||
};
|
||||
gl.text.humanize = function(string) {
|
||||
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
|
||||
}
|
||||
return gl.text.truncate = function(string, maxLength) {
|
||||
return string.substr(0, (maxLength - 3)) + '...';
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable no-unused-expressions, wrap-iife, func-names, curly, no-param-reassign, no-trailing-spaces, prefer-arrow-callback, no-var, one-var, quote-props, space-before-function-paren, vars-on-top, radix, prefer-template, space-infix-ops, no-use-before-define, newline-per-chained-call, no-useless-escape, no-nested-ternary, indent, no-undef, no-plusplus, one-var-declaration-per-line, operator-assignment, consistent-return, keyword-spacing, max-len, space-unary-ops, no-shadow, no-restricted-syntax, guard-for-in, eol-last, max-len */
|
||||
|
||||
/**
|
||||
* Copyright (c) 2016 hustcc
|
||||
* License: MIT
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue