Commit 12f07300 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file

* upstream/master: (141 commits)
  Log mv_namespace parameters
  Remove header ids from University docs
  Added test that checks the correct select box is there for the LFS enabled setting.
  Simplify copy on "Create a new list" dropdown in Issue Boards
  Fix `LFS enabled` select box.
  Use Commit#author so we share logic and cache
  Move admin abuse report spinach test to rspec
  fixes non-retina shadow and browser zoom issue
  Use default `closest` if available!
  Adds polyfill for CustomEvent
  Move abuse report spinach test to rspec
  Add support of Chrome/Chromium in requirements.md
  Fixed dragging issues on issue boards
  Grapify the sidekiq metrics API
  Add nested groups support to the routing
  Correctly determine mergeability of MR with no discussions
  API: Add endpoint to delete a group share
  Add a starting date to milestones
  Update ProjectTeam#fetch_members to use project authorizations
  Update ProjectTeam#max_member_access_for_user_ids to use project authorizations
  ...
parents b82f415f 3943e632
{ {
"env": {
"browser": true,
"es6": true
},
"extends": "airbnb", "extends": "airbnb",
"globals": {
"$": false,
"_": false,
"gl": false,
"gon": false,
"jQuery": false
},
"plugins": [ "plugins": [
"filenames" "filenames"
], ],
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"] "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"]
},
"globals": {
"$": false,
"_": false,
"beforeEach": false,
"d3": false,
"define": false,
"describe": false,
"document": false,
"expect": false,
"fixture": false,
"gl": false,
"it": false,
"jQuery": false,
"Mousetrap": false,
"spyOn": false,
"spyOnEvent": false,
"Turbolinks": false,
"window": false,
"Vue": false,
"Flash": false,
"Cookies": false
} }
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
.chef .chef
.directory .directory
/.envrc /.envrc
eslint-report.html
/.gitlab_shell_secret /.gitlab_shell_secret
.idea .idea
/.rbenv-version /.rbenv-version
......
...@@ -20,7 +20,7 @@ before_script: ...@@ -20,7 +20,7 @@ before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
- cp config/gitlab.yml.example config/gitlab.yml - cp config/gitlab.yml.example config/gitlab.yml
- bundle --version - bundle --version
- '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) $FLAGS'
- retry gem install knapsack - retry gem install knapsack
- '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql' - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql'
...@@ -271,12 +271,17 @@ rake db:seed_fu: ...@@ -271,12 +271,17 @@ rake db:seed_fu:
- log/development.log - log/development.log
teaspoon: teaspoon:
cache:
paths:
- vendor/ruby
- node_modules/
stage: test stage: test
<<: *use-db <<: *use-db
script: script:
- curl --silent --location https://deb.nodesource.com/setup_6.x | bash - - curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
- apt-get install --assume-yes nodejs - apt-get install --assume-yes nodejs
- npm install --global istanbul - npm install
- npm link istanbul
- rake teaspoon - rake teaspoon
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
...@@ -323,7 +328,7 @@ migration paths: ...@@ -323,7 +328,7 @@ migration paths:
- git checkout -f FETCH_HEAD - git checkout -f FETCH_HEAD
- cp config/resque.yml.example config/resque.yml - cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' 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 - rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF - git checkout $CI_BUILD_REF
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
...@@ -344,13 +349,33 @@ coverage: ...@@ -344,13 +349,33 @@ coverage:
- coverage/index.html - coverage/index.html
- coverage/assets/ - coverage/assets/
lint-javascript: lint:javascript:
cache:
paths:
- node_modules/
stage: test stage: test
image: "node:latest" image: "node:7.1"
before_script:
- npm install
script:
- npm --silent run eslint
lint:javascript:report:
cache:
paths:
- node_modules/
stage: post-test
image: "node:7.1"
before_script: before_script:
- npm install - npm install
script: script:
- npm run eslint - find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- npm --silent run eslint-report || true # ignore exit code
artifacts:
name: eslint-report
expire_in: 31d
paths:
- eslint-report.html
# Trigger docs build # Trigger docs build
# https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process # https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process
...@@ -376,7 +401,7 @@ notify:slack: ...@@ -376,7 +401,7 @@ notify:slack:
SETUP_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
script: script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>" - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure when: on_failure
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
...@@ -390,11 +415,13 @@ pages: ...@@ -390,11 +415,13 @@ pages:
dependencies: dependencies:
- coverage - coverage
- teaspoon - teaspoon
- lint:javascript:report
script: script:
- mv public/ .public/ - mv public/ .public/
- mkdir public/ - mkdir public/
- mv coverage public/coverage-ruby - mv coverage public/coverage-ruby
- mv coverage-javascript/default/ public/coverage-javascript/ - mv coverage-javascript/default/ public/coverage-javascript/
- mv eslint-report.html public/
artifacts: artifacts:
paths: paths:
- public - public
......
...@@ -4,6 +4,131 @@ entry. ...@@ -4,6 +4,131 @@ entry.
## 8.14.0 (2016-11-22) ## 8.14.0 (2016-11-22)
- Use separate email-token for incoming email and revert back the inactive feature. !5914
- API: allow recursive tree request. !6088 (Rebeca Mendez)
- 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
- Add button to delete all merged branches. !6449 (Toon Claes)
- Finer-grained Git gargage collection. !6588
- Introduce better credential and error checking to `rake gitlab:ldap:check`. !6601
- Centralize LDAP config/filter logic. !6606
- Make system notes less intrusive. !6755
- Process commits using a dedicated Sidekiq worker. !6802
- Show random messages when the To Do list is empty. !6818 (Josep Llaneras)
- Precalculate user's authorized projects in database. !6839
- Fix record not found error on NewNoteWorker processing. !6863 (Oswaldo Ferreira)
- Show avatars in mention dropdown. !6865
- Fix expanding a collapsed diff when converting a symlink to a regular file. !6953
- Defer saving project services to the database if there are no user changes. !6958
- Omniauth auto link LDAP user falls back to find by DN when user cannot be found by UID. !7002
- Display "folders" for environments. !7015
- Make it possible to trigger builds from webhooks. !7022 (Dmitry Poray)
- Fix showing pipeline status for a given commit from correct branch. !7034
- Add link to build pipeline within individual build pages. !7082
- Add api endpoint `/groups/owned`. !7103 (Borja Aparicio)
- 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 trace patching feature - update the updated_at value. !7146
- 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)
- Add api endpoint for creating a pipeline. !7209 (Ido Leibovich)
- Allow users to subscribe to group labels. !7215
- Reduce API calls needed when importing issues and pull requests from GitHub. !7241 (Andrew Smith (EspadaV8))
- 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
- Fix no "Register" tab if ldap auth is enabled (#24038). !7274 (Luc Didry)
- Expose label IDs in API. !7275 (Rares Sfirlogea)
- Fix invalid filename validation on eslint. !7281
- API: Ability to retrieve version information. !7286 (Robert Schilling)
- Added ability to throttle Sidekiq Jobs. !7292
- Set default Sidekiq retries to 3. !7294
- Fix double event and ajax request call on MR page. !7298 (YarNayar)
- Unify anchor link format for MR diff files. !7298 (YarNayar)
- Require projects before creating milestone. !7301 (gfyoung)
- Fix error when using invalid branch name when creating a new pipeline. !7324
- Return 400 when creating a system hook fails. !7350 (Robert Schilling)
- Auto-close environment when branch is deleted. !7355
- Rework cache invalidation so only changed data is refreshed. !7360
- Navigation bar issuables counters reflects dashboard issuables counters. !7368 (Lucas Deschamps)
- Fix cache for commit status in commits list to respect branches. !7372
- fixes 500 error on project show when user is not logged in and project is still empty. !7376
- Removed gray button styling from todo buttons in sidebars. !7387
- Fix project records with invalid visibility_level values. !7391
- Use 'Forking in progress' title when appropriate. !7394 (Philip Karpiak)
- Fix error links in help index page. !7396 (Fu Xu)
- Add support for reply-by-email when the email only contains HTML. !7397
- [Fix] Extra divider issue in dropdown. !7398
- Project download buttons always show. !7405 (Philip Karpiak)
- Give search-input correct padding-right value. !7407 (Philip Karpiak)
- Remove additional padding on right-aligned items in MR widget. !7411 (Didem Acet)
- Fix issue causing Labels not to appear in sidebar on MR page. !7416 (Alex Sanford)
- Allow mail_room idle_timeout option to be configurable. !7423
- Fix misaligned buttons on admin builds page. !7424 (Didem Acet)
- Disable "Request Access" functionality by default for new projects and groups. !7425
- fix shibboleth misconfigurations resulting in authentication bypass. !7428
- Added Mattermost slash command. !7438
- Allow to connect Chat account with GitLab. !7450
- Make New Group form respect default visibility application setting. !7454 (Jacopo Beschi @jacopo-beschi)
- Fix Error 500 when creating a merge request that contains an image that was deleted and added. !7457
- Fix labels API by adding missing current_user parameter. !7458 (Francesco Coda Zabetta)
- Changed restricted visibility admin buttons to checkboxes. !7463
- Send credentials (currently for registry only) with build data to GitLab Runner. !7474
- Fix POST /internal/allowed to cope with gitlab-shell v4.0.0 project paths. !7480
- Adds es6-promise Polyfill. !7482
- Added colored labels to related MR list. !7486 (Didem Acet)
- Use setter for key instead AR callback. !7488 (Semyon Pupkov)
- Limit labels returned for a specific project as an administrator. !7496
- Change slack notification comment link. !7498 (Herbert Kagumba)
- Allow registering users whose username contains dots. !7500 (Timothy Andrew)
- Fix race condition during group deletion and remove stale records present due to this bug. !7528 (Timothy Andrew)
- Check all namespaces on validation of new username. !7537
- Pass correct tag target to post-receive hook when creating tag via UI. !7556
- Add help message for configuring Mattermost slash commands. !7558
- Fix typo in Build page JavaScript. !7563 (winniehell)
- Make job script a required configuration entry. !7566
- Fix errors happening when source branch of merge request is removed and then restored. !7568
- Fix a wrong "The build for this merge request failed" message. !7579
- Fix Margins look weird in Project page with pinned sidebar in project stats bar. !7580
- Fix regression causing bad error message to appear on Merge Request form. !7599 (Alex Sanford)
- Fix activity page endless scroll on large viewports. !7608
- Fix 404 on some group pages when name contains dot. !7614
- Do not create a new TODO when failed build is allowed to fail. !7618
- Add deployment command to ChatOps. !7619
- Fix 500 error when group name ends with git. !7630
- Fix undefined error in CI linter. !7650
- Show events per stage on Cycle Analytics page. !23449
- Add JIRA remotelinks and prevent duplicated closing messages.
- Fixed issue boards counter border when unauthorized.
- Add placeholder for the example text for custom hex color on label creation popup. (Luis Alonso Chavez Armendariz)
- Add an index for project_id in project_import_data to improve performance.
- Fix broken commits search.
- Assignee dropdown now searches author of issue or merge request.
- Clicking "force remove source branch" label now toggles the checkbox again.
- More aggressively preload on merge request and issue index pages.
- Fix broken link to observatory cli on Frontend Dev Guide. (Sam Rose)
- Fixing the issue of the project fork url giving 500 when not signed instead of being redirected to sign in page. (Cagdas Gerede)
- Fix: Guest sees some repository details and gets 404.
- Add logging for rack attack events to production.log.
- Add environment info to builds page.
- Allow commit note to be visible if repo is visible.
- Bump omniauth-gitlab to 1.0.2 to fix incompatibility with omniauth-oauth2.
- Redesign pipelines page.
- Faster search inside Project.
- Search for a filename in a project.
- Allow sorting groups in the API.
- Fix: Todos Filter Shows All Users.
- Use the Gitlab Workhorse HTTP header in the admin dashboard. (Chris Wright)
- Fixed multiple requests sent when opening dropdowns.
- Added permissions per stage to cycle analytics endpoint.
- Fix project Visibility Level selector not using default values.
- Add events per stage to cycle analytics.
- Allow to test JIRA service settings without having a repository.
- Fix JIRA references for project snippets.
- Allow enabling and disabling commit and MR events for JIRA.
- simplify url generation. (Jarka Kadlecova)
- Show correct environment log in admin/logs (@duk3luk3 !7191) - Show correct environment log in admin/logs (@duk3luk3 !7191)
- Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117 - Fix Milestone dropdown not stay selected for `Upcoming` and `No Milestone` option !7117
- Diff collapse won't shift when collapsing. - Diff collapse won't shift when collapsing.
......
8.14.0-pre 8.15.0-pre
/* eslint-disable no-param-reassign, class-methods-use-this */ /* eslint-disable no-param-reassign, class-methods-use-this */
/* global Pager, Cookies */ /* global Pager */
/* global Cookies */
((global) => { ((global) => {
class Activities { class Activities {
......
...@@ -54,6 +54,9 @@ ...@@ -54,6 +54,9 @@
mouseDown () { mouseDown () {
this.showDetail = true; this.showDetail = true;
}, },
mouseMove() {
this.showDetail = false;
},
showIssue (e) { showIssue (e) {
const targetTagName = e.target.tagName.toLowerCase(); const targetTagName = e.target.tagName.toLowerCase();
......
...@@ -94,12 +94,10 @@ ...@@ -94,12 +94,10 @@
gl.issueBoards.onStart(); gl.issueBoards.onStart();
}, },
onAdd: (e) => { onAdd: (e) => {
// Add the element back to original list to allow Vue to handle DOM updates gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
e.from.appendChild(e.item);
this.$nextTick(() => { this.$nextTick(() => {
// Update the issues once we know the element has been moved e.item.remove();
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
}); });
}, },
}); });
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
this.pageUrl = options.pageUrl; this.pageUrl = options.pageUrl;
this.buildUrl = options.buildUrl; this.buildUrl = options.buildUrl;
this.buildStatus = options.buildStatus; this.buildStatus = options.buildStatus;
this.state = options.state1; this.state = options.logState;
this.buildStage = options.buildStage; this.buildStage = options.buildStage;
this.updateDropdown = bind(this.updateDropdown, this); this.updateDropdown = bind(this.updateDropdown, this);
this.$document = $(document); this.$document = $(document);
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* global Vue */
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
/* global Vue */
/* global Cookies */
/* global Flash */
//= require vue //= require vue
//= require_tree ./svg //= require_tree ./svg
//= require_tree . //= require_tree .
......
...@@ -145,25 +145,19 @@ ...@@ -145,25 +145,19 @@
class DueDateSelectors { class DueDateSelectors {
constructor() { constructor() {
this.initMilestoneDueDate(); this.initMilestoneDatePicker();
this.initIssuableSelect(); this.initIssuableSelect();
} }
initMilestoneDueDate() { initMilestoneDatePicker() {
const $datePicker = $('.datepicker'); $('.datepicker').datepicker({
dateFormat: 'yy-mm-dd'
});
if ($datePicker.length) { $('.js-clear-due-date,.js-clear-start-date').on('click', (e) => {
const $dueDate = $('#milestone_due_date');
$datePicker.datepicker({
dateFormat: 'yy-mm-dd',
onSelect: (dateText, inst) => {
$dueDate.val(dateText);
}
}).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
}
$('.js-clear-due-date').on('click', (e) => {
e.preventDefault(); e.preventDefault();
$.datepicker._clearDate($datePicker); const datepicker = $(e.target).siblings('.datepicker');
$.datepicker._clearDate(datepicker);
}); });
} }
......
/* eslint-disable no-param-reassign */
/* global Vue */
/* global EnvironmentsService */
//= require vue //= require vue
//= require vue-resource //= require vue-resource
//= require_tree ../services/ //= require_tree ../services/
//= require ./environment_item //= require ./environment_item
/* globals Vue, EnvironmentsService */ (() => {
/* eslint-disable no-param-reassign */
(() => { // eslint-disable-line
window.gl = window.gl || {}; window.gl = window.gl || {};
/** /**
...@@ -209,12 +210,12 @@ ...@@ -209,12 +210,12 @@
<table class="table ci-table environments"> <table class="table ci-table environments">
<thead> <thead>
<tr> <tr>
<th>Environment</th> <th class="environments-name">Environment</th>
<th>Last deployment</th> <th class="environments-deploy">Last deployment</th>
<th>Build</th> <th class="environments-build">Build</th>
<th>Commit</th> <th class="environments-commit">Commit</th>
<th></th> <th class="environments-date"></th>
<th class="hidden-xs"></th> <th class="hidden-xs environments-actions"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
......
/*= require lib/utils/timeago */ /* global Vue */
/* global timeago */
/*= require timeago */
/*= require lib/utils/text_utility */ /*= require lib/utils/text_utility */
/*= require vue_common_component/commit */ /*= require vue_common_component/commit */
/*= require ./environment_actions */ /*= require ./environment_actions */
...@@ -6,8 +9,6 @@ ...@@ -6,8 +9,6 @@
/*= require ./environment_stop */ /*= require ./environment_stop */
/*= require ./environment_rollback */ /*= require ./environment_rollback */
/* globals Vue, timeago */
(() => { (() => {
/** /**
* Envrionment Item Component * Envrionment Item Component
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector; Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
Element.prototype.closest = function closest(selector, selectedElement = this) { Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
if (!selectedElement) return; if (!selectedElement) return;
return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
}; };
/**
* CustomEvent support for IE
*/
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function CustomEvent(e, params) {
const options = params || { bubbles: false, cancelable: false, detail: undefined };
const evt = document.createEvent('CustomEvent');
evt.initCustomEvent(e, options.bubbles, options.cancelable, options.detail);
return evt;
};
window.CustomEvent.prototype = window.Event.prototype;
}
/* 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 */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, padded-blocks, max-len */
/* global timeago */
/* global dateFormat */
/*= require timeago */
/*= require date.format */
(function() { (function() {
(function(w) { (function(w) {
var base; var base;
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, padded-blocks */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, padded-blocks */
/* global Turbolinks */
(function() { (function() {
Turbolinks.enableProgressBar(); Turbolinks.enableProgressBar();
......
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
MergeRequestWidget.prototype.addEventListeners = function() { MergeRequestWidget.prototype.addEventListeners = function() {
var allowedPages; var allowedPages;
allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes'];
return $(document).on('page:change.merge_request', (function(_this) { $(document).on('page:change.merge_request', (function(_this) {
return function() { return function() {
var page; var page;
page = $('body').data('page').split(':').last(); page = $('body').data('page').split(':').last();
...@@ -245,7 +245,7 @@ ...@@ -245,7 +245,7 @@
case "not_found": case "not_found":
return this.setMergeButtonClass('btn-danger'); return this.setMergeButtonClass('btn-danger');
case "running": case "running":
return this.setMergeButtonClass('btn-warning'); return this.setMergeButtonClass('btn-info');
case "success": case "success":
case "success_with_warnings": case "success_with_warnings":
return this.setMergeButtonClass('btn-create'); return this.setMergeButtonClass('btn-create');
...@@ -263,7 +263,7 @@ ...@@ -263,7 +263,7 @@
}; };
MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) {
return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class);
}; };
return MergeRequestWidget; return MergeRequestWidget;
......
...@@ -113,6 +113,7 @@ ...@@ -113,6 +113,7 @@
$(document).off("click", ".js-note-discard"); $(document).off("click", ".js-note-discard");
$(document).off("keydown", ".js-note-text"); $(document).off("keydown", ".js-note-text");
$(document).off('click', '.js-comment-resolve-button'); $(document).off('click', '.js-comment-resolve-button');
$(document).off("click", '.system-note-commit-list-toggler');
$('.note .js-task-list-container').taskList('disable'); $('.note .js-task-list-container').taskList('disable');
return $(document).off('tasklist:changed', '.note .js-task-list-container'); return $(document).off('tasklist:changed', '.note .js-task-list-container');
}; };
......
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
} }
onSubmitForm(e) { onSubmitForm(e) {
e.preventDefault();
return this.saveForm(); return this.saveForm();
} }
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, padded-blocks, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, padded-blocks, max-len */
/* global Turbolinks */
(function() { (function() {
this.TreeView = (function() { this.TreeView = (function() {
function TreeView() { function TreeView() {
......
...@@ -206,6 +206,7 @@ ...@@ -206,6 +206,7 @@
} }
}); });
} else { } else {
this.currentSelectedDate = '';
return $('.user-calendar-activities').html(''); return $('.user-calendar-activities').html('');
} }
}; };
......
...@@ -299,6 +299,10 @@ table { ...@@ -299,6 +299,10 @@ table {
.well { .well {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
hr {
border-color: $gray-darker;
}
} }
.search_box { .search_box {
......
...@@ -68,6 +68,46 @@ label { ...@@ -68,6 +68,46 @@ label {
} }
} }
.help-form .form-group {
margin-left: 0;
margin-right: 0;
.control-label {
font-weight: bold;
padding-top: 4px;
}
.form-control {
height: 29px;
background: $white-light;
font-family: $monospace_font;
}
.input-group-btn .btn {
padding: 3px $gl-btn-padding;
background-color: $gray-light;
border: 1px solid $border-color;
}
.text-block {
line-height: 0.8;
padding-top: 9px;
code {
line-height: 1.8;
}
}
@media(max-width: $screen-sm-min) {
padding: 0 $gl-padding;
.control-label,
.text-block {
padding-left: 0;
}
}
}
.fieldset-form fieldset { .fieldset-form fieldset {
margin-bottom: 20px; margin-bottom: 20px;
} }
...@@ -167,4 +207,3 @@ label { ...@@ -167,4 +207,3 @@ label {
color: $gl-text-color; color: $gl-text-color;
} }
} }
...@@ -39,4 +39,8 @@ ...@@ -39,4 +39,8 @@
&.status-box-expired { &.status-box-expired {
background: #cea61b; background: #cea61b;
} }
&.status-box-upcoming {
background: #8f8f8f;
}
} }
...@@ -243,7 +243,7 @@ ...@@ -243,7 +243,7 @@
} }
.issue-boards-search { .issue-boards-search {
width: 335px; width: 290px;
.form-control { .form-control {
display: inline-block; display: inline-block;
......
...@@ -18,6 +18,31 @@ ...@@ -18,6 +18,31 @@
.environments { .environments {
table-layout: fixed; table-layout: fixed;
.environments-commit,
.environments-actions,
.environments-deploy,
.environments-build,
.environments-date {
position: static;
float: none;
display: table-cell;
}
.environments-commit,
.environments-actions {
width: 20%;
}
.environments-deploy,
.environments-build,
.environments-date {
width: 10%;
}
.environments-name {
width: 30%;
}
.deployment-column { .deployment-column {
.avatar { .avatar {
float: none; float: none;
......
// CI icon colors .ci-status-icon-success {
color: $gl-success;
.ci-status-icon { svg {
&-created { fill: $gl-success;
fill: $gray-darkest; }
}
.ci-status-icon-failed {
color: $gl-danger;
svg {
fill: $gl-danger;
}
}
.ci-status-icon-pending,
.ci-status-icon-success_with_warnings {
color: $gl-warning;
svg {
fill: $gl-warning;
}
}
.ci-status-icon-running {
color: $blue-normal;
svg {
fill: $blue-normal;
}
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found {
color: $gl-gray;
svg {
fill: $gl-gray;
} }
}
&-skipped, .ci-status-icon-created,
&-canceled { .ci-status-icon-skipped {
fill: $gl-text-color; color: $gray-darkest;
svg {
fill: $gray-darkest;
} }
} }
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.accept_merge_request { .accept_merge_request {
&.ci-pending, &.ci-pending,
&.ci-running { &.ci-running {
@include btn-orange; @include btn-blue;
} }
&.ci-skipped, &.ci-skipped,
...@@ -62,6 +62,7 @@ ...@@ -62,6 +62,7 @@
.ci_widget { .ci_widget {
border-bottom: 1px solid $well-inner-border; border-bottom: 1px solid $well-inner-border;
color: $gl-gray;
svg { svg {
margin-right: 4px; margin-right: 4px;
...@@ -70,48 +71,12 @@ ...@@ -70,48 +71,12 @@
overflow: visible; overflow: visible;
} }
&.ci-success {
color: $gl-success;
a.environment,
a.pipeline {
color: inherit;
}
}
&.ci-success_with_warnings { &.ci-success_with_warnings {
color: $gl-success;
i { i {
color: $gl-warning; color: $gl-warning;
} }
} }
&.ci-skipped {
background-color: #eee;
color: #888;
}
&.ci-pending {
color: $gl-warning;
}
&.ci-running {
color: $blue-normal;
}
&.ci-failed,
&.ci-error {
color: $gl-danger;
}
&.ci-canceled {
color: $gl-gray;
}
a.monospace {
color: inherit;
}
} }
.mr-widget-body, .mr-widget-body,
......
...@@ -43,12 +43,25 @@ ul.notes { ...@@ -43,12 +43,25 @@ ul.notes {
} }
.system-note-message { .system-note-message {
text-transform: lowercase; display: inline-block;
&::first-letter {
text-transform: lowercase;
}
a { a {
color: $gl-link-color; color: $gl-link-color;
text-decoration: none; text-decoration: none;
} }
p {
display: inline-block;
margin: 0;
&::first-letter {
text-transform: lowercase;
}
}
} }
.timeline-content { .timeline-content {
...@@ -62,6 +75,13 @@ ul.notes { ...@@ -62,6 +75,13 @@ ul.notes {
display: none; display: none;
padding: 10px 0 0; padding: 10px 0 0;
cursor: pointer; cursor: pointer;
position: relative;
z-index: 2;
&:hover {
color: $gl-link-color;
text-decoration: underline;
}
} }
.note-text { .note-text {
...@@ -87,14 +107,24 @@ ul.notes { ...@@ -87,14 +107,24 @@ ul.notes {
display: none; display: none;
} }
p:last-child {
a {
color: $gl-text-color;
&:hover {
color: $gl-link-color;
}
}
}
&::after { &::after {
content: ''; content: '';
width: 100%; width: 100%;
height: 20px; height: 67px;
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 50px; bottom: 0;
background: linear-gradient(rgba($gray-light, .3) 0, $white-light); background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%);
} }
&.hide-shade { &.hide-shade {
...@@ -188,11 +218,6 @@ ul.notes { ...@@ -188,11 +218,6 @@ ul.notes {
padding-bottom: 3px; padding-bottom: 3px;
padding-right: 20px; padding-right: 20px;
p {
display: inline;
margin: 0;
}
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: 0; padding-right: 0;
} }
......
...@@ -91,14 +91,6 @@ ...@@ -91,14 +91,6 @@
} }
} }
.ci-status {
svg {
top: 1px;
margin-right: 0;
}
}
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
...@@ -195,7 +187,7 @@ ...@@ -195,7 +187,7 @@
width: 8px; width: 8px;
position: absolute; position: absolute;
right: -7px; right: -7px;
bottom: 8px; bottom: 9px;
border-bottom: 2px solid $border-color; border-bottom: 2px solid $border-color;
} }
} }
...@@ -691,10 +683,3 @@ ...@@ -691,10 +683,3 @@
} }
} }
} }
.ci-status-icon-created {
svg {
fill: $gray-darkest;
}
}
...@@ -20,3 +20,7 @@ ...@@ -20,3 +20,7 @@
.danger-title { .danger-title {
color: $gl-danger; color: $gl-danger;
} }
.service-settings .control-label {
padding-top: 0;
}
...@@ -11,80 +11,108 @@ ...@@ -11,80 +11,108 @@
text-decoration: none; text-decoration: none;
} }
svg {
height: 13px;
width: 13px;
position: relative;
top: 1px;
margin-right: 3px;
overflow: visible;
}
&.ci-failed { &.ci-failed {
color: $gl-danger; color: $gl-danger;
border-color: $gl-danger; border-color: $gl-danger;
&:not(span):hover {
background-color: rgba( $gl-danger, .07);
}
svg {
fill: $gl-danger;
}
} }
&.ci-success, &.ci-success,
&.ci-success_with_warnings { &.ci-success_with_warnings {
color: $gl-success; color: $gl-success;
border-color: $gl-success; border-color: $gl-success;
&:not(span):hover {
background-color: rgba( $gl-success, .07);
}
svg {
fill: $gl-success;
}
} }
&.ci-info { &.ci-info {
color: $gl-info; color: $gl-info;
border-color: $gl-info; border-color: $gl-info;
&:not(span):hover {
background-color: rgba( $gl-info, .07);
}
svg {
fill: $gl-info;
}
} }
&.ci-canceled, &.ci-canceled,
&.ci-skipped,
&.ci-disabled { &.ci-disabled {
color: $gl-gray; color: $gl-gray;
border-color: $gl-gray; border-color: $gl-gray;
&:not(span):hover {
background-color: rgba( $gl-gray, .07);
}
svg {
fill: $gl-gray;
}
} }
&.ci-pending { &.ci-pending {
color: $gl-warning; color: $gl-warning;
border-color: $gl-warning; border-color: $gl-warning;
&:not(span):hover {
background-color: rgba( $gl-warning, .07);
}
svg {
fill: $gl-warning;
}
} }
&.ci-running { &.ci-running {
color: $blue-normal; color: $blue-normal;
border-color: $blue-normal; border-color: $blue-normal;
&:not(span):hover {
background-color: rgba( $blue-normal, .07);
}
svg {
fill: $blue-normal;
}
} }
&.ci-created { &.ci-created,
&.ci-skipped {
color: $table-text-gray; color: $table-text-gray;
border-color: $table-text-gray; border-color: $table-text-gray;
&:not(span):hover {
background-color: rgba( $table-text-gray, .07);
}
svg { svg {
fill: $table-text-gray; fill: $table-text-gray;
} }
} }
svg {
height: 13px;
width: 13px;
position: relative;
top: 1px;
margin: 0 3px;
overflow: visible;
}
}
.ci-status-icon-success {
color: $gl-success;
}
.ci-status-icon-failed {
color: $gl-danger;
}
.ci-status-icon-pending,
.ci-status-icon-success_with_warning {
color: $gl-warning;
}
.ci-status-icon-running {
color: $blue-normal;
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
.ci-status-icon-skipped {
color: $gl-gray;
} }
} }
......
...@@ -49,6 +49,14 @@ class ApplicationController < ActionController::Base ...@@ -49,6 +49,14 @@ class ApplicationController < ActionController::Base
render_404 render_404
end end
def route_not_found
if current_user
not_found
else
redirect_to new_user_session_path
end
end
protected protected
# This filter handles both private tokens and personal access tokens # This filter handles both private tokens and personal access tokens
...@@ -224,7 +232,7 @@ class ApplicationController < ActionController::Base ...@@ -224,7 +232,7 @@ class ApplicationController < ActionController::Base
end end
def require_email def require_email
if current_user && current_user.temp_oauth_email? if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil?
redirect_to profile_path, notice: 'Please complete your profile with email address' and return redirect_to profile_path, notice: 'Please complete your profile with email address' and return
end end
end end
......
...@@ -67,7 +67,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -67,7 +67,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end end
def milestone_params def milestone_params
params.require(:milestone).permit(:title, :description, :due_date, :state_event) params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end end
def milestone_path(title) def milestone_path(title)
......
...@@ -563,11 +563,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -563,11 +563,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_pipelines_vars def define_pipelines_vars
@pipelines = @merge_request.all_pipelines @pipelines = @merge_request.all_pipelines
@pipeline = @merge_request.pipeline
if @pipelines.present? && @merge_request.commits.present? @statuses = @pipeline.statuses.relevant if @pipeline.present?
@pipeline = @pipelines.first
@statuses = @pipeline.statuses.relevant
end
end end
def define_new_vars def define_new_vars
......
...@@ -112,6 +112,6 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -112,6 +112,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def milestone_params def milestone_params
params.require(:milestone).permit(:title, :description, :due_date, :state_event) params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end end
end end
...@@ -17,7 +17,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -17,7 +17,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated." flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated."
redirect_to namespace_project_pipelines_settings_path(@project.namespace, @project) redirect_to namespace_project_pipelines_settings_path(@project.namespace, @project)
else else
render 'index' render 'show'
end end
end end
......
...@@ -67,14 +67,14 @@ module ApplicationSettingsHelper ...@@ -67,14 +67,14 @@ module ApplicationSettingsHelper
def import_sources_checkboxes(help_block_id) def import_sources_checkboxes(help_block_id)
Gitlab::ImportSources.options.map do |name, source| Gitlab::ImportSources.options.map do |name, source|
checked = current_application_settings.import_sources.include?(source) checked = current_application_settings.import_sources.include?(source)
css_class = 'btn' css_class = checked ? 'active' : ''
css_class += ' active' if checked
checkbox_name = 'application_setting[import_sources][]' checkbox_name = 'application_setting[import_sources][]'
label_tag(checkbox_name, class: css_class) do label_tag(name, class: css_class) do
check_box_tag(checkbox_name, source, checked, check_box_tag(checkbox_name, source, checked,
autocomplete: 'off', autocomplete: 'off',
'aria-describedby' => help_block_id) + name 'aria-describedby' => help_block_id,
id: name.tr(' ', '_')) + name
end end
end end
end end
......
...@@ -12,7 +12,7 @@ module BuildsHelper ...@@ -12,7 +12,7 @@ module BuildsHelper
build_url: namespace_project_build_url(@project.namespace, @project, @build, :json), build_url: namespace_project_build_url(@project.namespace, @project, @build, :json),
build_status: @build.status, build_status: @build.status,
build_stage: @build.stage, build_stage: @build.stage,
state1: @build.trace_with_state[:state] log_state: @build.trace_with_state[:state].to_s
} }
end end
end end
...@@ -5,7 +5,7 @@ module CiStatusHelper ...@@ -5,7 +5,7 @@ module CiStatusHelper
end end
def ci_status_with_icon(status, target = nil) def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status) content = ci_icon_for_status(status) + ci_label_for_status(status)
klass = "ci-status ci-#{status}" klass = "ci-status ci-#{status}"
if target if target
link_to content, target, class: klass link_to content, target, class: klass
......
...@@ -64,6 +64,8 @@ module IssuesHelper ...@@ -64,6 +64,8 @@ module IssuesHelper
'status-box-merged' 'status-box-merged'
elsif item.closed? elsif item.closed?
'status-box-closed' 'status-box-closed'
elsif item.respond_to?(:upcoming?) && item.upcoming?
'status-box-upcoming'
else else
'status-box-open' 'status-box-open'
end end
......
...@@ -86,6 +86,30 @@ module MilestonesHelper ...@@ -86,6 +86,30 @@ module MilestonesHelper
days = milestone.remaining_days days = milestone.remaining_days
content = content_tag(:strong, days) content = content_tag(:strong, days)
content << " #{'day'.pluralize(days)} remaining" content << " #{'day'.pluralize(days)} remaining"
elsif milestone.upcoming?
content_tag(:strong, 'Upcoming')
elsif milestone.start_date && milestone.start_date.past?
days = milestone.elapsed_days
content = content_tag(:strong, days)
content << " #{'day'.pluralize(days)} elapsed"
end
end
def milestone_date_range(milestone)
if milestone.start_date && milestone.due_date
"#{milestone.start_date.to_s(:medium)} - #{milestone.due_date.to_s(:medium)}"
elsif milestone.due_date
if milestone.due_date.past?
"expired on #{milestone.due_date.to_s(:medium)}"
else
"expires on #{milestone.due_date.to_s(:medium)}"
end
elsif milestone.start_date
if milestone.start_date.past?
"started on #{milestone.start_date.to_s(:medium)}"
else
"starts on #{milestone.start_date.to_s(:medium)}"
end
end end
end end
end end
...@@ -5,15 +5,11 @@ module SidekiqHelper ...@@ -5,15 +5,11 @@ module SidekiqHelper
(?<mem>[\d\.,]+)\s+ (?<mem>[\d\.,]+)\s+
(?<state>[DRSTWXZNLsl\+<]+)\s+ (?<state>[DRSTWXZNLsl\+<]+)\s+
(?<start>.+)\s+ (?<start>.+)\s+
(?<command>sidekiq.*\])\s* (?<command>sidekiq.*\])
\z/x \z/x
def parse_sidekiq_ps(line) def parse_sidekiq_ps(line)
match = line.match(SIDEKIQ_PS_REGEXP) match = line.strip.match(SIDEKIQ_PS_REGEXP)
if match match ? match[1..6] : Array.new(6, '?')
match[1..6]
else
%w[? ? ? ? ? ?]
end
end end
end end
...@@ -135,15 +135,19 @@ class CommitStatus < ActiveRecord::Base ...@@ -135,15 +135,19 @@ class CommitStatus < ActiveRecord::Base
allow_failure? && (failed? || canceled?) allow_failure? && (failed? || canceled?)
end end
def duration
calculate_duration
end
def playable? def playable?
false false
end end
def duration def stuck?
calculate_duration false
end end
def stuck? def has_trace?
false false
end end
end end
...@@ -23,7 +23,31 @@ module Milestoneish ...@@ -23,7 +23,31 @@ module Milestoneish
(due_date - Date.today).to_i (due_date - Date.today).to_i
end end
def elapsed_days
return 0 if !start_date || start_date.future?
(Date.today - start_date).to_i
end
def issues_visible_to_user(user = nil) def issues_visible_to_user(user = nil)
issues.visible_to_user(user) issues.visible_to_user(user)
end end
def upcoming?
start_date && start_date.future?
end
def expires_at
if due_date
if due_date.past?
"expired on #{due_date.to_s(:medium)}"
else
"expires on #{due_date.to_s(:medium)}"
end
end
end
def expired?
due_date && due_date.past?
end
end end
...@@ -28,26 +28,16 @@ class GlobalMilestone ...@@ -28,26 +28,16 @@ class GlobalMilestone
@title.to_slug.normalize.to_s @title.to_slug.normalize.to_s
end end
def expired?
if due_date
due_date.past?
else
false
end
end
def projects def projects
@projects ||= Project.for_milestones(milestones.select(:id)) @projects ||= Project.for_milestones(milestones.select(:id))
end end
def state def state
state = milestones.map { |milestone| milestone.state } milestones.each do |milestone|
return 'active' if milestone.state != 'closed'
if state.count('closed') == state.size
'closed'
else
'active'
end end
'closed'
end end
def active? def active?
...@@ -81,18 +71,15 @@ class GlobalMilestone ...@@ -81,18 +71,15 @@ class GlobalMilestone
@due_date = @due_date =
if @milestones.all? { |x| x.due_date == @milestones.first.due_date } if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
@milestones.first.due_date @milestones.first.due_date
else
nil
end end
end end
def expires_at def start_date
if due_date return @start_date if defined?(@start_date)
if due_date.past?
"expired on #{due_date.to_s(:medium)}" @start_date =
else if @milestones.all? { |x| x.start_date == @milestones.first.start_date }
"expires on #{due_date.to_s(:medium)}" @milestones.first.start_date
end end
end
end end
end end
...@@ -65,7 +65,9 @@ class Group < Namespace ...@@ -65,7 +65,9 @@ class Group < Namespace
def select_for_project_authorization def select_for_project_authorization
if current_scope.joins_values.include?(:shared_projects) if current_scope.joins_values.include?(:shared_projects)
select("members.user_id, projects.id AS project_id, project_group_links.group_access") joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id')
.where('project_namespace.share_with_group_lock = ?', false)
.select("members.user_id, projects.id AS project_id, LEAST(project_group_links.group_access, members.access_level) AS access_level")
else else
super super
end end
......
...@@ -93,7 +93,7 @@ class Issue < ActiveRecord::Base ...@@ -93,7 +93,7 @@ class Issue < ActiveRecord::Base
# Check if we are scoped to a specific project's issues # Check if we are scoped to a specific project's issues
if owner_project if owner_project
if owner_project.authorized_for_user?(user, Gitlab::Access::REPORTER) if owner_project.team.member?(user, Gitlab::Access::REPORTER)
# If the project is authorized for the user, they can see all issues in the project # If the project is authorized for the user, they can see all issues in the project
return all return all
else else
......
...@@ -246,7 +246,7 @@ class Member < ActiveRecord::Base ...@@ -246,7 +246,7 @@ class Member < ActiveRecord::Base
end end
def post_update_hook def post_update_hook
# override in subclass UserProjectAccessChangedService.new(user.id).execute if access_level_changed?
end end
def post_destroy_hook def post_destroy_hook
......
...@@ -494,10 +494,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -494,10 +494,14 @@ class MergeRequest < ActiveRecord::Base
discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?) discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?)
end end
def discussions_to_be_resolved?
discussions_resolvable? && !discussions_resolved?
end
def mergeable_discussions_state? def mergeable_discussions_state?
return true unless project.only_allow_merge_if_all_discussions_are_resolved? return true unless project.only_allow_merge_if_all_discussions_are_resolved?
discussions_resolved? !discussions_to_be_resolved?
end end
def hook_attrs def hook_attrs
......
...@@ -29,6 +29,7 @@ class Milestone < ActiveRecord::Base ...@@ -29,6 +29,7 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true, uniqueness: { scope: :project_id } validates :title, presence: true, uniqueness: { scope: :project_id }
validates :project, presence: true validates :project, presence: true
validate :start_date_should_be_less_than_due_date, if: Proc.new { |m| m.start_date.present? && m.due_date.present? }
strip_attributes :title strip_attributes :title
...@@ -131,24 +132,6 @@ class Milestone < ActiveRecord::Base ...@@ -131,24 +132,6 @@ class Milestone < ActiveRecord::Base
self.title self.title
end end
def expired?
if due_date
due_date.past?
else
false
end
end
def expires_at
if due_date
if due_date.past?
"expired on #{due_date.to_s(:medium)}"
else
"expires on #{due_date.to_s(:medium)}"
end
end
end
def can_be_closed? def can_be_closed?
active? && issues.opened.count.zero? active? && issues.opened.count.zero?
end end
...@@ -212,4 +195,10 @@ class Milestone < ActiveRecord::Base ...@@ -212,4 +195,10 @@ class Milestone < ActiveRecord::Base
def sanitize_title(value) def sanitize_title(value)
CGI.unescape_html(Sanitize.clean(value.to_s)) CGI.unescape_html(Sanitize.clean(value.to_s))
end end
def start_date_should_be_less_than_due_date
if due_date <= start_date
errors.add(:start_date, "Can't be greater than due date")
end
end
end end
...@@ -27,6 +27,7 @@ class Namespace < ActiveRecord::Base ...@@ -27,6 +27,7 @@ class Namespace < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
after_update :move_dir, if: :path_changed? after_update :move_dir, if: :path_changed?
after_commit :refresh_access_of_projects_invited_groups, on: :update, if: -> { previous_changes.key?('share_with_group_lock') }
# Save the storage paths before the projects are destroyed to use them on after destroy # Save the storage paths before the projects are destroyed to use them on after destroy
before_destroy(prepend: true) { @old_repository_storage_paths = repository_storage_paths } before_destroy(prepend: true) { @old_repository_storage_paths = repository_storage_paths }
...@@ -103,6 +104,8 @@ class Namespace < ActiveRecord::Base ...@@ -103,6 +104,8 @@ class Namespace < ActiveRecord::Base
gitlab_shell.add_namespace(repository_storage_path, path_was) gitlab_shell.add_namespace(repository_storage_path, path_was)
unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
# if we cannot move namespace directory we should rollback # if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs # db changes in order to prevent out of sync between db and fs
raise Exception.new('namespace directory cannot be moved') raise Exception.new('namespace directory cannot be moved')
...@@ -175,4 +178,11 @@ class Namespace < ActiveRecord::Base ...@@ -175,4 +178,11 @@ class Namespace < ActiveRecord::Base
end end
end end
end end
def refresh_access_of_projects_invited_groups
Group.
joins(project_group_links: :project).
where(projects: { namespace_id: id }).
find_each(&:refresh_members_authorized_projects)
end
end end
...@@ -126,6 +126,8 @@ class Project < ActiveRecord::Base ...@@ -126,6 +126,8 @@ class Project < ActiveRecord::Base
has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy has_many :protected_branches, dependent: :destroy
has_many :project_authorizations, dependent: :destroy
has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
alias_method :members, :project_members alias_method :members, :project_members
has_many :users, through: :project_members has_many :users, through: :project_members
...@@ -163,6 +165,7 @@ class Project < ActiveRecord::Base ...@@ -163,6 +165,7 @@ class Project < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
delegate :add_user, to: :team delegate :add_user, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
...@@ -174,6 +177,7 @@ class Project < ActiveRecord::Base ...@@ -174,6 +177,7 @@ class Project < ActiveRecord::Base
message: Gitlab::Regex.project_name_regex_message } message: Gitlab::Regex.project_name_regex_message }
validates :path, validates :path,
presence: true, presence: true,
project_path: true,
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_path_regex, format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message } message: Gitlab::Regex.project_path_regex_message }
...@@ -1292,14 +1296,6 @@ class Project < ActiveRecord::Base ...@@ -1292,14 +1296,6 @@ class Project < ActiveRecord::Base
end end
end end
# Checks if `user` is authorized for this project, with at least the
# `min_access_level` (if given).
def authorized_for_user?(user, min_access_level = nil)
return false unless user
user.authorized_project?(self, min_access_level)
end
def append_or_update_attribute(name, value) def append_or_update_attribute(name, value)
old_values = public_send(name.to_s) old_values = public_send(name.to_s)
......
...@@ -57,9 +57,9 @@ class JiraService < IssueTrackerService ...@@ -57,9 +57,9 @@ class JiraService < IssueTrackerService
end end
def help def help
'See the ' \ 'You need to configure JIRA before enabling this service. For more details
'[integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) '\ read the
'for details.' [JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).'
end end
def title def title
......
...@@ -19,13 +19,6 @@ class MattermostSlashCommandsService < ChatService ...@@ -19,13 +19,6 @@ class MattermostSlashCommandsService < ChatService
'mattermost_slash_commands' 'mattermost_slash_commands'
end end
def help
"This service allows you to use slash commands with your Mattermost installation.<br/>
To setup this Service you need to create a new <b>Slash commands</b> in your Mattermost integration panel.<br/>
<br/>
Create integration with URL #{service_trigger_url(self)} and enter the token below."
end
def fields def fields
[ [
{ type: 'text', name: 'token', placeholder: '' } { type: 'text', name: 'token', placeholder: '' }
......
...@@ -21,6 +21,22 @@ class ProjectTeam ...@@ -21,6 +21,22 @@ class ProjectTeam
end end
end end
def add_guest(user, current_user: nil)
self << [user, :guest, current_user]
end
def add_reporter(user, current_user: nil)
self << [user, :reporter, current_user]
end
def add_developer(user, current_user: nil)
self << [user, :developer, current_user]
end
def add_master(user, current_user: nil)
self << [user, :master, current_user]
end
def find_member(user_id) def find_member(user_id)
member = project.members.find_by(user_id: user_id) member = project.members.find_by(user_id: user_id)
...@@ -64,19 +80,19 @@ class ProjectTeam ...@@ -64,19 +80,19 @@ class ProjectTeam
alias_method :users, :members alias_method :users, :members
def guests def guests
@guests ||= fetch_members(:guests) @guests ||= fetch_members(Gitlab::Access::GUEST)
end end
def reporters def reporters
@reporters ||= fetch_members(:reporters) @reporters ||= fetch_members(Gitlab::Access::REPORTER)
end end
def developers def developers
@developers ||= fetch_members(:developers) @developers ||= fetch_members(Gitlab::Access::DEVELOPER)
end end
def masters def masters
@masters ||= fetch_members(:masters) @masters ||= fetch_members(Gitlab::Access::MASTER)
end end
def import(source_project, current_user = nil) def import(source_project, current_user = nil)
...@@ -125,8 +141,12 @@ class ProjectTeam ...@@ -125,8 +141,12 @@ class ProjectTeam
max_member_access(user.id) == Gitlab::Access::MASTER max_member_access(user.id) == Gitlab::Access::MASTER
end end
def member?(user, min_member_access = Gitlab::Access::GUEST) # Checks if `user` is authorized for this project, with at least the
max_member_access(user.id) >= min_member_access # `min_access_level` (if given).
def member?(user, min_access_level = Gitlab::Access::GUEST)
return false unless user
user.authorized_project?(project, min_access_level)
end end
def human_max_access(user_id) def human_max_access(user_id)
...@@ -149,112 +169,29 @@ class ProjectTeam ...@@ -149,112 +169,29 @@ class ProjectTeam
# Lookup only the IDs we need # Lookup only the IDs we need
user_ids = user_ids - access.keys user_ids = user_ids - access.keys
users_access = project.project_authorizations.
where(user: user_ids).
group(:user_id).
maximum(:access_level)
if user_ids.present? access.merge!(users_access)
user_ids.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
member_access = project.members.access_for_user_ids(user_ids)
merge_max!(access, member_access)
if group
group_access = group.members.access_for_user_ids(user_ids)
merge_max!(access, group_access)
end
# Each group produces a list of maximum access level per user. We take the
# max of the values produced by each group.
if project_shared_with_group?
project.project_group_links.each do |group_link|
invited_access = max_invited_level_for_users(group_link, user_ids)
merge_max!(access, invited_access)
end
end
end
access access
end end
def max_member_access(user_id) def max_member_access(user_id)
max_member_access_for_user_ids([user_id])[user_id] max_member_access_for_user_ids([user_id])[user_id] || Gitlab::Access::NO_ACCESS
end end
private private
# For a given group, return the maximum access level for the user. This is the min of
# the invited access level of the group and the access level of the user within the group.
# For example, if the group has been given DEVELOPER access but the member has MASTER access,
# the user should receive only DEVELOPER access.
def max_invited_level_for_users(group_link, user_ids)
invited_group = group_link.group
capped_access_level = group_link.group_access
access = invited_group.group_members.access_for_user_ids(user_ids)
# If the user is not in the list, assume he/she does not have access
missing_users = user_ids - access.keys
missing_users.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
# Cap the maximum access by the invited level access
access.each { |key, value| access[key] = [value, capped_access_level].min }
end
def fetch_members(level = nil) def fetch_members(level = nil)
project_members = project.members members = project.authorized_users
group_members = group ? group.members : [] members = members.where(project_authorizations: { access_level: level }) if level
if level
project_members = project_members.public_send(level)
group_members = group_members.public_send(level) if group
end
user_ids = project_members.pluck(:user_id)
invited_members = fetch_invited_members(level)
user_ids.push(*invited_members.map(&:user_id)) if invited_members.any?
user_ids.push(*group_members.pluck(:user_id)) if group members
User.where(id: user_ids)
end end
def group def group
project.group project.group
end end
def merge_max!(first_hash, second_hash)
first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
end
def project_shared_with_group?
project.invited_groups.any? && project.allowed_to_share_with_group?
end
def fetch_invited_members(level = nil)
invited_members = []
return invited_members unless project_shared_with_group?
project.project_group_links.includes(group: [:group_members]).each do |link|
invited_group_members = link.group.members
if level
numeric_level = GroupMember.access_level_roles[level.to_s.singularize.titleize]
# If we're asked for a level that's higher than the group's access,
# there's nothing left to do
next if numeric_level > link.group_access
# Make sure we include everyone _above_ the requested level as well
invited_group_members =
if numeric_level == link.group_access
invited_group_members.where("access_level >= ?", link.group_access)
else
invited_group_members.public_send(level)
end
end
invited_members << invited_group_members
end
invited_members.flatten.compact
end
end end
...@@ -291,8 +291,12 @@ class User < ActiveRecord::Base ...@@ -291,8 +291,12 @@ class User < ActiveRecord::Base
end end
end end
def find_by_username(username)
iwhere(username: username).take
end
def find_by_username!(username) def find_by_username!(username)
find_by!('lower(username) = ?', username.downcase) iwhere(username: username).take!
end end
def find_by_personal_access_token(token_string) def find_by_personal_access_token(token_string)
...@@ -926,7 +930,7 @@ class User < ActiveRecord::Base ...@@ -926,7 +930,7 @@ class User < ActiveRecord::Base
# Returns a union query of projects that the user is authorized to access # Returns a union query of projects that the user is authorized to access
def project_authorizations_union def project_authorizations_union
relations = [ relations = [
personal_projects.select("#{id} AS user_id, projects.id AS project_id, #{Gitlab::Access::OWNER} AS access_level"), personal_projects.select("#{id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"),
groups_projects.select_for_project_authorization, groups_projects.select_for_project_authorization,
projects.select_for_project_authorization, projects.select_for_project_authorization,
groups.joins(:shared_projects).select_for_project_authorization groups.joins(:shared_projects).select_for_project_authorization
......
...@@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity ...@@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity
end end
expose :duration, as: :total_time do |build| expose :duration, as: :total_time do |build|
distance_of_time_as_hash(build[:duration].to_f) distance_of_time_as_hash(build.duration.to_f)
end end
expose :branch do expose :branch do
......
...@@ -2,7 +2,7 @@ module EntityDateHelper ...@@ -2,7 +2,7 @@ module EntityDateHelper
include ActionView::Helpers::DateHelper include ActionView::Helpers::DateHelper
def interval_in_words(diff) def interval_in_words(diff)
"#{distance_of_time_in_words(diff.to_f)} ago" "#{distance_of_time_in_words(Time.now, diff)} ago"
end end
# Converts seconds into a hash such as: # Converts seconds into a hash such as:
......
require_relative 'base_service'
## ##
# Branch can be deleted either by DeleteBranchService # Branch can be deleted either by DeleteBranchService
# or by GitPushService. # or by GitPushService.
......
require_relative 'base_service'
class CreateBranchService < BaseService class CreateBranchService < BaseService
def execute(branch_name, ref) def execute(branch_name, ref)
failure = validate_new_branch(branch_name) failure = validate_new_branch(branch_name)
......
require_relative 'base_service'
class CreateDeploymentService < BaseService class CreateDeploymentService < BaseService
def execute(deployable = nil) def execute(deployable = nil)
return unless executable? return unless executable?
......
require_relative 'base_service'
class CreateReleaseService < BaseService class CreateReleaseService < BaseService
def execute(tag_name, release_description) def execute(tag_name, release_description)
repository = project.repository repository = project.repository
......
require_relative 'base_service'
class CreateTagService < BaseService class CreateTagService < BaseService
def execute(tag_name, target, message, release_description = nil) def execute(tag_name, target, message, release_description = nil)
valid_tag = Gitlab::GitRefValidator.validate(tag_name) valid_tag = Gitlab::GitRefValidator.validate(tag_name)
......
require_relative 'base_service'
class DeleteBranchService < BaseService class DeleteBranchService < BaseService
def execute(branch_name) def execute(branch_name)
repository = project.repository repository = project.repository
......
require_relative 'base_service'
class DeleteMergedBranchesService < BaseService class DeleteMergedBranchesService < BaseService
def async_execute def async_execute
DeleteMergedBranchesWorker.perform_async(project.id, current_user.id) DeleteMergedBranchesWorker.perform_async(project.id, current_user.id)
......
require_relative 'base_service'
class DeleteTagService < BaseService class DeleteTagService < BaseService
def execute(tag_name) def execute(tag_name)
repository = project.repository repository = project.repository
......
require_relative "base_service"
module Files module Files
class CreateDirService < Files::BaseService class CreateDirService < Files::BaseService
def commit def commit
......
require_relative "base_service"
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def commit def commit
......
require_relative "base_service"
module Files module Files
class DeleteService < Files::BaseService class DeleteService < Files::BaseService
def commit def commit
......
require_relative "base_service"
module Files module Files
class MultiService < Files::BaseService class MultiService < Files::BaseService
class FileChangedError < StandardError; end class FileChangedError < StandardError; end
......
require_relative "base_service"
module Files module Files
class UpdateService < Files::BaseService class UpdateService < Files::BaseService
class FileChangedError < StandardError; end class FileChangedError < StandardError; end
......
require_relative 'base_service'
require_relative 'reopen_service'
require_relative 'close_service'
module MergeRequests module MergeRequests
class UpdateService < MergeRequests::BaseService class UpdateService < MergeRequests::BaseService
def execute(merge_request) def execute(merge_request)
......
require_relative 'base_service'
class UpdateReleaseService < BaseService class UpdateReleaseService < BaseService
def execute(tag_name, release_description) def execute(tag_name, release_description)
repository = project.repository repository = project.repository
......
...@@ -35,8 +35,22 @@ class NamespaceValidator < ActiveModel::EachValidator ...@@ -35,8 +35,22 @@ class NamespaceValidator < ActiveModel::EachValidator
users users
].freeze ].freeze
def self.valid?(value)
!reserved?(value) && follow_format?(value)
end
def self.reserved?(value)
RESERVED.include?(value)
end
def self.follow_format?(value)
value =~ Gitlab::Regex.namespace_regex
end
delegate :reserved?, :follow_format?, to: :class
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unless value =~ Gitlab::Regex.namespace_regex unless follow_format?(value)
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message) record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
end end
...@@ -44,10 +58,4 @@ class NamespaceValidator < ActiveModel::EachValidator ...@@ -44,10 +58,4 @@ class NamespaceValidator < ActiveModel::EachValidator
record.errors.add(attribute, "#{value} is a reserved name") record.errors.add(attribute, "#{value} is a reserved name")
end end
end end
private
def reserved?(value)
RESERVED.include?(value)
end
end end
# ProjectPathValidator
#
# Custom validator for GitLab project path values.
#
# Values are checked for formatting and exclusion from a list of reserved path
# names.
class ProjectPathValidator < ActiveModel::EachValidator
# All project routes with wildcard argument must be listed here.
# Otherwise it can lead to routing issues when route considered as project name.
#
# Example:
# /group/project/tree/deploy_keys
#
# without tree as reserved name routing can match 'group/project' as group name,
# 'tree' as project name and 'deploy_keys' as route.
#
RESERVED = (NamespaceValidator::RESERVED +
%w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file]).freeze
def self.valid?(value)
!reserved?(value)
end
def self.reserved?(value)
RESERVED.include?(value)
end
delegate :reserved?, to: :class
def validate_each(record, attribute, value)
if reserved?(value)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
end
...@@ -31,10 +31,8 @@ ...@@ -31,10 +31,8 @@
.form-group .form-group
= f.label :import_sources, class: 'control-label col-sm-2' = f.label :import_sources, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
- data_attrs = { toggle: 'buttons' } - import_sources_checkboxes('import-sources-help').each do |source|
.btn-group{ data: data_attrs } .checkbox= source
- import_sources_checkboxes('import-sources-help').each do |source|
= source
%span.help-block#import-sources-help %span.help-block#import-sources-help
Enabled sources for code import during project creation. OmniAuth must be configured for GitHub Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
= link_to "(?)", help_page_path("integration/github") = link_to "(?)", help_page_path("integration/github")
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
%p #{@service.description} template %p #{@service.description} template
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |form| = form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |form|
= render 'shared/service_settings', form: form = render 'shared/service_settings', form: form, subject: @service
.form-actions .footer-block.row-content-block
= form.submit 'Save', class: 'btn btn-save' .form-actions
= form.submit 'Save', class: 'btn btn-save'
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline) - grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards| - awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), data: { placement: "bottom", title: award_user_list(awards, current_user) } } %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
disabled: !current_user,
class: (award_active_class(awards, current_user)),
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji, sprite: false) = emoji_icon(emoji, sprite: false)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= awards.count = awards.count
......
...@@ -36,19 +36,8 @@ ...@@ -36,19 +36,8 @@
= f.collection_select :project_ids, @group.projects.non_archived, :id, :name, = f.collection_select :project_ids, @group.projects.non_archived, :id, :name,
{ selected: @group.projects.non_archived.pluck(:id) }, required: true, multiple: true, class: 'select2' { selected: @group.projects.non_archived.pluck(:id) }, required: true, multiple: true, class: 'select2'
.col-md-6 = render "shared/milestones/form_dates", f: f
.form-group
= f.label :due_date, "Due Date", class: "control-label"
.col-sm-10
= f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
.form-actions .form-actions
= f.submit 'Create Milestone', class: "btn-create btn" = f.submit 'Create Milestone', class: "btn-create btn"
= link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel" = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
:javascript
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
%a{href: pipeline_build_url(pipeline, build), style: "color:#3777b0;text-decoration:none;"}
= build.name
Build #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> )
...@@ -158,12 +158,14 @@ ...@@ -158,12 +158,14 @@
%td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"}
= build.stage = build.stage
%td{align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} %td{align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"}
%a{href: pipeline_build_url(@pipeline, build), style: "color:#3777b0;text-decoration:none;"} = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build
= build.name
%tr.build-log %tr.build-log
%td{colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} - if build.has_trace?
%pre{style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"} %td{colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"}
= build.trace_html(last_lines: 10).html_safe %pre{style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"}
= build.trace_html(last_lines: 10).html_safe
- else
%td{colspan: "2"}
%tr.footer %tr.footer
%td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"}
%img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/ %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/
......
...@@ -19,10 +19,12 @@ Commit Author: <%= commit.author_name %> ...@@ -19,10 +19,12 @@ Commit Author: <%= commit.author_name %>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>.
<% failed.each do |build| -%> <% failed.each do |build| -%>
Build #<%= build.id %> ( <%= pipeline_build_url(@pipeline, build) %> ) <%= render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %>
Stage: <%= build.stage %> Stage: <%= build.stage %>
Name: <%= build.name %> Name: <%= build.name %>
<% if build.has_trace? -%>
Trace: <%= build.trace_with_state(last_lines: 10)[:text] %> Trace: <%= build.trace_with_state(last_lines: 10)[:text] %>
<% end -%>
<% end -%> <% end -%>
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
":issue" => "issue", ":issue" => "issue",
":issue-link-base" => "issueLinkBase", ":issue-link-base" => "issueLinkBase",
":disabled" => "disabled", ":disabled" => "disabled",
"key" => "id" } ":key" => "issue.id" }
%li.board-list-count.text-center{ "v-if" => "showCount" } %li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" ) = icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" } %span{ "v-if" => "list.issues.length === list.issuesSize" }
......
%li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }', %li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
":index" => "index", ":index" => "index",
"@mousedown" => "mouseDown", "@mousedown" => "mouseDown",
"@mousemove" => "mouseMove",
"@mouseup" => "showIssue($event)" } "@mouseup" => "showIssue($event)" }
%h4.card-title %h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
......
...@@ -128,7 +128,8 @@ ...@@ -128,7 +128,8 @@
.build-job{class: sidebar_build_class(build, @build), data: {stage: build.stage}} .build-job{class: sidebar_build_class(build, @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do = link_to namespace_project_build_path(@project.namespace, @project, build) do
= icon('arrow-right') = icon('arrow-right')
= ci_icon_for_status(build.status) %span{class: "ci-status-icon-#{build.status}"}
= ci_icon_for_status(build.status)
%span %span
- if build.name - if build.name
= build.name = build.name
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
.ci-status-text= subject.name .ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project) - elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do
%span.ci-status-icon %span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
= ci_icon_for_status(subject.status) = ci_icon_for_status(subject.status)
.ci-status-text= subject.name .ci-status-text= subject.name
- else - else
%span.ci-status-icon %span{class: "ci-status-icon ci-status-icon-#{subject.status}"}
= ci_icon_for_status(subject.status) = ci_icon_for_status(subject.status)
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
%tr.commit %tr.commit
%td.commit-link %td.commit-link
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{status}" do
= ci_status_with_icon(status) = ci_icon_for_status(status)
= ci_label_for_status(status)
%td %td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment