Commit 77cf83aa authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents 497844d0 477f0309
...@@ -13,7 +13,7 @@ variables: ...@@ -13,7 +13,7 @@ variables:
ELASTIC_HOST: "registry.gitlab.com-gitlab-org-test-elastic-image" ELASTIC_HOST: "registry.gitlab.com-gitlab-org-test-elastic-image"
RAILS_ENV: "test" RAILS_ENV: "test"
SIMPLECOV: "true" SIMPLECOV: "true"
USE_DB: "true" SETUP_DB: "true"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20" GIT_DEPTH: "20"
PHANTOMJS_VERSION: "2.1.1" PHANTOMJS_VERSION: "2.1.1"
...@@ -24,7 +24,7 @@ before_script: ...@@ -24,7 +24,7 @@ before_script:
- 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
- '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate' - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
stages: stages:
- prepare - prepare
...@@ -36,7 +36,7 @@ stages: ...@@ -36,7 +36,7 @@ stages:
.knapsack-state: &knapsack-state .knapsack-state: &knapsack-state
services: [] services: []
variables: variables:
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
cache: cache:
key: "knapsack" key: "knapsack"
...@@ -198,7 +198,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 ...@@ -198,7 +198,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
.ruby-static-analysis: &ruby-static-analysis .ruby-static-analysis: &ruby-static-analysis
variables: variables:
SIMPLECOV: "false" SIMPLECOV: "false"
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
.exec: &exec .exec: &exec
...@@ -226,6 +226,23 @@ rake db:migrate:reset: ...@@ -226,6 +226,23 @@ rake db:migrate:reset:
script: script:
- rake db:migrate:reset - rake db:migrate:reset
rake db:seed_fu:
stage: test
<<: *use-db
variables:
SIZE: "1"
SETUP_DB: "false"
RAILS_ENV: "development"
script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git
- bundle exec rake db:setup db:seed_fu
artifacts:
when: on_failure
expire_in: 1d
paths:
- log/development.log
teaspoon: teaspoon:
stage: test stage: test
<<: *use-db <<: *use-db
...@@ -274,7 +291,7 @@ coverage: ...@@ -274,7 +291,7 @@ coverage:
stage: post-test stage: post-test
services: [] services: []
variables: variables:
USE_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
script: script:
- bundle exec scripts/merge-simplecov - bundle exec scripts/merge-simplecov
...@@ -290,7 +307,7 @@ coverage: ...@@ -290,7 +307,7 @@ coverage:
notify:slack: notify:slack:
stage: post-test stage: post-test
variables: variables:
USE_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 "#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>"
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.12.1 (unreleased) v 8.12.1 (unreleased)
v 8.13.0 (unreleased)
- Speed-up group milestones show page
v 8.12.2 (unreleased)
- Fix Import/Export not recognising correctly the imported services.
v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
- Fix issue with search filter labels not displaying
v 8.12.0 v 8.12.0
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
...@@ -29,6 +38,7 @@ v 8.12.0 ...@@ -29,6 +38,7 @@ v 8.12.0
- Move pushes_since_gc from the database to Redis - Move pushes_since_gc from the database to Redis
- Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags
- Add font color contrast to external label in admin area (ClemMakesApps) - Add font color contrast to external label in admin area (ClemMakesApps)
- Fix find file navigation links (ClemMakesApps)
- Change logo animation to CSS (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps)
- Instructions for enabling Git packfile bitmaps !6104 - Instructions for enabling Git packfile bitmaps !6104
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
...@@ -47,6 +57,7 @@ v 8.12.0 ...@@ -47,6 +57,7 @@ v 8.12.0
- Fix file permissions change when updating a file on the Gitlab UI !5979 - Fix file permissions change when updating a file on the Gitlab UI !5979
- Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev) - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev)
- Change merge_error column from string to text type - Change merge_error column from string to text type
- Fix issue with search filter labels not displaying
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Show all pipelines for merge requests even from discarded commits !6414 - Show all pipelines for merge requests even from discarded commits !6414
- Replace contributions calendar timezone payload with dates (ClemMakesApps) - Replace contributions calendar timezone payload with dates (ClemMakesApps)
...@@ -81,6 +92,7 @@ v 8.12.0 ...@@ -81,6 +92,7 @@ v 8.12.0
- Fix markdown anchor icon interaction (ClemMakesApps) - Fix markdown anchor icon interaction (ClemMakesApps)
- Test migration paths from 8.5 until current release !4874 - Test migration paths from 8.5 until current release !4874
- Replace animateEmoji timeout with eventListener (ClemMakesApps) - Replace animateEmoji timeout with eventListener (ClemMakesApps)
- Show badges in Milestone tabs. !5946 (Dan Rowden)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
......
...@@ -333,10 +333,6 @@ group :test do ...@@ -333,10 +333,6 @@ group :test do
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
end end
group :production do
gem 'gitlab_meta', '7.0'
end
gem 'newrelic_rpm', '~> 3.16' gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0' gem 'octokit', '~> 4.3.0'
......
...@@ -306,7 +306,6 @@ GEM ...@@ -306,7 +306,6 @@ GEM
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
rugged (~> 0.24.0) rugged (~> 0.24.0)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -895,7 +894,6 @@ DEPENDENCIES ...@@ -895,7 +894,6 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab_git (~> 10.6.6) gitlab_git (~> 10.6.6)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
...@@ -1025,4 +1023,4 @@ DEPENDENCIES ...@@ -1025,4 +1023,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.13.0 1.13.1
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
## Canonical source ## Canonical source
The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ee). The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ee).
......
8.12.0-ee 8.13.0-ee-pre
((global) => { ((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed'; const COOKIE_NAME = 'cycle_analytics_help_dismissed';
const store = gl.cycleAnalyticsStore = {
isLoading: true,
hasError: false,
isHelpDismissed: $.cookie(COOKIE_NAME),
analytics: {}
};
gl.CycleAnalytics = class CycleAnalytics { gl.CycleAnalytics = class CycleAnalytics {
constructor() { constructor() {
const that = this; const that = this;
this.isHelpDismissed = $.cookie(COOKIE_NAME);
this.vue = new Vue({ this.vue = new Vue({
el: '#cycle-analytics', el: '#cycle-analytics',
name: 'CycleAnalytics', name: 'CycleAnalytics',
created: this.fetchData(), created: this.fetchData(),
data: this.decorateData({ isLoading: true }), data: store,
methods: { methods: {
dismissLanding() { dismissLanding() {
that.dismissLanding(); that.dismissLanding();
...@@ -21,6 +26,7 @@ ...@@ -21,6 +26,7 @@
} }
fetchData(options) { fetchData(options) {
store.isLoading = true;
options = options || { startDate: 30 }; options = options || { startDate: 30 };
$.ajax({ $.ajax({
...@@ -30,22 +36,20 @@ ...@@ -30,22 +36,20 @@
contentType: 'application/json', contentType: 'application/json',
data: { start_date: options.startDate } data: { start_date: options.startDate }
}).done((data) => { }).done((data) => {
this.vue.$data = this.decorateData(data); this.decorateData(data);
this.initDropdown(); this.initDropdown();
}) })
.error((data) => { .error((data) => {
this.handleError(data); this.handleError(data);
}) })
.always(() => { .always(() => {
this.vue.isLoading = false; store.isLoading = false;
}) })
} }
decorateData(data) { decorateData(data) {
data.summary = data.summary || []; data.summary = data.summary || [];
data.stats = data.stats || []; data.stats = data.stats || [];
data.isHelpDismissed = this.isHelpDismissed;
data.isLoading = data.isLoading || false;
data.summary.forEach((item) => { data.summary.forEach((item) => {
item.value = item.value || '-'; item.value = item.value || '-';
...@@ -53,23 +57,21 @@ ...@@ -53,23 +57,21 @@
data.stats.forEach((item) => { data.stats.forEach((item) => {
item.value = item.value || '- - -'; item.value = item.value || '- - -';
}) });
return data; store.analytics = data;
} }
handleError(data) { handleError(data) {
this.vue.$data = { store.hasError = true;
hasError: true,
isHelpDismissed: this.isHelpDismissed
};
new Flash('There was an error while fetching cycle analytics data.', 'alert'); new Flash('There was an error while fetching cycle analytics data.', 'alert');
} }
dismissLanding() { dismissLanding() {
this.vue.isHelpDismissed = true; store.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true); $.cookie(COOKIE_NAME, true, {
path: gon.relative_url_root || '/'
});
} }
initDropdown() { initDropdown() {
...@@ -82,7 +84,6 @@ ...@@ -82,7 +84,6 @@
const value = $target.data('value'); const value = $target.data('value');
$label.text($target.text().trim()); $label.text($target.text().trim());
this.vue.isLoading = true;
this.fetchData({ startDate: value }); this.fetchData({ startDate: value });
}) })
} }
......
...@@ -608,14 +608,13 @@ ...@@ -608,14 +608,13 @@
} }
} }
field = []; field = [];
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
if (isInput) { if (isInput) {
field = $(this.el); field = $(this.el);
} else if(value) { } else if(value) {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
} }
if (field.length && el.hasClass(ACTIVE_CLASS)) { if (el.hasClass(ACTIVE_CLASS)) {
el.removeClass(ACTIVE_CLASS); el.removeClass(ACTIVE_CLASS);
if (field && field.length) { if (field && field.length) {
if (isInput) { if (isInput) {
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
function ProjectFindFile(element1, options) { function ProjectFindFile(element1, options) {
this.element = element1; this.element = element1;
this.options = options; this.options = options;
this.goToBlob = bind(this.goToBlob, this);
this.goToTree = bind(this.goToTree, this); this.goToTree = bind(this.goToTree, this);
this.selectRowDown = bind(this.selectRowDown, this); this.selectRowDown = bind(this.selectRowDown, this);
this.selectRowUp = bind(this.selectRowUp, this); this.selectRowUp = bind(this.selectRowUp, this);
...@@ -36,16 +35,6 @@ ...@@ -36,16 +35,6 @@
} }
}; };
})(this)); })(this));
return this.element.find(".tree-content-holder .tree-table").on("click", function(event) {
var path;
if (event.target.nodeName !== "A") {
path = this.element.find(".tree-item-file-name a", this).attr("href");
if (path) {
return location.href = path;
}
}
});
// init event
}; };
ProjectFindFile.prototype.findFile = function() { ProjectFindFile.prototype.findFile = function() {
...@@ -121,11 +110,12 @@ ...@@ -121,11 +110,12 @@
// make tbody row html // make tbody row html
ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) { ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
var $tr; var $tr;
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>"); $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
if (matches) { if (matches) {
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl)); $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
} else { } else {
$tr.find("a").attr("href", blobItemUrl).text(filePath); $tr.find("a").attr("href", blobItemUrl);
$tr.find(".str-truncated").text(filePath);
} }
return $tr; return $tr;
}; };
...@@ -164,14 +154,6 @@ ...@@ -164,14 +154,6 @@
return location.href = this.options.treeUrl; return location.href = this.options.treeUrl;
}; };
ProjectFindFile.prototype.goToBlob = function() {
var path;
path = this.element.find(".tree-item.selected .tree-item-file-name a").attr("href");
if (path) {
return location.href = path;
}
};
return ProjectFindFile; return ProjectFindFile;
})(); })();
......
...@@ -67,7 +67,6 @@ ...@@ -67,7 +67,6 @@
dataType: 'json', dataType: 'json',
data: { data: {
_method: 'PATCH', _method: 'PATCH',
id: this.$wrap.data('banchId'),
protected_branch: formData protected_branch: formData
}, },
success: (response) => { success: (response) => {
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
margin: 0; margin: 0;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
font-size: 14px; font-size: 14px;
z-index: 100;
.flash-notice { .flash-notice {
@extend .alert; @extend .alert;
...@@ -41,4 +40,3 @@ ...@@ -41,4 +40,3 @@
} }
} }
} }
...@@ -269,6 +269,12 @@ $calendar-hover-bg: #ecf3fe; ...@@ -269,6 +269,12 @@ $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1); $calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: $gray-light; $calendar-unselectable-bg: $gray-light;
/*
* Cycle Analytics
*/
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
/* /*
* Personal Access Tokens * Personal Access Tokens
*/ */
......
#cycle-analytics { #cycle-analytics {
margin: 24px auto 0; margin: 24px auto 0;
width: 800px; max-width: 800px;
position: relative; position: relative;
.panel { .panel {
...@@ -9,10 +9,18 @@ ...@@ -9,10 +9,18 @@
padding: 24px 0; padding: 24px 0;
border-bottom: none; border-bottom: none;
position: relative; position: relative;
@media (max-width: $screen-sm-min) {
padding: 6px 0 24px;
}
} }
.column { .column {
text-align: center; text-align: center;
@media (max-width: $screen-sm-min) {
padding: 15px 0;
}
.header { .header {
font-size: 30px; font-size: 30px;
...@@ -28,11 +36,14 @@ ...@@ -28,11 +36,14 @@
&:last-child { &:last-child {
text-align: right; text-align: right;
@media (max-width: $screen-sm-min) {
text-align: center;
}
} }
} }
.dropdown { .dropdown {
position: relative;
top: 13px; top: 13px;
} }
} }
...@@ -40,7 +51,7 @@ ...@@ -40,7 +51,7 @@
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $border-color;
@include border-radius($border-radius-default); @include border-radius($border-radius-default);
position: relative;
} }
.content-list { .content-list {
...@@ -60,9 +71,15 @@ ...@@ -60,9 +71,15 @@
line-height: 19px; line-height: 19px;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: $gl-title-color;
} }
&:text {
color: #8c8c8c; &.text {
color: $layout-link-gray;
&.value-col {
color: $gl-title-color;
}
} }
} }
} }
...@@ -71,7 +88,9 @@ ...@@ -71,7 +88,9 @@
text-align: right; text-align: right;
span { span {
line-height: 42px; position: relative;
vertical-align: middle;
top: 3px;
} }
} }
} }
...@@ -82,21 +101,25 @@ ...@@ -82,21 +101,25 @@
.dismiss-icon { .dismiss-icon {
position: absolute; position: absolute;
right: $gl-padding; right: $cycle-analytics-box-padding;
cursor: pointer; cursor: pointer;
color: #b2b2b2; color: #b2b2b2;
} }
svg { .svg-container {
margin: 0 20px; text-align: center;
float: left;
width: 136px; svg {
height: 136px; width: 136px;
height: 136px;
}
} }
.inner-content { .inner-content {
width: 480px; @media (max-width: $screen-sm-min) {
float: left; padding: 0 28px;
text-align: center;
}
h4 { h4 {
color: $gl-text-color; color: $gl-text-color;
...@@ -104,7 +127,7 @@ ...@@ -104,7 +127,7 @@
} }
p { p {
color: #8c8c8c; color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
} }
......
...@@ -2,13 +2,17 @@ ...@@ -2,13 +2,17 @@
max-width: 90%; max-width: 90%;
} }
li.milestone { .milestones {
h4 { .milestone {
font-weight: bold; padding: 10px 16px;
}
h4 {
font-weight: bold;
}
.progress { .progress {
height: 6px; height: 6px;
}
} }
} }
...@@ -64,3 +68,14 @@ li.milestone { ...@@ -64,3 +68,14 @@ li.milestone {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
padding: 20px 0; padding: 20px 0;
} }
@media (max-width: $screen-sm-min) {
.milestone-actions {
@include clearfix();
padding-top: $gl-vert-padding;
.btn:first-child {
margin-left: 0;
}
}
}
...@@ -177,6 +177,10 @@ ...@@ -177,6 +177,10 @@
border-bottom: 2px solid $border-color; border-bottom: 2px solid $border-color;
} }
} }
a {
display: block;
}
} }
} }
......
...@@ -55,6 +55,15 @@ ...@@ -55,6 +55,15 @@
} }
.tree-item { .tree-item {
.link-container {
padding: 0;
a {
padding: 10px $gl-padding;
display: block;
}
}
.tree-item-file-name { .tree-item-file-name {
max-width: 320px; max-width: 320px;
vertical-align: middle; vertical-align: middle;
......
...@@ -6,8 +6,6 @@ class SearchController < ApplicationController ...@@ -6,8 +6,6 @@ class SearchController < ApplicationController
layout 'search' layout 'search'
def show def show
return if params[:search].nil? || params[:search].blank?
if params[:project_id].present? if params[:project_id].present?
@project = Project.find_by(id: params[:project_id]) @project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project) @project = nil unless can?(current_user, :download_code, @project)
...@@ -18,6 +16,8 @@ class SearchController < ApplicationController ...@@ -18,6 +16,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group) @group = nil unless can?(current_user, :read_group, @group)
end end
return if params[:search].nil? || params[:search].blank?
@search_term = params[:search] @search_term = params[:search]
@scope = params[:scope] @scope = params[:scope]
......
...@@ -35,6 +35,30 @@ module MilestonesHelper ...@@ -35,6 +35,30 @@ module MilestonesHelper
milestone.issues.with_label(label.title).send(state).size milestone.issues.with_label(label.title).send(state).size
end end
# Returns count of milestones for different states
# Uses explicit hash keys as the 'opened' state URL params differs from the db value
# and we need to add the total
def milestone_counts(milestones)
counts = milestones.reorder(nil).group(:state).count
{
opened: counts['active'] || 0,
closed: counts['closed'] || 0,
all: counts.values.sum || 0
}
end
# Show 'active' class if provided GET param matches check
# `or_blank` allows the function to return 'active' when given an empty param
# Could be refactored to be simpler but that may make it harder to read
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
else
'active' if param == check
end
end
def milestone_progress_bar(milestone) def milestone_progress_bar(milestone)
options = { options = {
class: 'progress-bar progress-bar-success', class: 'progress-bar progress-bar-success',
......
...@@ -91,7 +91,7 @@ module Ci ...@@ -91,7 +91,7 @@ module Ci
sha: build.sha, sha: build.sha,
ref: build.ref, ref: build.ref,
tag: build.tag, tag: build.tag,
options: build.options[:environment], options: build.options.to_h[:environment],
variables: build.variables) variables: build.variables)
service.execute(build) service.execute(build)
end end
......
...@@ -8,7 +8,8 @@ class GlobalMilestone ...@@ -8,7 +8,8 @@ class GlobalMilestone
milestones = milestones.group_by(&:title) milestones = milestones.group_by(&:title)
milestones.map do |title, milestones| milestones.map do |title, milestones|
new(title, milestones) milestones_relation = Milestone.where(id: milestones.map(&:id))
new(title, milestones_relation)
end end
end end
...@@ -31,7 +32,7 @@ class GlobalMilestone ...@@ -31,7 +32,7 @@ class GlobalMilestone
end end
def projects def projects
@projects ||= Project.for_milestones(milestones.map(&:id)) @projects ||= Project.for_milestones(milestones.select(:id))
end end
def state def state
...@@ -53,19 +54,19 @@ class GlobalMilestone ...@@ -53,19 +54,19 @@ class GlobalMilestone
end end
def issues def issues
@issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project) @issues ||= Issue.of_milestones(milestones.select(:id)).includes(:project, :assignee, :labels)
end end
def merge_requests def merge_requests
@merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project) @merge_requests ||= MergeRequest.of_milestones(milestones.select(:id)).includes(:target_project, :assignee, :labels)
end end
def participants def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq @participants ||= milestones.includes(:participants).map(&:participants).flatten.compact.uniq
end end
def labels def labels
@labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten) @labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten)
.sort_by!(&:title) .sort_by!(&:title)
end end
......
...@@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService ...@@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService
end end
end end
def title=(value)
self.properties['title'] = value if self.properties
end
def description def description
if self.properties && self.properties['description'].present? if self.properties && self.properties['description'].present?
self.properties['description'] self.properties['description']
......
= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
......
...@@ -2,18 +2,20 @@ ...@@ -2,18 +2,20 @@
- page_title "Cycle Analytics" - page_title "Cycle Analytics"
= render "projects/pipelines/head" = render "projects/pipelines/head"
#cycle-analytics{"v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} #cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"} .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
= icon('times', class: 'dismiss-icon', "@click": "dismissLanding()") = icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
= custom_icon('icon_cycle_analytics_splash') .row
.inner-content .col-sm-3.col-xs-12.svg-container
%h4 = custom_icon('icon_cycle_analytics_splash')
Introducing Cycle Analytics .col-sm-8.col-xs-12.inner-content
%p %h4
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project. Introducing Cycle Analytics
%p
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
= link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn' = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= icon("spinner spin", "v-show" => "isLoading") = icon("spinner spin", "v-show" => "isLoading")
...@@ -25,11 +27,11 @@ ...@@ -25,11 +27,11 @@
.content-block .content-block
.container-fluid .container-fluid
.row .row
.col-xs-3.column{"v-for" => "item in summary"} .col-sm-3.col-xs-12.column{"v-for" => "item in analytics.summary"}
%h3.header {{item.value}} %h3.header {{item.value}}
%p.text {{item.title}} %p.text {{item.title}}
.col-xs-3.column .col-sm-3.col-xs-12.column
.dropdown.inline.js-ca-dropdown .dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"} %button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"}
%span.dropdown-label Last 30 days %span.dropdown-label Last 30 days
...@@ -44,14 +46,14 @@ ...@@ -44,14 +46,14 @@
.bordered-box .bordered-box
%ul.content-list %ul.content-list
%li{"v-for" => "item in stats"} %li{"v-for" => "item in analytics.stats"}
.container-fluid .container-fluid
.row .row
.col-xs-10.title-col .col-xs-8.title-col
%p.title %p.title
{{item.title}} {{item.title}}
%p.text %p.text
{{item.description}} {{item.description}}
.col-xs-2.value-col .col-xs-4.value-col
%span %span
{{item.value}} {{item.value}}
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
.disabled-comment.text-center .disabled-comment.text-center
.disabled-comment-text.inline .disabled-comment-text.inline
Please Please
= link_to "register", new_session_path(:user, redirect_to_referer: 'yes') = link_to "sign up", new_session_path(:user, redirect_to_referer: 'yes')
or or
= link_to "login", new_session_path(:user, redirect_to_referer: 'yes') = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment to post a comment
:javascript :javascript
......
%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), branch_id: protected_branch.id } } %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
%td %td
= protected_branch.name = protected_branch.name
- if @project.root_ref?(protected_branch.name) - if @project.root_ref?(protected_branch.name)
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= succeed '.' do = succeed '.' do
More examples are in the More examples are in the
= link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown") = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
.form-group .form-group
= f.label :commit_message, class: 'control-label' = f.label :commit_message, class: 'control-label'
......
- if @project
- counts = milestone_counts(@project.milestones)
%ul.nav-links %ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} %li{class: milestone_class_for_state(params[:state], 'opened', true)}
= link_to milestones_filter_path(state: 'opened') do = link_to milestones_filter_path(state: 'opened') do
Open Open
%li{class: ("active" if params[:state] == 'closed')} - if @project
%span.badge #{counts[:opened]}
%li{class: milestone_class_for_state(params[:state], 'closed')}
= link_to milestones_filter_path(state: 'closed') do = link_to milestones_filter_path(state: 'closed') do
Closed Closed
%li{class: ("active" if params[:state] == 'all')} - if @project
%span.badge #{counts[:closed]}
%li{class: milestone_class_for_state(params[:state], 'all')}
= link_to milestones_filter_path(state: 'all') do = link_to milestones_filter_path(state: 'all') do
All All
- if @project
%span.badge #{counts[:all]}
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40"> <svg xmlns="http://www.w3.org/2000/svg" width="30" height="40" viewBox="5 0 30 40"><path fill="#7E7E7E" fill-rule="evenodd" d="M22 29.535V22.72c4.19-1.21 7.4-5.917 7.4-5.917.34-.446.6-1.247.6-1.795v-4.543C31.196 9.773 32 8.48 32 7c0-2.21-1.79-4-4-4s-4 1.79-4 4c0 1.48.804 2.773 2 3.465v4.243c0 .17-.094.39-.21.502 0 0-1.253 1.203-1.84 1.742C22.5 18.277 21.12 19 20.09 19c-1.042 0-2.473-.74-3.977-2.086-.61-.546-1.746-1.574-1.746-1.574-.2-.182-.367-.555-.367-.83v-4.045C15.196 9.773 16 8.48 16 7c0-2.21-1.79-4-4-4S8 4.79 8 7c0 1.48.804 2.773 2 3.465v4.543c0 .537.274 1.345.61 1.78 0 0 3.25 4.59 7.39 5.88v6.867c-1.196.692-2 1.984-2 3.465 0 2.21 1.79 4 4 4s4-1.79 4-4c0-1.48-.804-2.773-2-3.465zM14 7c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2zm16 0c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2zm-8 26c0-1.105-.895-2-2-2s-2 .895-2 2 .895 2 2 2 2-.895 2-2z"/></svg>
<path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/>
</svg>
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
- if @project - if @project
.row .row
.col-sm-6= render('shared/milestone_expired', milestone: milestone) .col-sm-6= render('shared/milestone_expired', milestone: milestone)
.col-sm-6 .col-sm-6.milestone-actions
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit Edit
......
...@@ -3,11 +3,11 @@ require 'sidekiq/testing' ...@@ -3,11 +3,11 @@ require 'sidekiq/testing'
Sidekiq::Testing.inline! do Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do Gitlab::Seeder.quiet do
project_urls = [ project_urls = [
'https://github.com/documentcloud/underscore.git', 'https://gitlab.com/gitlab-org/gitlab-test.git',
'https://gitlab.com/gitlab-org/gitlab-ce.git', 'https://gitlab.com/gitlab-org/gitlab-ce.git',
'https://gitlab.com/gitlab-org/gitlab-ci.git', 'https://gitlab.com/gitlab-org/gitlab-ci.git',
'https://gitlab.com/gitlab-org/gitlab-shell.git', 'https://gitlab.com/gitlab-org/gitlab-shell.git',
'https://gitlab.com/gitlab-org/gitlab-test.git', 'https://github.com/documentcloud/underscore.git',
'https://github.com/twitter/flight.git', 'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git', 'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git', 'https://github.com/h5bp/html5-boilerplate.git',
...@@ -38,12 +38,7 @@ Sidekiq::Testing.inline! do ...@@ -38,12 +38,7 @@ Sidekiq::Testing.inline! do
] ]
# You can specify how many projects you need during seed execution # You can specify how many projects you need during seed execution
size = if ENV['SIZE'].present? size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
ENV['SIZE'].to_i
else
8
end
project_urls.first(size).each_with_index do |url, i| project_urls.first(size).each_with_index do |url, i|
group_path, project_path = url.split('/')[-2..-1] group_path, project_path = url.split('/')[-2..-1]
......
class AddIndexToLabelsTitle < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index :labels, :title
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160915201649) do ActiveRecord::Schema.define(version: 20160920160832) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -582,6 +582,7 @@ ActiveRecord::Schema.define(version: 20160915201649) do ...@@ -582,6 +582,7 @@ ActiveRecord::Schema.define(version: 20160915201649) do
add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
create_table "ldap_group_links", force: :cascade do |t| create_table "ldap_group_links", force: :cascade do |t|
t.string "cn", null: false t.string "cn", null: false
......
...@@ -10,6 +10,7 @@ following locations: ...@@ -10,6 +10,7 @@ following locations:
- [Award Emoji](award_emoji.md) - [Award Emoji](award_emoji.md)
- [Branches](branches.md) - [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md) - [Builds](builds.md)
- [Build Triggers](build_triggers.md) - [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md) - [Build Variables](build_variables.md)
......
...@@ -13,7 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo ...@@ -13,7 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Test a Scala application](test-scala-application.md) - [Test a Scala application](test-scala-application.md)
- [Using `dpl` as deployment tool](deployment/README.md) - [Using `dpl` as deployment tool](deployment/README.md)
- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml [gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
...@@ -44,7 +44,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. ...@@ -44,7 +44,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project | | **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run |
| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry | | **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returnes the address of the registry tied to the specific project | | **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
......
...@@ -28,6 +28,7 @@ module API ...@@ -28,6 +28,7 @@ module API
helpers ::SentryHelper helpers ::SentryHelper
helpers ::API::Helpers helpers ::API::Helpers
# Keep in alphabetical order
mount ::API::AccessRequests mount ::API::AccessRequests
mount ::API::AwardEmoji mount ::API::AwardEmoji
mount ::API::Branches mount ::API::Branches
...@@ -52,6 +53,7 @@ module API ...@@ -52,6 +53,7 @@ module API
mount ::API::Lint mount ::API::Lint
mount ::API::Members mount ::API::Members
mount ::API::MergeRequests mount ::API::MergeRequests
mount ::API::MergeRequestDiffs
mount ::API::Milestones mount ::API::Milestones
mount ::API::Namespaces mount ::API::Namespaces
mount ::API::Notes mount ::API::Notes
...@@ -76,6 +78,5 @@ module API ...@@ -76,6 +78,5 @@ module API
mount ::API::Triggers mount ::API::Triggers
mount ::API::Users mount ::API::Users
mount ::API::Variables mount ::API::Variables
mount ::API::MergeRequestDiffs
end end
end end
...@@ -7,7 +7,7 @@ module Banzai ...@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist def whitelist
whitelist = super.dup whitelist = super
customize_whitelist(whitelist) customize_whitelist(whitelist)
...@@ -42,58 +42,58 @@ module Banzai ...@@ -42,58 +42,58 @@ module Banzai
# Allow any protocol in `a` elements... # Allow any protocol in `a` elements...
whitelist[:protocols].delete('a') whitelist[:protocols].delete('a')
whitelist[:transformers] = whitelist[:transformers].dup
# ...but then remove links with unsafe protocols # ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links) whitelist[:transformers].push(self.class.remove_unsafe_links)
# Remove `rel` attribute from `a` elements # Remove `rel` attribute from `a` elements
whitelist[:transformers].push(remove_rel) whitelist[:transformers].push(self.class.remove_rel)
# Remove `class` attribute from non-highlight spans # Remove `class` attribute from non-highlight spans
whitelist[:transformers].push(clean_spans) whitelist[:transformers].push(self.class.clean_spans)
whitelist whitelist
end end
def remove_unsafe_links class << self
lambda do |env| def remove_unsafe_links
node = env[:node] lambda do |env|
node = env[:node]
return unless node.name == 'a' return unless node.name == 'a'
return unless node.has_attribute?('href') return unless node.has_attribute?('href')
begin begin
uri = Addressable::URI.parse(node['href']) uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.strip.downcase if uri.scheme uri.scheme = uri.scheme.strip.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme) node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError rescue Addressable::URI::InvalidURIError
node.remove_attribute('href') node.remove_attribute('href')
end
end end
end end
end
def remove_rel def remove_rel
lambda do |env| lambda do |env|
if env[:node_name] == 'a' if env[:node_name] == 'a'
env[:node].remove_attribute('rel') env[:node].remove_attribute('rel')
end
end end
end end
end
def clean_spans def clean_spans
lambda do |env| lambda do |env|
node = env[:node] node = env[:node]
return unless node.name == 'span' return unless node.name == 'span'
return unless node.has_attribute?('class') return unless node.has_attribute?('class')
unless has_ancestor?(node, 'pre') unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? }
node.remove_attribute('class') node.remove_attribute('class')
end end
{ node_whitelist: [node] } { node_whitelist: [node] }
end
end end
end end
end end
......
...@@ -63,10 +63,8 @@ module Gitlab ...@@ -63,10 +63,8 @@ module Gitlab
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
active_db_connection = ActiveRecord::Base.connection.active? rescue false active_db_connection = ActiveRecord::Base.connection.active? rescue false
ENV['USE_DB'] != 'false' &&
active_db_connection && active_db_connection &&
ActiveRecord::Base.connection.table_exists?('application_settings') ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError rescue ActiveRecord::NoDatabaseError
false false
end end
......
...@@ -73,5 +73,7 @@ excluded_attributes: ...@@ -73,5 +73,7 @@ excluded_attributes:
methods: methods:
statuses: statuses:
- :type - :type
services:
- :type
merge_request_diff: merge_request_diff:
- :utf8_st_diffs - :utf8_st_diffs
...@@ -9,19 +9,22 @@ module Gitlab ...@@ -9,19 +9,22 @@ module Gitlab
SIDEKIQ_NAMESPACE = 'resque:gitlab' SIDEKIQ_NAMESPACE = 'resque:gitlab'
MAILROOM_NAMESPACE = 'mail_room:gitlab' MAILROOM_NAMESPACE = 'mail_room:gitlab'
DEFAULT_REDIS_URL = 'redis://localhost:6379' DEFAULT_REDIS_URL = 'redis://localhost:6379'
CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__)
# To be thread-safe we must be careful when writing the class instance # To be thread-safe we must be careful when writing the class instance
# variables @url and @pool. Because @pool depends on @url we need two # variables @_raw_config and @pool. Because @pool depends on @_raw_config we need two
# mutexes to prevent deadlock. # mutexes to prevent deadlock.
PARAMS_MUTEX = Mutex.new RAW_CONFIG_MUTEX = Mutex.new
POOL_MUTEX = Mutex.new POOL_MUTEX = Mutex.new
private_constant :PARAMS_MUTEX, :POOL_MUTEX private_constant :RAW_CONFIG_MUTEX, :POOL_MUTEX
class << self class << self
# Do NOT cache in an instance variable. Result may be mutated by caller.
def params def params
@params || PARAMS_MUTEX.synchronize { @params = new.params } new.params
end end
# Do NOT cache in an instance variable. Result may be mutated by caller.
# @deprecated Use .params instead to get sentinel support # @deprecated Use .params instead to get sentinel support
def url def url
new.url new.url
...@@ -36,8 +39,17 @@ module Gitlab ...@@ -36,8 +39,17 @@ module Gitlab
@pool.with { |redis| yield redis } @pool.with { |redis| yield redis }
end end
def reset_params! def _raw_config
@params = nil return @_raw_config if defined?(@_raw_config)
RAW_CONFIG_MUTEX.synchronize do
begin
@_raw_config = File.read(CONFIG_FILE).freeze
rescue Errno::ENOENT
@_raw_config = false
end
end
@_raw_config
end end
end end
...@@ -83,12 +95,7 @@ module Gitlab ...@@ -83,12 +95,7 @@ module Gitlab
end end
def fetch_config def fetch_config
file = config_file self.class._raw_config ? YAML.load(self.class._raw_config)[@rails_env] : false
File.exist?(file) ? YAML.load_file(file)[@rails_env] : false
end
def config_file
File.expand_path('../../../config/resque.yml', __FILE__)
end end
end end
end end
...@@ -60,7 +60,7 @@ module Gitlab ...@@ -60,7 +60,7 @@ module Gitlab
def send_git_diff(repository, diff_refs) def send_git_diff(repository, diff_refs)
params = { params = {
'RepoPath' => repository.path_to_repo, 'RepoPath' => repository.path_to_repo,
'ShaFrom' => diff_refs.start_sha, 'ShaFrom' => diff_refs.base_sha,
'ShaTo' => diff_refs.head_sha 'ShaTo' => diff_refs.head_sha
} }
...@@ -73,7 +73,7 @@ module Gitlab ...@@ -73,7 +73,7 @@ module Gitlab
def send_git_patch(repository, diff_refs) def send_git_patch(repository, diff_refs)
params = { params = {
'RepoPath' => repository.path_to_repo, 'RepoPath' => repository.path_to_repo,
'ShaFrom' => diff_refs.start_sha, 'ShaFrom' => diff_refs.base_sha,
'ShaTo' => diff_refs.head_sha 'ShaTo' => diff_refs.head_sha
} }
...@@ -107,15 +107,15 @@ module Gitlab ...@@ -107,15 +107,15 @@ module Gitlab
bytes bytes
end end
end end
def write_secret def write_secret
bytes = SecureRandom.random_bytes(SECRET_LENGTH) bytes = SecureRandom.random_bytes(SECRET_LENGTH)
File.open(secret_path, 'w:BINARY', 0600) do |f| File.open(secret_path, 'w:BINARY', 0600) do |f|
f.chmod(0600) f.chmod(0600)
f.write(Base64.strict_encode64(bytes)) f.write(Base64.strict_encode64(bytes))
end end
end end
def verify_api_request!(request_headers) def verify_api_request!(request_headers)
JWT.decode( JWT.decode(
request_headers[INTERNAL_API_REQUEST_HEADER], request_headers[INTERNAL_API_REQUEST_HEADER],
...@@ -128,7 +128,7 @@ module Gitlab ...@@ -128,7 +128,7 @@ module Gitlab
def secret_path def secret_path
Rails.root.join('.gitlab_workhorse_secret') Rails.root.join('.gitlab_workhorse_secret')
end end
protected protected
def encode(hash) def encode(hash)
......
...@@ -3,10 +3,15 @@ FactoryGirl.define do ...@@ -3,10 +3,15 @@ FactoryGirl.define do
title title
project project
trait :active do
state "active"
end
trait :closed do trait :closed do
state :closed state "closed"
end end
factory :active_milestone, traits: [:active]
factory :closed_milestone, traits: [:closed] factory :closed_milestone, traits: [:closed]
end end
end end
require 'spec_helper' require 'spec_helper'
describe "Search", feature: true do describe "Search", feature: true do
include WaitForAjax
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, assignee: user) } let!(:issue) { create(:issue, project: project, assignee: user) }
...@@ -16,6 +18,36 @@ describe "Search", feature: true do ...@@ -16,6 +18,36 @@ describe "Search", feature: true do
expect(page).not_to have_selector('.search') expect(page).not_to have_selector('.search')
end end
context 'search filters', js: true do
let(:group) { create(:group) }
before do
group.add_owner(user)
end
it 'shows group name after filtering' do
find('.js-search-group-dropdown').click
wait_for_ajax
page.within '.search-holder' do
click_link group.name
end
expect(find('.js-search-group-dropdown')).to have_content(group.name)
end
it 'shows project name after filtering' do
page.within('.project-filter') do
find('.js-search-project-dropdown').click
wait_for_ajax
click_link project.name_with_namespace
end
expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
end
end
describe 'searching for Projects' do describe 'searching for Projects' do
it 'finds a project' do it 'finds a project' do
page.within '.search-holder' do page.within '.search-holder' do
......
require 'spec_helper'
describe MilestonesHelper do
describe '#milestone_counts' do
let(:project) { FactoryGirl.create(:project) }
let(:counts) { helper.milestone_counts(project.milestones) }
context 'when there are milestones' do
let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) }
it 'returns the correct counts' do
expect(counts).to eq(opened: 2, closed: 1, all: 3)
end
end
context 'when there are only milestones of one type' do
let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
it 'returns the correct counts' do
expect(counts).to eq(opened: 2, closed: 0, all: 2)
end
end
context 'when there are no milestones' do
it 'returns the correct counts' do
expect(counts).to eq(opened: 0, closed: 0, all: 0)
end
end
end
end
...@@ -6918,6 +6918,7 @@ ...@@ -6918,6 +6918,7 @@
"note_events": true, "note_events": true,
"build_events": true, "build_events": true,
"category": "issue_tracker", "category": "issue_tracker",
"type": "CustomIssueTrackerService",
"default": true, "default": true,
"wiki_page_events": true "wiki_page_events": true
}, },
......
...@@ -107,6 +107,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do ...@@ -107,6 +107,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(Label.first.label_links.first.target).not_to be_nil expect(Label.first.label_links.first.target).not_to be_nil
end end
it 'restores the correct service' do
restored_project_json
expect(CustomIssueTrackerService.first).not_to be_nil
end
context 'Merge requests' do context 'Merge requests' do
before do before do
restored_project_json restored_project_json
......
...@@ -111,6 +111,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -111,6 +111,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
end end
it 'saves the correct service type' do
expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
end
it 'has project feature' do it 'has project feature' do
project_feature = saved_project_json['project_feature'] project_feature = saved_project_json['project_feature']
expect(project_feature).not_to be_empty expect(project_feature).not_to be_empty
...@@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do ...@@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
commit_id: ci_pipeline.sha) commit_id: ci_pipeline.sha)
create(:event, target: milestone, project: project, action: Event::CREATED, author: user) create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
......
...@@ -3,19 +3,27 @@ require 'spec_helper' ...@@ -3,19 +3,27 @@ require 'spec_helper'
describe Gitlab::Redis do describe Gitlab::Redis do
let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s } let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s }
before(:each) { described_class.reset_params! } before(:each) { clear_raw_config }
after(:each) { described_class.reset_params! } after(:each) { clear_raw_config }
describe '.params' do describe '.params' do
subject { described_class.params } subject { described_class.params }
it 'withstands mutation' do
params1 = described_class.params
params2 = described_class.params
params1[:foo] = :bar
expect(params2).not_to have_key(:foo)
end
context 'when url contains unix socket reference' do context 'when url contains unix socket reference' do
let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s } let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s } let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
context 'with old format' do context 'with old format' do
it 'returns path key instead' do it 'returns path key instead' do
expect_any_instance_of(described_class).to receive(:config_file) { config_old } stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(path: '/path/to/old/redis.sock') is_expected.to include(path: '/path/to/old/redis.sock')
is_expected.not_to have_key(:url) is_expected.not_to have_key(:url)
...@@ -24,7 +32,7 @@ describe Gitlab::Redis do ...@@ -24,7 +32,7 @@ describe Gitlab::Redis do
context 'with new format' do context 'with new format' do
it 'returns path key instead' do it 'returns path key instead' do
expect_any_instance_of(described_class).to receive(:config_file) { config_new } stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(path: '/path/to/redis.sock') is_expected.to include(path: '/path/to/redis.sock')
is_expected.not_to have_key(:url) is_expected.not_to have_key(:url)
...@@ -38,7 +46,7 @@ describe Gitlab::Redis do ...@@ -38,7 +46,7 @@ describe Gitlab::Redis do
context 'with old format' do context 'with old format' do
it 'returns hash with host, port, db, and password' do it 'returns hash with host, port, db, and password' do
expect_any_instance_of(described_class).to receive(:config_file) { config_old } stub_const("#{described_class}::CONFIG_FILE", config_old)
is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99) is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
is_expected.not_to have_key(:url) is_expected.not_to have_key(:url)
...@@ -47,7 +55,7 @@ describe Gitlab::Redis do ...@@ -47,7 +55,7 @@ describe Gitlab::Redis do
context 'with new format' do context 'with new format' do
it 'returns hash with host, port, db, and password' do it 'returns hash with host, port, db, and password' do
expect_any_instance_of(described_class).to receive(:config_file) { config_new } stub_const("#{described_class}::CONFIG_FILE", config_new)
is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99) is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
is_expected.not_to have_key(:url) is_expected.not_to have_key(:url)
...@@ -56,6 +64,30 @@ describe Gitlab::Redis do ...@@ -56,6 +64,30 @@ describe Gitlab::Redis do
end end
end end
describe '.url' do
it 'withstands mutation' do
url1 = described_class.url
url2 = described_class.url
url1 << 'foobar'
expect(url2).not_to end_with('foobar')
end
end
describe '._raw_config' do
subject { described_class._raw_config }
it 'should be frozen' do
expect(subject).to be_frozen
end
it 'returns false when the file does not exist' do
stub_const("#{described_class}::CONFIG_FILE", '/var/empty/doesnotexist')
expect(subject).to eq(false)
end
end
describe '#raw_config_hash' do describe '#raw_config_hash' do
it 'returns default redis url when no config file is present' do it 'returns default redis url when no config file is present' do
expect(subject).to receive(:fetch_config) { false } expect(subject).to receive(:fetch_config) { false }
...@@ -71,9 +103,15 @@ describe Gitlab::Redis do ...@@ -71,9 +103,15 @@ describe Gitlab::Redis do
describe '#fetch_config' do describe '#fetch_config' do
it 'returns false when no config file is present' do it 'returns false when no config file is present' do
allow(File).to receive(:exist?).with(redis_config) { false } allow(described_class).to receive(:_raw_config) { false }
expect(subject.send(:fetch_config)).to be_falsey expect(subject.send(:fetch_config)).to be_falsey
end end
end end
def clear_raw_config
described_class.remove_instance_variable(:@_raw_config)
rescue NameError
# raised if @_raw_config was not set; ignore
end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Workhorse, lib: true do describe Gitlab::Workhorse, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:subject) { Gitlab::Workhorse } let(:repository) { project.repository }
def decode_workhorse_header(array)
key, value = array
command, encoded_params = value.split(":")
params = JSON.parse(Base64.urlsafe_decode64(encoded_params))
[key, command, params]
end
describe ".send_git_archive" do describe ".send_git_archive" do
context "when the repository doesn't have an archive file path" do context "when the repository doesn't have an archive file path" do
...@@ -11,11 +19,37 @@ describe Gitlab::Workhorse, lib: true do ...@@ -11,11 +19,37 @@ describe Gitlab::Workhorse, lib: true do
end end
it "raises an error" do it "raises an error" do
expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError) expect { described_class.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
end end
end end
end end
describe '.send_git_patch' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
subject { described_class.send_git_patch(repository, diff_refs) }
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("git-format-patch")
expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
end
end
describe '.send_git_diff' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
subject { described_class.send_git_patch(repository, diff_refs) }
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("git-format-patch")
expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head")
end
end
describe ".secret" do describe ".secret" do
subject { described_class.secret } subject { described_class.secret }
......
...@@ -50,8 +50,9 @@ describe GlobalMilestone, models: true do ...@@ -50,8 +50,9 @@ describe GlobalMilestone, models: true do
milestone1_project2, milestone1_project2,
milestone1_project3, milestone1_project3,
] ]
milestones_relation = Milestone.where(id: milestones.map(&:id))
@global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones) @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones_relation)
end end
it 'has exactly one group milestone' do it 'has exactly one group milestone' do
...@@ -67,7 +68,7 @@ describe GlobalMilestone, models: true do ...@@ -67,7 +68,7 @@ describe GlobalMilestone, models: true do
let(:milestone) { create(:milestone, title: "git / test", project: project1) } let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'strips out slashes and spaces' do it 'strips out slashes and spaces' do
global_milestone = GlobalMilestone.new(milestone.title, [milestone]) global_milestone = GlobalMilestone.new(milestone.title, Milestone.where(id: milestone.id))
expect(global_milestone.safe_title).to eq('git-test') expect(global_milestone.safe_title).to eq('git-test')
end end
......
...@@ -25,5 +25,21 @@ describe CustomIssueTrackerService, models: true do ...@@ -25,5 +25,21 @@ describe CustomIssueTrackerService, models: true do
it { is_expected.not_to validate_presence_of(:issues_url) } it { is_expected.not_to validate_presence_of(:issues_url) }
it { is_expected.not_to validate_presence_of(:new_issue_url) } it { is_expected.not_to validate_presence_of(:new_issue_url) }
end end
context 'title' do
let(:issue_tracker) { described_class.new(properties: {}) }
it 'sets a default title' do
issue_tracker.title = nil
expect(issue_tracker.title).to eq('Custom Issue Tracker')
end
it 'sets the custom title' do
issue_tracker.title = 'test title'
expect(issue_tracker.title).to eq('test title')
end
end
end end
end end
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