summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2017-05-18 15:33:52 +0800
committerLin Jen-Shin <godfat@godfat.org>2017-05-18 15:33:52 +0800
commit54a8cbddb16338bf48aa04143566ccd33646d769 (patch)
tree82fe3eb4b1b6a8bade602f8f17e488564e6cd2a7
parent226b517ce70674a1c25084b8535dd6869addb21e (diff)
parentc9e61fa3cd8eeb63aced9a55039dc4d17616cd42 (diff)
downloadgitlab-ce-54a8cbddb16338bf48aa04143566ccd33646d769.tar.gz
Merge remote-tracking branch 'upstream/master' into rename-builds-controller
* upstream/master: (31 commits) Remove 'no changes' entries from changelog Check if OLD is set when migrating issue assignees Fix data migration from trigger schedules Replace EFS section in AWS guide Add warning about AWS EFS and performance Consolidate opening text from about.gitlab.com and add active/passive note Fix invalid object reference in ee_compat_check script Fix Ordered Task List Items Add auxiliary viewer for README Update fe_guide testing.md Add auxiliary blob viewer for CHANGELOG Add spec for last commit info when browsing repository files Show last commit for current tree on tree page Use same last commit widget on project homepage and tree view Fix unassigned checkmark Add missing changelog for iPython notebook rendering feature Convert fa-history to svg; tweak alignment Get rid of pluck in app/services/members/authorized_destroy_service.rb Removes duplicate environment variable in documentation Fixed spacing issues in issue sidebar ...
-rw-r--r--CHANGELOG.md4
-rw-r--r--app/assets/javascripts/blob/viewer/index.js1
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js6
-rw-r--r--app/assets/javascripts/users_select.js21
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js47
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss25
-rw-r--r--app/assets/stylesheets/framework/typography.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss9
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss16
-rw-r--r--app/assets/stylesheets/pages/projects.scss30
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/tree_controller.rb2
-rw-r--r--app/helpers/blob_helper.rb15
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/models/blob.rb6
-rw-r--r--app/models/blob_viewer/auxiliary.rb6
-rw-r--r--app/models/blob_viewer/base.rb2
-rw-r--r--app/models/blob_viewer/changelog.rb16
-rw-r--r--app/models/blob_viewer/contributing.rb10
-rw-r--r--app/models/blob_viewer/license.rb2
-rw-r--r--app/models/blob_viewer/readme.rb14
-rw-r--r--app/models/user.rb10
-rw-r--r--app/serializers/merge_request_entity.rb8
-rw-r--r--app/services/issuable_base_service.rb9
-rw-r--r--app/services/members/authorized_destroy_service.rb10
-rw-r--r--app/views/admin/health_check/show.html.haml19
-rw-r--r--app/views/projects/_files.html.haml10
-rw-r--r--app/views/projects/_last_commit.html.haml12
-rw-r--r--app/views/projects/blob/_auxiliary_viewer.html.haml5
-rw-r--r--app/views/projects/blob/_blob.html.haml8
-rw-r--r--app/views/projects/blob/viewers/_changelog.html.haml4
-rw-r--r--app/views/projects/blob/viewers/_contributing.html.haml9
-rw-r--r--app/views/projects/blob/viewers/_readme.html.haml4
-rw-r--r--app/views/projects/boards/components/sidebar/_assignee.html.haml3
-rw-r--r--app/views/projects/show.html.haml5
-rw-r--r--app/views/projects/tree/show.html.haml11
-rw-r--r--app/views/shared/icons/_icon_history.svg1
-rw-r--r--app/views/shared/icons/_mr_widget_empty_state.svg1
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--changelogs/unreleased/31483-ordered-task-list.yml4
-rw-r--r--changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml4
-rw-r--r--changelogs/unreleased/counters_cache_invalidation.yml4
-rw-r--r--changelogs/unreleased/dm-tree-last-commit.yml4
-rw-r--r--changelogs/unreleased/get_rid_of_pluck.yml4
-rw-r--r--changelogs/unreleased/update-admin-health-page.yml5
-rw-r--r--db/migrate/20170516153305_migrate_assignee_to_separate_table.rb2
-rw-r--r--db/post_migrate/20170425121605_migrate_trigger_schedules_to_pipeline_schedules.rb7
-rw-r--r--doc/administration/environment_variables.md1
-rw-r--r--doc/administration/high_availability/README.md18
-rw-r--r--doc/administration/high_availability/nfs.md19
-rw-r--r--doc/development/fe_guide/img/testing_triangle.pngbin0 -> 11836 bytes
-rw-r--r--doc/development/fe_guide/testing.md39
-rw-r--r--doc/university/high-availability/aws/README.md22
-rw-r--r--features/steps/shared/project.rb4
-rw-r--r--lib/gitlab/ee_compat_check.rb8
-rw-r--r--spec/features/dashboard/issues_spec.rb11
-rw-r--r--spec/features/projects/files/browse_files_spec.rb12
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request.json1
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js16
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb27
-rw-r--r--spec/models/user_spec.rb28
-rw-r--r--spec/serializers/merge_request_entity_spec.rb17
-rw-r--r--spec/views/projects/_last_commit.html.haml_spec.rb22
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb6
66 files changed, 479 insertions, 180 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 38de411ebb7..2843e3a3955 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,6 @@ entry.
## 9.1.4 (2017-05-12)
-- No changes.
-- No changes.
-- No changes.
- Fix error on CI/CD Settings page related to invalid pipeline trigger. !10948 (dosuken123)
- Sort the network graph both by commit date and topographically. !11057
- Fix cross referencing for private and internal projects. !11243
@@ -56,6 +53,7 @@ entry.
## 9.1.0 (2017-04-22)
+- Add Jupyter notebook rendering !10017
- Added merge requests empty state. !7342
- Add option to start a new resolvable discussion in an MR. !7527
- Hide form inputs for group member without editing rights. !7816
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 849da633c89..54b85e47358 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -114,6 +114,7 @@ export default class BlobViewer {
$(viewer).syntaxHighlight();
this.$fileHolder.trigger('highlight:line');
+ gl.utils.handleLocationHash();
this.toggleCopyButtonState();
})
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 082025eabaa..9e2102eb077 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -45,6 +45,12 @@ gl.issueBoards.BoardSidebar = Vue.extend({
detail: {
handler () {
if (this.issue.id !== this.detail.issue.id) {
+ $('.block.assignee')
+ .find('input:not(.js-vue)[name="issue[assignee_ids][]"]')
+ .each((i, el) => {
+ $(el).remove();
+ });
+
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu();
});
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 8119a8cd000..1724ca86b61 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -50,7 +50,11 @@ function UsersSelect(currentUser, els) {
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
selectedIdDefault = (defaultNullUser && showNullUser) ? 0 : null;
- selectedId = $dropdown.data('selected') || selectedIdDefault;
+ selectedId = $dropdown.data('selected');
+
+ if (selectedId === undefined) {
+ selectedId = selectedIdDefault;
+ }
const assignYourself = function () {
const unassignedSelected = $dropdown.closest('.selectbox')
@@ -423,8 +427,9 @@ function UsersSelect(currentUser, els) {
},
opened: function(e) {
const $el = $(e.currentTarget);
- if ($dropdown.hasClass('js-issue-board-sidebar')) {
- selectedId = parseInt($dropdown[0].dataset.selected, 10) || selectedIdDefault;
+ const selected = getSelected();
+ if ($dropdown.hasClass('js-issue-board-sidebar') && selected.length === 0) {
+ this.addInput($dropdown.data('field-name'), 0, {});
}
$el.find('.is-active').removeClass('is-active');
@@ -432,8 +437,10 @@ function UsersSelect(currentUser, els) {
$el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
}
- if ($selectbox[0]) {
+ if (selected.length > 0) {
getSelected().forEach(selectedId => highlightSelected(selectedId));
+ } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
+ highlightSelected(0);
} else {
highlightSelected(selectedId);
}
@@ -444,15 +451,19 @@ function UsersSelect(currentUser, els) {
username = user.username ? "@" + user.username : "";
avatar = user.avatar_url ? user.avatar_url : false;
- let selected = user.id === parseInt(selectedId, 10);
+ let selected = false;
if (this.multiSelect) {
+ selected = getSelected().find(u => user.id === u);
+
const fieldName = this.fieldName;
const field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']");
if (field.length) {
selected = true;
}
+ } else {
+ selected = user.id === selectedId;
}
img = "";
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
index 8c4535f1337..375a382615a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge.js
@@ -1,17 +1,42 @@
+import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
+
export default {
name: 'MRWidgetNothingToMerge',
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return { emptyStateSVG };
+ },
template: `
- <div class="mr-widget-body">
- <button
- type="button"
- class="btn btn-success btn-small"
- disabled="true">
- Merge
- </button>
- <span class="bold">
- There is nothing to merge from source branch into target branch.
- Please push new commits or use a different branch.
- </span>
+ <div class="mr-widget-body empty-state">
+ <div class="row">
+ <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
+ <span v-html="emptyStateSVG"></span>
+ </div>
+ <div class="text col-sm-7 col-sm-pull-5 col-xs-12">
+ <span>
+ Merge requests are a place to propose changes you have made to a project
+ and discuss those changes with others.
+ </span>
+ <p>
+ Interested parties can even contribute by pushing commits if they want to.
+ </p>
+ <p>
+ Currently there are no changes in this merge request's source branch.
+ Please push new commits or use a different branch.
+ </p>
+ <a
+ v-if="mr.newBlobPath"
+ :href="mr.newBlobPath"
+ class="btn btn-inverted btn-save">
+ Create file
+ </a>
+ </div>
+ </div>
</div>
`,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 05e67706983..06661b930e3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -58,6 +58,7 @@ export default class MergeRequestStore {
this.statusPath = data.status_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
+ this.newBlobPath = data.new_blob_path;
this.createIssueToResolveDiscussionsPath = data.create_issue_to_resolve_discussions_path;
this.mergeCheckPath = data.merge_check_path;
this.mergeActionsContentPath = data.commit_change_content_path;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 5c9b71a452c..5ab48b6c874 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -97,7 +97,7 @@
.fa-chevron-down {
font-size: $dropdown-chevron-size;
position: relative;
- top: -3px;
+ top: -2px;
margin-left: 5px;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index e624d0d951e..637731cc479 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -283,17 +283,10 @@
.filtered-search-history-dropdown-toggle-button {
flex: 1;
width: auto;
- padding-right: 10px;
-
border-radius: 0;
- border-top: 0;
- border-left: 0;
- border-bottom: 0;
+ border: 0;
border-right: 1px solid $border-color;
-
color: $gl-text-color-secondary;
- line-height: 1;
-
transition: color 0.1s linear;
&:hover,
@@ -301,6 +294,17 @@
color: $gl-text-color;
border-color: $dropdown-input-focus-border;
outline: none;
+
+ svg {
+ fill: $gl-text-color;
+ }
+ }
+
+ svg {
+ height: 14px;
+ width: 14px;
+ fill: $gl-text-color-secondary;
+ vertical-align: middle;
}
.dropdown-toggle-text {
@@ -312,11 +316,6 @@
color: inherit;
}
}
-
- .fa {
- position: static;
- }
-
}
.filtered-search-history-dropdown {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index a7c6cbaae21..1a4c3d6b370 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -169,14 +169,14 @@
}
ul.task-list {
- li.task-list-item {
+ > li.task-list-item {
list-style-type: none;
position: relative;
min-height: 22px;
padding-left: 28px;
margin-left: 0 !important;
- input.task-list-item-checkbox {
+ > input.task-list-item-checkbox {
position: absolute;
left: 8px;
top: 5px;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index ed4e5811b56..65ac321d2c6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -195,7 +195,7 @@
right: 0;
transition: width .3s;
background: $gray-light;
- padding: 10px 20px;
+ padding: 0 20px;
z-index: 200;
overflow: hidden;
@@ -219,6 +219,10 @@
}
}
+ .issuable-sidebar-header {
+ padding-top: 10px;
+ }
+
.assign-yourself .btn-link {
padding-left: 0;
}
@@ -272,11 +276,10 @@
}
width: $gutter_collapsed_width;
- padding-top: 0;
+ padding: 0;
.block {
width: $gutter_collapsed_width - 2px;
- margin-left: -19px;
padding: 15px 0 0;
border-bottom: none;
overflow: hidden;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index fa9d05ee8fd..e5adca658ba 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -353,6 +353,22 @@
margin-top: 10px;
margin-left: 12px;
}
+
+ &.empty-state {
+ .artwork {
+ margin-bottom: $gl-padding;
+ }
+
+ .text {
+ span {
+ font-weight: bold;
+ }
+
+ p {
+ margin-top: $gl-padding;
+ }
+ }
+ }
}
.mr-widget-footer {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index ed4a5474034..20ed5cce4f1 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -639,36 +639,6 @@ pre.light-well {
}
}
-.project-last-commit {
- background-color: $gray-light;
- border: 1px solid $border-color;
- border-radius: $border-radius-base;
- padding: 12px;
-
- @media (min-width: $screen-sm-min) {
- margin-top: $gl-padding;
- }
-
- .ci-status {
- margin-right: $gl-padding;
- }
-
- .commit-row-message {
- color: $gl-text-color;
- }
-
- .commit-sha {
- margin-right: 5px;
- font-weight: 600;
- }
-
- .commit-author-link {
- .commit-author-name {
- font-weight: 600;
- }
- }
-}
-
.project-show-readme {
.row-content-block {
background-color: inherit;
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 9489bbddfc4..87721fbe2f5 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -42,6 +42,8 @@ class Projects::BlobController < Projects::ApplicationController
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
+ @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+
render 'show'
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 3ce65b29b3c..f8eb8e00a5d 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -24,6 +24,8 @@ class Projects::TreeController < Projects::ApplicationController
end
end
+ @last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
+
respond_to do |format|
format.html
# Disable cache so browser history works
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 6a72b0059ea..11c972c6563 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -278,4 +278,19 @@ module BlobHelper
options
end
+
+ def contribution_options(project)
+ options = []
+
+ if can?(current_user, :create_issue, project)
+ options << link_to("submit an issue", new_namespace_project_issue_path(project.namespace, project))
+ end
+
+ merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
+ if merge_project
+ options << link_to("create a merge request", new_namespace_project_merge_request_path(project.namespace, project))
+ end
+
+ options
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 6d6f1361bf9..d59d51905a6 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -91,7 +91,7 @@ module CommitsHelper
end
def link_to_browse_code(project, commit)
- return unless current_controller?(:projects, :commits)
+ return unless current_controller?(:commits)
if @path.blank?
return link_to(
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 63a81c0e3bd..8e25ba590c7 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -39,7 +39,11 @@ class Blob < SimpleDelegator
AUXILIARY_VIEWERS = [
BlobViewer::GitlabCiYml,
BlobViewer::RouteMap,
- BlobViewer::License
+
+ BlobViewer::Readme,
+ BlobViewer::License,
+ BlobViewer::Contributing,
+ BlobViewer::Changelog
].freeze
attr_reader :project
diff --git a/app/models/blob_viewer/auxiliary.rb b/app/models/blob_viewer/auxiliary.rb
index cd6e596ed60..07a207730cf 100644
--- a/app/models/blob_viewer/auxiliary.rb
+++ b/app/models/blob_viewer/auxiliary.rb
@@ -2,11 +2,17 @@ module BlobViewer
module Auxiliary
extend ActiveSupport::Concern
+ include Gitlab::Allowable
+
included do
self.loading_partial_name = 'loading_auxiliary'
self.type = :auxiliary
self.overridable_max_size = 100.kilobytes
self.max_size = 100.kilobytes
end
+
+ def visible_to?(current_user)
+ true
+ end
end
end
diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb
index c7b8fbfc56a..26a3778c2a3 100644
--- a/app/models/blob_viewer/base.rb
+++ b/app/models/blob_viewer/base.rb
@@ -11,6 +11,8 @@ module BlobViewer
attr_reader :blob
attr_accessor :override_max_size
+ delegate :project, to: :blob
+
def initialize(blob)
@blob = blob
end
diff --git a/app/models/blob_viewer/changelog.rb b/app/models/blob_viewer/changelog.rb
new file mode 100644
index 00000000000..0464ae27f71
--- /dev/null
+++ b/app/models/blob_viewer/changelog.rb
@@ -0,0 +1,16 @@
+module BlobViewer
+ class Changelog < Base
+ include Auxiliary
+ include Static
+
+ self.partial_name = 'changelog'
+ self.file_types = %i(changelog)
+ self.binary = false
+
+ def render_error
+ return if project.repository.tag_count > 0
+
+ :no_tags
+ end
+ end
+end
diff --git a/app/models/blob_viewer/contributing.rb b/app/models/blob_viewer/contributing.rb
new file mode 100644
index 00000000000..fbd1dd48697
--- /dev/null
+++ b/app/models/blob_viewer/contributing.rb
@@ -0,0 +1,10 @@
+module BlobViewer
+ class Contributing < Base
+ include Auxiliary
+ include Static
+
+ self.partial_name = 'contributing'
+ self.file_types = %i(contributing)
+ self.binary = false
+ end
+end
diff --git a/app/models/blob_viewer/license.rb b/app/models/blob_viewer/license.rb
index 6751ea15a5d..57355f2c3aa 100644
--- a/app/models/blob_viewer/license.rb
+++ b/app/models/blob_viewer/license.rb
@@ -8,7 +8,7 @@ module BlobViewer
self.binary = false
def license
- blob.project.repository.license
+ project.repository.license
end
def render_error
diff --git a/app/models/blob_viewer/readme.rb b/app/models/blob_viewer/readme.rb
new file mode 100644
index 00000000000..75c373a03bb
--- /dev/null
+++ b/app/models/blob_viewer/readme.rb
@@ -0,0 +1,14 @@
+module BlobViewer
+ class Readme < Base
+ include Auxiliary
+ include Static
+
+ self.partial_name = 'readme'
+ self.file_types = %i(readme)
+ self.binary = false
+
+ def visible_to?(current_user)
+ can?(current_user, :read_wiki, project)
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index c7160a6af14..088a7cb83d5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -930,10 +930,18 @@ class User < ActiveRecord::Base
end
def invalidate_cache_counts
- Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
+ invalidate_issue_cache_counts
+ invalidate_merge_request_cache_counts
+ end
+
+ def invalidate_issue_cache_counts
Rails.cache.delete(['users', id, 'assigned_open_issues_count'])
end
+ def invalidate_merge_request_cache_counts
+ Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
+ end
+
def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
TodosFinder.new(self, state: :done).execute.count
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index a2542c54f7a..a49f4d834cd 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -97,6 +97,14 @@ class MergeRequestEntity < IssuableEntity
presenter(merge_request).target_branch_commits_path
end
+ expose :new_blob_path do |merge_request|
+ if can?(current_user, :push_code, merge_request.project)
+ namespace_project_new_blob_path(merge_request.project.namespace,
+ merge_request.project,
+ merge_request.source_branch)
+ end
+ end
+
expose :conflict_resolution_path do |merge_request|
presenter(merge_request).conflict_resolution_path
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index dc2ab99b982..5ad4b2a9adf 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -179,6 +179,7 @@ class IssuableBaseService < BaseService
issuable.create_cross_references!(current_user)
execute_hooks(issuable)
issuable.assignees.each(&:invalidate_cache_counts)
+ invalidate_cache_counts(issuable.assignees, issuable)
end
issuable
@@ -237,7 +238,7 @@ class IssuableBaseService < BaseService
if old_assignees != issuable.assignees
assignees = old_assignees + issuable.assignees.to_a
- assignees.compact.each(&:invalidate_cache_counts)
+ invalidate_cache_counts(assignees.compact, issuable)
end
after_update(issuable)
@@ -330,4 +331,10 @@ class IssuableBaseService < BaseService
create_labels_note(issuable, old_labels) if issuable.labels != old_labels
end
+
+ def invalidate_cache_counts(users, issuable)
+ users.each do |user|
+ user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts")
+ end
+ end
end
diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb
index 9e84e2a8f62..0f94504625a 100644
--- a/app/services/members/authorized_destroy_service.rb
+++ b/app/services/members/authorized_destroy_service.rb
@@ -26,10 +26,14 @@ module Members
def unassign_issues_and_merge_requests(member)
if member.is_a?(GroupMember)
- issue_ids = IssuesFinder.new(user, group_id: member.source_id, assignee_id: member.user_id).
- execute.pluck(:id)
+ issues = Issue.unscoped.select(1).
+ joins(:project).
+ where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id)
- IssueAssignee.delete_all(issue_id: issue_ids, user_id: member.user_id)
+ # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
+ IssueAssignee.unscoped.
+ where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues).
+ delete_all
MergeRequestsFinder.new(user, group_id: member.source_id, assignee_id: member.user_id).
execute.
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index 6a208d76a38..4deccf4aa93 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -16,24 +16,15 @@
= icon('spinner')
Reset health check access token
%p.light
- Health information can be retrieved as plain text, JSON, or XML using:
+ Health information can be retrieved from the following endpoints. More information is available
+ = link_to 'here', help_page_path('user/admin_area/monitoring/health_check')
%ul
%li
- %code= health_check_url(token: current_application_settings.health_check_access_token)
+ %code= readiness_url(token: current_application_settings.health_check_access_token)
%li
- %code= health_check_url(token: current_application_settings.health_check_access_token, format: :json)
+ %code= liveness_url(token: current_application_settings.health_check_access_token)
%li
- %code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml)
-
- %p.light
- You can also ask for the status of specific services:
- %ul
- %li
- %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache)
- %li
- %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database)
- %li
- %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations)
+ %code= metrics_url(token: current_application_settings.health_check_access_token)
%hr
.panel.panel-default
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 96c2fa87f45..426085b3e1c 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -1,6 +1,14 @@
+- commit = local_assigns.fetch(:commit) { @repository.commit }
+- ref = local_assigns.fetch(:ref) { current_ref }
+- project = local_assigns.fetch(:project) { @project }
#tree-holder.tree-holder.clearfix
.nav-block
= render 'projects/tree/tree_header', tree: @tree
- = render 'projects/tree/tree_content', tree: @tree
+ - if commit
+ .info-well.hidden-xs.project-last-commit.append-bottom-default
+ .well-segment
+ %ul.blob-commit-info
+ = render 'projects/commits/commit', commit: commit, ref: ref, project: project
+ = render 'projects/tree/tree_content', tree: @tree
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
deleted file mode 100644
index d104cc7c1a3..00000000000
--- a/app/views/projects/_last_commit.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-- ref = local_assigns.fetch(:ref)
-- status = commit.status(ref)
-- if status
- = link_to pipelines_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{status}" do
- = ci_icon_for_status(status)
- = ci_text_for_status(status)
-
-= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha"
-= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
-&middot;
-#{time_ago_with_tooltip(commit.committed_date)} by
-= commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/blob/_auxiliary_viewer.html.haml b/app/views/projects/blob/_auxiliary_viewer.html.haml
new file mode 100644
index 00000000000..9749afdc580
--- /dev/null
+++ b/app/views/projects/blob/_auxiliary_viewer.html.haml
@@ -0,0 +1,5 @@
+- blob = local_assigns.fetch(:blob)
+- auxiliary_viewer = blob.auxiliary_viewer
+- if auxiliary_viewer && auxiliary_viewer.render_error.nil? && auxiliary_viewer.visible_to?(current_user)
+ .well-segment.blob-auxiliary-viewer
+ = render 'projects/blob/viewer', viewer: auxiliary_viewer
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 8af945ddb2c..8bd336269ff 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -3,13 +3,9 @@
.info-well.hidden-xs
.well-segment
%ul.blob-commit-info
- - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
- = render blob_commit, project: @project, ref: @ref
+ = render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref
- - auxiliary_viewer = blob.auxiliary_viewer
- - if auxiliary_viewer && !auxiliary_viewer.render_error
- .well-segment.blob-auxiliary-viewer
- = render 'projects/blob/viewer', viewer: auxiliary_viewer
+ = render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
%article.file-holder
diff --git a/app/views/projects/blob/viewers/_changelog.html.haml b/app/views/projects/blob/viewers/_changelog.html.haml
new file mode 100644
index 00000000000..53921e63b5f
--- /dev/null
+++ b/app/views/projects/blob/viewers/_changelog.html.haml
@@ -0,0 +1,4 @@
+= icon('history fw')
+= succeed '.' do
+ To find the state of this project's repository at the time of any of these versions, check out
+ = link_to "the tags", namespace_project_tags_path(viewer.project.namespace, viewer.project)
diff --git a/app/views/projects/blob/viewers/_contributing.html.haml b/app/views/projects/blob/viewers/_contributing.html.haml
new file mode 100644
index 00000000000..c78f04c9c7c
--- /dev/null
+++ b/app/views/projects/blob/viewers/_contributing.html.haml
@@ -0,0 +1,9 @@
+= icon('book fw')
+After you've reviewed these contribution guidelines, you'll be all set to
+
+- options = contribution_options(viewer.project)
+- if options.any?
+ = succeed '.' do
+ = options.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ').html_safe
+- else
+ contribute to this project.
diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml
new file mode 100644
index 00000000000..334b33faf48
--- /dev/null
+++ b/app/views/projects/blob/viewers/_readme.html.haml
@@ -0,0 +1,4 @@
+= icon('info-circle fw')
+= succeed '.' do
+ To learn more about this project, read
+ = link_to "the wiki", namespace_project_wikis_path(viewer.project.namespace, viewer.project)
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml
index 642da679f97..48f8c656080 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml
@@ -10,7 +10,7 @@
- if can?(current_user, :admin_issue, @project)
.selectbox.hide-collapsed
- %input{ type: "hidden",
+ %input.js-vue{ type: "hidden",
name: "issue[assignee_ids][]",
":value" => "assignee.id",
"v-if" => "issue.assignees",
@@ -18,7 +18,6 @@
.dropdown
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } },
":data-issuable-id" => "issue.id",
- ":data-selected" => "assigneeId",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee
= icon("chevron-down")
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index d6c4195e2d0..1ca464696ed 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -73,11 +73,6 @@
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', branch_name: 'auto-deploy', context: 'autodeploy') do
Set up auto deploy
- - if @repository.commit
- %div{ class: container_class }
- .project-last-commit
- = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
-
%div{ class: container_class }
- if @project.archived?
.text-warning.center.prepend-top-20
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 42700c237fc..b51955010ce 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -7,13 +7,4 @@
= render 'projects/last_push'
%div{ class: container_class }
- #tree-holder.tree-holder.clearfix
- .nav-block
- = render 'projects/tree/tree_header', tree: @tree
-
- .info-well.hidden-xs.append-bottom-default
- .well-segment
- %ul.blob-commit-info
- = render 'projects/commits/commit', commit: @commit, project: @project, ref: @ref
-
- = render 'projects/tree/tree_content', tree: @tree
+ = render 'projects/files', commit: @last_commit, project: @project, ref: @ref
diff --git a/app/views/shared/icons/_icon_history.svg b/app/views/shared/icons/_icon_history.svg
new file mode 100644
index 00000000000..41096da19c5
--- /dev/null
+++ b/app/views/shared/icons/_icon_history.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1792" height="1792" viewBox="0 0 1792 1792"><path d="M1664 896q0 156-61 298t-164 245-245 164-298 61q-172 0-327-72.5T305 1387q-7-10-6.5-22.5t8.5-20.5l137-138q10-9 25-9 16 2 23 12 73 95 179 147t225 52q104 0 198.5-40.5T1258 1258t109.5-163.5T1408 896t-40.5-198.5T1258 534t-163.5-109.5T896 384q-98 0-188 35.5T548 521l137 138q31 30 14 69-17 40-59 40H192q-26 0-45-19t-19-45V256q0-42 40-59 39-17 69 14l130 129q107-101 244.5-156.5T896 128q156 0 298 61t245 164 164 245 61 298zm-640-288v448q0 14-9 23t-23 9H672q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224V608q0-14 9-23t23-9h64q14 0 23 9t9 23z"/></svg>
diff --git a/app/views/shared/icons/_mr_widget_empty_state.svg b/app/views/shared/icons/_mr_widget_empty_state.svg
new file mode 100644
index 00000000000..6a811893b2d
--- /dev/null
+++ b/app/views/shared/icons/_mr_widget_empty_state.svg
@@ -0,0 +1 @@
+<svg width="256" height="146" viewBox="0 0 256 146" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>illustration</title><defs><rect id="a" width="178.714" height="115.389" rx="10"/><mask id="d" x="0" y="0" width="178.714" height="115.389" fill="#fff"><use xlink:href="#a"/></mask><path d="M8.796 31.515c.395.047.8.072 1.207.072h23.065c5.536 0 10.003-4.475 10.003-9.994v-11.6C43.07 4.476 38.594 0 33.07 0H10.003C4.467 0 0 4.475 0 9.994v11.6c0 1.248.23 2.444.65 3.547H0v7.414c0 4.094 2.394 5.113 5.342 2.28l3.454-3.32z" id="b"/><mask id="e" x="0" y="0" width="43.071" height="36.437" fill="#fff"><use xlink:href="#b"/></mask><path d="M8.796 31.515c.395.047.8.072 1.207.072h23.065c5.536 0 10.003-4.475 10.003-9.994v-11.6C43.07 4.476 38.594 0 33.07 0H10.003C4.467 0 0 4.475 0 9.994v11.6c0 1.248.23 2.444.65 3.547H0v7.414c0 4.094 2.394 5.113 5.342 2.28l3.454-3.32z" id="c"/><mask id="f" x="0" y="0" width="43.071" height="36.437" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 3.868)" fill="#F9F9F9"><rect x="19.286" width="77.143" height="14.182" rx="7.091"/><rect y="28.364" width="84.857" height="14.182" rx="7.091"/><rect x="133.714" y="42.546" width="122.143" height="14.182" rx="7.091"/><rect x="82.929" y="126.992" width="101.571" height="14.182" rx="7.091"/><rect x="42.429" y="99.273" width="101.571" height="14.182" rx="7.091"/><rect x="19.929" y="70.909" width="225" height="14.182" rx="7.091"/><path d="M98.37 14.182H13.488h13.81a7.098 7.098 0 0 1 7.094 7.09 7.09 7.09 0 0 1-7.094 7.092h-13.81 84.88-23.452a7.098 7.098 0 0 1-7.095-7.09 7.09 7.09 0 0 1 7.096-7.092h23.452zm162 42.545h-75.238 23.452a7.098 7.098 0 0 1 7.095 7.09 7.09 7.09 0 0 1-7.096 7.092h-23.452 75.237-23.453a7.098 7.098 0 0 1-7.095-7.09 7.09 7.09 0 0 1 7.095-7.093h23.452zM103.512 85.09H28.275h23.452a7.098 7.098 0 0 1 7.095 7.092 7.09 7.09 0 0 1-7.095 7.09H28.275h75.237H80.06a7.098 7.098 0 0 1-7.095-7.09 7.09 7.09 0 0 1 7.095-7.09h23.452zm48.215 28.365H76.49 90.3a7.098 7.098 0 0 1 7.093 7.09 7.09 7.09 0 0 1-7.094 7.092H76.49h75.237-33.096a7.098 7.098 0 0 1-7.094-7.09 7.09 7.09 0 0 1 7.095-7.092h33.097z"/></g><g transform="translate(38.57 12.248)"><use stroke="#EEE" mask="url(#d)" stroke-width="8" fill="#FFF" xlink:href="#a"/><path fill="#EEE" d="M2.57 18.694h174.215v2.58H2.57z"/><g transform="translate(21.857 38.678)"><rect fill="#B5A7DD" y=".645" width="3.857" height="1.289" rx=".645"/><rect fill="#EEE" x="9.643" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="46.286" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="25.071" y="14.182" width="9.643" height="2.579" rx="1.289"/><rect fill="#FC6D26" x="34.071" y="7.091" width="9.643" height="2.579" rx="1.289"/><rect fill="#FC6D26" opacity=".5" x="30.857" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="14.182" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="18.643" y="7.091" width="12.857" height="2.579" rx="1.289"/><rect fill="#FC6D26" x="21.857" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="7.091" width="6.429" height="2.579" rx="1.289"/><rect fill="#B5A7DD" y="7.736" width="3.857" height="1.289" rx=".645"/><rect fill="#B5A7DD" y="14.826" width="3.857" height="1.289" rx=".645"/></g><g transform="translate(21.857 59.95)"><rect fill="#B5A7DD" y=".645" width="3.857" height="1.289" rx=".645"/><rect fill="#FC6D26" x="9.643" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="46.286" width="9.643" height="2.579" rx="1.289"/><rect fill="#FC6D26" opacity=".5" x="25.071" y="14.182" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="34.071" y="7.091" width="9.643" height="2.579" rx="1.289"/><rect fill="#FC6D26" x="30.857" width="12.857" height="2.579" rx="1.289"/><rect fill="#FC6D26" x="9.643" y="14.182" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="18.643" y="7.091" width="12.857" height="2.579" rx="1.289"/><rect fill="#FC6D26" opacity=".5" x="21.857" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="7.091" width="6.429" height="2.579" rx="1.289"/><rect fill="#B5A7DD" y="7.736" width="3.857" height="1.289" rx=".645"/><rect fill="#B5A7DD" y="14.826" width="3.857" height="1.289" rx=".645"/></g><g transform="translate(21.857 81.223)"><rect fill="#B5A7DD" y=".645" width="3.857" height="1.289" rx=".645"/><rect fill="#EEE" x="9.643" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="46.286" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="25.071" y="14.182" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="34.071" y="7.091" width="9.643" height="2.579" rx="1.289"/><rect fill="#FC6D26" x="30.857" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="14.182" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="18.643" y="7.091" width="12.857" height="2.579" rx="1.289"/><rect fill="#FC6D26" opacity=".5" x="21.857" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="7.091" width="6.429" height="2.579" rx="1.289"/><rect fill="#B5A7DD" y="7.736" width="3.857" height="1.289" rx=".645"/><rect fill="#B5A7DD" y="14.826" width="3.857" height="1.289" rx=".645"/></g><g transform="translate(100.93 38.033)"><rect fill="#FDE5D8" y=".645" width="3.857" height="1.289" rx=".645"/><rect fill="#EEE" x="9.643" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="46.286" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" opacity=".5" x="25.071" y="14.182" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" x="34.071" y="7.091" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" opacity=".5" x="30.857" width="12.857" height="2.579" rx="1.289"/><rect fill="#6B4FBB" x="9.643" y="14.182" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="18.643" y="7.091" width="12.857" height="2.579" rx="1.289"/><rect fill="#6B4FBB" x="21.857" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="7.091" width="6.429" height="2.579" rx="1.289"/><rect fill="#FDE5D8" y="7.736" width="3.857" height="1.289" rx=".645"/><rect fill="#FDE5D8" y="14.826" width="3.857" height="1.289" rx=".645"/><rect fill="#FDE5D8" y="21.917" width="3.857" height="1.289" rx=".645"/><rect fill="#EEE" x="9.643" y="21.273" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="37.286" y="14.182" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" opacity=".5" x="25.071" y="35.455" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" x="18.643" y="28.364" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" x="30.857" y="21.273" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="35.455" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="21.857" y="21.273" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="28.364" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="30.857" y="28.364" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="39.857" y="28.364" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="49.5" y="14.182" width="6.429" height="2.579" rx="1.289"/><rect fill="#FDE5D8" y="29.008" width="3.857" height="1.289" rx=".645"/><rect fill="#FDE5D8" y="36.099" width="3.857" height="1.289" rx=".645"/><rect fill="#FDE5D8" y="43.19" width="3.857" height="1.289" rx=".645"/><rect fill="#6B4FBB" x="9.643" y="42.546" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="25.071" y="56.727" width="9.643" height="2.579" rx="1.289"/><rect fill="#6B4FBB" opacity=".5" x="34.071" y="49.636" width="9.643" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="56.727" width="12.857" height="2.579" rx="1.289"/><rect fill="#6B4FBB" x="18.643" y="49.636" width="12.857" height="2.579" rx="1.289"/><rect fill="#EEE" x="21.857" y="42.546" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="46.286" y="49.636" width="6.429" height="2.579" rx="1.289"/><rect fill="#EEE" x="9.643" y="49.636" width="6.429" height="2.579" rx="1.289"/><rect fill="#FDE5D8" y="50.281" width="3.857" height="1.289" rx=".645"/><rect fill="#FDE5D8" y="57.372" width="3.857" height="1.289" rx=".645"/></g></g><g transform="translate(196.07)"><use stroke="#FDE5D8" mask="url(#e)" stroke-width="8" fill="#FFF" xlink:href="#b"/><rect fill="#FDB692" x="9" y="9.025" width="18.643" height="1.934" rx=".967"/><rect fill="#FDB692" x="9" y="14.826" width="25.071" height="1.934" rx=".967"/><rect fill="#FDB692" x="9" y="20.628" width="18.643" height="1.934" rx=".967"/></g><g transform="translate(189 41.256)"><ellipse stroke="#FC6D26" stroke-width="3" fill="#FFF7F4" cx="10.286" cy="9.669" rx="9.643" ry="9.669"/><path d="M.023 9.002a8.352 8.352 0 0 0 7.94-4.308M9 .644c0-.21-.008-.416-.023-.62" stroke="#FC6D26" stroke-width="2"/><path d="M5.045 2.008A10.266 10.266 0 0 0 13.5 6.446c2.112 0 4.076-.638 5.71-1.733" stroke="#FC6D26" stroke-width="2"/><ellipse fill="#FC6D26" cx="6.75" cy="11.281" rx=".964" ry=".967"/><ellipse fill="#FC6D26" cx="13.821" cy="11.281" rx=".964" ry=".967"/></g><g transform="translate(46.93 96.05)"><ellipse stroke="#6B4FBB" stroke-width="3" fill="#F4F1FA" cx="9.643" cy="10.314" rx="9.643" ry="9.669"/><path d="M12.86 4.51h-.005L11.25 2.58 9.645 4.51H9.64L8.036 2.58 6.43 4.51h-.002L4.82 2.58 3.215 4.512h-1.75A9.646 9.646 0 0 1 9.642 0c3.447 0 6.47 1.8 8.176 4.508h-1.75l-1.605-1.93L12.86 4.51z" fill="#6B4FBB"/><ellipse fill="#6B4FBB" cx="6.107" cy="11.281" rx=".964" ry=".967"/><ellipse fill="#6B4FBB" cx="13.179" cy="11.281" rx=".964" ry=".967"/></g><g transform="matrix(-1 0 0 1 56.57 54.794)"><use stroke="#E2DCF2" mask="url(#f)" stroke-width="8" fill="#FFF" xlink:href="#c"/><rect fill="#6B4FBB" opacity=".5" x="15.429" y="9.025" width="18.643" height="1.934" rx=".967"/><rect fill="#6B4FBB" opacity=".5" x="21.857" y="14.826" width="12.214" height="1.934" rx=".967"/><rect fill="#6B4FBB" opacity=".5" x="21.857" y="20.628" width="12.214" height="1.934" rx=".967"/></g></g></svg>
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 0e535117353..80974bdb066 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -13,7 +13,7 @@
.issues-other-filters.filtered-search-wrapper
.filtered-search-box
- if type != :boards_modal && type != :boards
- = dropdown_tag(content_tag(:i, '', class: 'fa fa-history'),
+ = dropdown_tag(custom_icon('icon_history'),
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
toggle_class: "filtered-search-history-dropdown-toggle-button",
dropdown_class: "filtered-search-history-dropdown",
diff --git a/changelogs/unreleased/31483-ordered-task-list.yml b/changelogs/unreleased/31483-ordered-task-list.yml
new file mode 100644
index 00000000000..c43915b3268
--- /dev/null
+++ b/changelogs/unreleased/31483-ordered-task-list.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Ordered Task List Items
+merge_request: 31483
+author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml b/changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml
new file mode 100644
index 00000000000..d2be3d6cc4b
--- /dev/null
+++ b/changelogs/unreleased/32395-duplicate-string-in-https-docs-gitlab-com-ce-administration-environment_variables-html.yml
@@ -0,0 +1,4 @@
+---
+title: Removes duplicate environment variable in documentation
+merge_request:
+author:
diff --git a/changelogs/unreleased/counters_cache_invalidation.yml b/changelogs/unreleased/counters_cache_invalidation.yml
new file mode 100644
index 00000000000..1e78765ec10
--- /dev/null
+++ b/changelogs/unreleased/counters_cache_invalidation.yml
@@ -0,0 +1,4 @@
+---
+title: Invalidate cache for issue and MR counters more granularly
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-tree-last-commit.yml b/changelogs/unreleased/dm-tree-last-commit.yml
new file mode 100644
index 00000000000..50619fd6ef2
--- /dev/null
+++ b/changelogs/unreleased/dm-tree-last-commit.yml
@@ -0,0 +1,4 @@
+---
+title: Show last commit for current tree on tree page
+merge_request:
+author:
diff --git a/changelogs/unreleased/get_rid_of_pluck.yml b/changelogs/unreleased/get_rid_of_pluck.yml
new file mode 100644
index 00000000000..987af5e9317
--- /dev/null
+++ b/changelogs/unreleased/get_rid_of_pluck.yml
@@ -0,0 +1,4 @@
+---
+title: Issue assignees are now removed without loading unnecessary data into memory
+merge_request:
+author:
diff --git a/changelogs/unreleased/update-admin-health-page.yml b/changelogs/unreleased/update-admin-health-page.yml
new file mode 100644
index 00000000000..51aa6682b49
--- /dev/null
+++ b/changelogs/unreleased/update-admin-health-page.yml
@@ -0,0 +1,5 @@
+---
+title: Added application readiness endpoints to the monitoring health check admin
+ view
+merge_request:
+author:
diff --git a/db/migrate/20170516153305_migrate_assignee_to_separate_table.rb b/db/migrate/20170516153305_migrate_assignee_to_separate_table.rb
index f269ca7fc34..eed9f00d8b2 100644
--- a/db/migrate/20170516153305_migrate_assignee_to_separate_table.rb
+++ b/db/migrate/20170516153305_migrate_assignee_to_separate_table.rb
@@ -47,7 +47,7 @@ class MigrateAssigneeToSeparateTable < ActiveRecord::Migration
RETURNS trigger AS
$BODY$
BEGIN
- if OLD.assignee_id IS NOT NULL THEN
+ if OLD IS NOT NULL AND OLD.assignee_id IS NOT NULL THEN
DELETE FROM issue_assignees WHERE issue_id = OLD.id;
END IF;
diff --git a/db/post_migrate/20170425121605_migrate_trigger_schedules_to_pipeline_schedules.rb b/db/post_migrate/20170425121605_migrate_trigger_schedules_to_pipeline_schedules.rb
index a44b399c4de..dae9750558f 100644
--- a/db/post_migrate/20170425121605_migrate_trigger_schedules_to_pipeline_schedules.rb
+++ b/db/post_migrate/20170425121605_migrate_trigger_schedules_to_pipeline_schedules.rb
@@ -4,6 +4,13 @@ class MigrateTriggerSchedulesToPipelineSchedules < ActiveRecord::Migration
DOWNTIME = false
def up
+ connection.execute <<~SQL
+ DELETE FROM ci_trigger_schedules WHERE NOT EXISTS
+ (SELECT true FROM projects
+ WHERE ci_trigger_schedules.project_id = projects.id
+ )
+ SQL
+
connection.execute <<-SQL
INSERT INTO ci_pipeline_schedules (
project_id,
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 76029b30dd8..b6676026d06 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -20,7 +20,6 @@ Variable | Type | Description
`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
-`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
`GITLAB_EMAIL_SUBJECT_SUFFIX` | string | The e-mail subject suffix used in e-mails sent by GitLab
`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index d5a5aef7ec0..4d3be0ab8f6 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -5,6 +5,20 @@ The solution you choose will be based on the level of scalability and
availability you require. The easiest solutions are scalable, but not necessarily
highly available.
+GitLab provides a service that is usually essential to most organizations: it
+enables people to collaborate on code in a timely fashion. Any downtime should
+therefore be short and planned. Luckily, GitLab provides a solid setup even on
+a single server without special measures. Due to the distributed nature
+of Git, developers can still commit code locally even when GitLab is not
+available. However, some GitLab features such as the issue tracker and
+Continuous Integration are not available when GitLab is down.
+
+**Keep in mind that all Highly Available solutions come with a trade-off between
+cost/complexity and uptime**. The more uptime you want, the more complex the
+solution. And the more complex the solution, the more work is involved in
+setting up and maintaining it. High availability is not free and every HA
+solution should balance the costs against the benefits.
+
## Architecture
There are two kinds of setups:
@@ -37,6 +51,10 @@ Block Device) to keep all data in sync. DRBD requires a low latency link to
remain in sync. It is not advisable to attempt to run DRBD between data centers
or in different cloud availability zones.
+> **Note:** GitLab recommends against choosing this HA method because of the
+ complexity of managing DRBD and crafting automatic failover. This is
+ *compatible* with GitLab, but not officially *supported*.
+
Components/Servers Required: 2 servers/virtual machines (one active/one passive)
![Active/Passive HA Diagram](../img/high_availability/active-passive-diagram.png)
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index c5125dc6d5a..d8e76d6ab94 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -7,6 +7,25 @@ supported natively in NFS version 4. NFSv3 also supports locking as long as
Linux Kernel 2.6.5+ is used. We recommend using version 4 and do not
specifically test NFSv3.
+## AWS Elastic File System
+
+GitLab does not recommend using AWS Elastic File System (EFS).
+
+Customers and users have reported that AWS EFS does not perform well for GitLab's
+use-case. There are several issues that can cause problems. For these reasons
+GitLab does not recommend using EFS with GitLab.
+
+- EFS bases allowed IOPS on volume size. The larger the volume, the more IOPS
+ are allocated. For smaller volumes, users may experience decent performance
+ for a period of time due to 'Burst Credits'. Over a period of weeks to months
+ credits may run out and performance will bottom out.
+- For larger volumes, allocated IOPS may not be the problem. Workloads where
+ many small files are written in a serialized manner are not well-suited for EFS.
+ EBS with an NFS server on top will perform much better.
+
+For more details on another person's experience with EFS, see
+[Amazon's Elastic File System: Burst Credits](https://www.rawkode.io/2017/04/amazons-elastic-file-system-burst-credits/)
+
### Recommended options
When you define your NFS exports, we recommend you also add the following
diff --git a/doc/development/fe_guide/img/testing_triangle.png b/doc/development/fe_guide/img/testing_triangle.png
new file mode 100644
index 00000000000..7a9a848c2ee
--- /dev/null
+++ b/doc/development/fe_guide/img/testing_triangle.png
Binary files differ
diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md
index a8c264bbd3c..5852cac2aa5 100644
--- a/doc/development/fe_guide/testing.md
+++ b/doc/development/fe_guide/testing.md
@@ -1,11 +1,20 @@
# Frontend Testing
-There are two types of tests you'll encounter while developing frontend code
-at GitLab. We use Karma and Jasmine for JavaScript unit testing, and RSpec
-feature tests with Capybara for integration testing.
+There are two types of test suites you'll encounter while developing frontend code
+at GitLab. We use Karma and Jasmine for JavaScript unit and integration testing, and RSpec
+feature tests with Capybara for e2e (end-to-end) integration testing.
-Feature tests need to be written for all new features. Regression tests ought
-to be written for all bug fixes to prevent them from recurring in the future.
+Unit and feature tests need to be written for all new features.
+Most of the time, you should use rspec for your feature tests.
+There are cases where the behaviour you are testing is not worth the time spent running the full application,
+for example, if you are testing styling, animation or small actions that don't involve the backend,
+you should write an integration test using Jasmine.
+
+![Testing priority triangle](img/testing_triangle.png)
+
+_This diagram demonstrates the relative priority of each test type we use_
+
+Regression tests should be written for bug fixes to prevent them from recurring in the future.
See [the Testing Standards and Style Guidelines](../testing.md)
for more information on general testing practices at GitLab.
@@ -13,10 +22,12 @@ for more information on general testing practices at GitLab.
## Karma test suite
GitLab uses the [Karma][karma] test runner with [Jasmine][jasmine] as its test
-framework for our JavaScript unit tests. For tests that rely on DOM
-manipulation, we generate HTML files using RSpec suites (see `spec/javascripts/fixtures/*.rb` for examples).
+framework for our JavaScript unit and integration tests. For integration tests,
+we generate HTML files using RSpec (see `spec/javascripts/fixtures/*.rb` for examples).
Some fixtures are still HAML templates that are translated to HTML files using the same mechanism (see `static_fixtures.rb`).
-Those will be migrated over time.
+Adding these static fixtures should be avoided as they are harder to keep up to date with real views.
+The existing static fixtures will be migrated over time.
+Please see [gitlab-org/gitlab-ce#24753](https://gitlab.com/gitlab-org/gitlab-ce/issues/24753) to track our progress.
Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
JavaScript tests live in `spec/javascripts/`, matching the folder structure
@@ -28,7 +39,9 @@ browser and you will not have access to certain APIs, such as
[`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification),
which will have to be stubbed.
-### Writing tests
+### Best practice
+
+#### Naming unit tests
When writing describe test blocks to test specific functions/methods,
please use the method name as the describe block name.
@@ -56,6 +69,14 @@ describe('.methodName', () => {
});
```
+#### Stubbing
+
+For unit tests, you should stub methods that are unrelated to the current unit you are testing.
+If you need to use a prototype method, instantiate an instance of the class and call it there instead of mocking the instance completely.
+
+For integration tests, you should stub methods that will effect the stability of the test if they
+execute their original behaviour. i.e. Network requests.
+
### Vue.js unit tests
See this [section][vue-test].
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index 088f1cd7290..6b8f3cd3d1d 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -159,19 +159,21 @@ subnet and security group and
***
-## Elastic File System
+## Network File System
-This new AWS offering allows us to create a file system accessible by

-EC2 instances within a VPC. Choose our VPC and the subnets will be
-
automatically configured assuming we don't need to set explicit IPs.
-The
next section allows us to add tags and choose between General
-Purpose or
Max I/O which is a good option when being accessed by a
-large number of
EC2 instances.
+GitLab requires a shared filesystem such as NFS. The file share(s) will be
+mounted on all application servers. There are a variety of ways to build an
+NFS server on AWS.
-

![Elastic File System](img/elastic-file-system.png)
+One option is to use a third-party AMI that offers NFS as a service. A [search
+for 'NFS' in the AWS Marketplace](https://aws.amazon.com/marketplace/search/results?x=0&y=0&searchTerms=NFS&page=1&ref_=nav_search_box)
+shows options such as NetApp, SoftNAS and others.
-To actually mount and install the NFS client we'll use the User Data
-section when adding our Launch Configuration.
+Another option is to build a simple NFS server using a vanilla Linux server backed
+by AWS Elastic Block Storage (EBS).
+
+> **Note:** GitLab does not recommend using AWS Elastic File System (EFS). See
+ details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#aws-elastic-file-system)
***
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 15625e045f5..c4f1c57836f 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -256,9 +256,9 @@ module SharedProject
end
step 'I should see last commit with CI status' do
- page.within ".project-last-commit" do
+ page.within ".blob-commit-info" do
expect(page).to have_content(project.commit.sha[0..6])
- expect(page).to have_content("skipped")
+ expect(page).to have_link("Commit: skipped")
end
end
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 06438d2df41..38e27513281 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -131,10 +131,12 @@ module Gitlab
def check_patch(patch_path)
step("Checking out master", %w[git checkout master])
step("Resetting to latest master", %w[git reset --hard origin/master])
+ step("Fetching CE/#{ce_branch}", %W[git fetch #{CE_REPO} #{ce_branch}])
step(
"Checking if #{patch_path} applies cleanly to EE/master",
%W[git apply --check --3way #{patch_path}]
) do |output, status|
+ puts output
unless status.zero?
@failed_files = output.lines.reduce([]) do |memo, line|
if line.start_with?('error: patch failed:')
@@ -309,12 +311,12 @@ module Gitlab
U lib/gitlab/ee_compat_check.rb
Resolve them, stage the changes and commit them.
-
+
If the patch couldn't be applied cleanly, use the following command:
-
+
# In the EE repo
$ git apply --reject path/to/#{ce_branch}.patch
-
+
This option makes git apply the parts of the patch that are applicable,
and leave the rejected hunks in corresponding `.rej` files.
You can then resolve the conflicts highlighted in `.rej` by
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 86c7954e60c..7a132dba1e9 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -26,9 +26,20 @@ RSpec.describe 'Dashboard Issues', feature: true do
expect(page).not_to have_content(other_issue.title)
end
+ it 'shows checkmark when unassigned is selected for assignee', js: true do
+ find('.js-assignee-search').click
+ find('li', text: 'Unassigned').click
+ find('.js-assignee-search').click
+
+ expect(find('li[data-user-id="0"] a.is-active')).to be_visible
+ end
+
it 'shows issues when current user is author', js: true do
find('#assignee_id', visible: false).set('')
find('.js-author-search', match: :first).click
+
+ expect(find('li[data-user-id="null"] a.is-active')).to be_visible
+
find('.dropdown-menu-author li a', match: :first, text: current_user.to_reference).click
find('.js-author-search', match: :first).click
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 70e96efd557..4166aec1956 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -31,4 +31,16 @@ feature 'user browses project', feature: true, js: true do
expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
expect(page).to have_content 'size 1575078'
end
+
+ scenario 'can see last commit for current directory' do
+ last_commit = project.repository.last_commit_for_path(project.default_branch, 'files')
+
+ click_link 'files'
+ wait_for_ajax
+
+ page.within('.blob-commit-info') do
+ expect(page).to have_content last_commit.short_id
+ expect(page).to have_content last_commit.author_name
+ end
+ end
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index 0a7e0e2d5f2..e5df3e7b6d1 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -86,6 +86,7 @@
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
"status_path": { "type": "string" },
+ "new_blob_path": { "type": "string" },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
"merge_commit_message_with_description": { "type": "string" },
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
index d40c67b189d..a8a02fa6b66 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -4,14 +4,26 @@ import nothingToMergeComponent from '~/vue_merge_request_widget/components/state
describe('MRWidgetNothingToMerge', () => {
describe('template', () => {
const Component = Vue.extend(nothingToMergeComponent);
+ const newBlobPath = '/foo';
const vm = new Component({
el: document.createElement('div'),
+ propsData: {
+ mr: { newBlobPath },
+ },
});
+
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
- expect(vm.$el.innerText).toContain('There is nothing to merge from source branch into target branch.');
+ expect(vm.$el.querySelector('a').href).toContain(newBlobPath);
+ expect(vm.$el.innerText).toContain('Currently there are no changes in this merge request\'s source branch');
expect(vm.$el.innerText).toContain('Please push new commits or use a different branch.');
});
+
+ it('should not show new blob link if there is no link available', () => {
+ vm.mr.newBlobPath = null;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('a')).toEqual(null);
+ });
+ });
});
});
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
new file mode 100644
index 00000000000..9066c5a05ac
--- /dev/null
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe BlobViewer::Changelog, model: true do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:blob) { fake_blob(path: 'CHANGELOG') }
+ subject { described_class.new(blob) }
+
+ describe '#render_error' do
+ context 'when there are no tags' do
+ before do
+ allow(project.repository).to receive(:tag_count).and_return(0)
+ end
+
+ it 'returns :no_tags' do
+ expect(subject.render_error).to eq(:no_tags)
+ end
+ end
+
+ context 'when there are tags' do
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f2c059010f4..e6e7774431e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1777,4 +1777,32 @@ describe User, models: true do
expect(user.preferred_language).to eq('en')
end
end
+
+ context '#invalidate_issue_cache_counts' do
+ let(:user) { build_stubbed(:user) }
+
+ it 'invalidates cache for issue counter' do
+ cache_mock = double
+
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_issues_count'])
+
+ allow(Rails).to receive(:cache).and_return(cache_mock)
+
+ user.invalidate_issue_cache_counts
+ end
+ end
+
+ context '#invalidate_merge_request_cache_counts' do
+ let(:user) { build_stubbed(:user) }
+
+ it 'invalidates cache for Merge Request counter' do
+ cache_mock = double
+
+ expect(cache_mock).to receive(:delete).with(['users', user.id, 'assigned_open_merge_requests_count'])
+
+ allow(Rails).to receive(:cache).and_return(cache_mock)
+
+ user.invalidate_merge_request_cache_counts
+ end
+ end
end
diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb
index bb6e83ae4bd..b75c73e78c2 100644
--- a/spec/serializers/merge_request_entity_spec.rb
+++ b/spec/serializers/merge_request_entity_spec.rb
@@ -65,6 +65,23 @@ describe MergeRequestEntity do
.to eq(resource.merge_commit_message(include_description: true))
end
+ describe 'new_blob_path' do
+ context 'when user can push to project' do
+ it 'returns path' do
+ project.add_developer(user)
+
+ expect(subject[:new_blob_path])
+ .to eq("/#{resource.project.full_path}/new/#{resource.source_branch}")
+ end
+ end
+
+ context 'when user cannot push to project' do
+ it 'returns nil' do
+ expect(subject[:new_blob_path]).to be_nil
+ end
+ end
+ end
+
describe 'diff_head_sha' do
before do
allow(resource).to receive(:diff_head_sha) { 'sha' }
diff --git a/spec/views/projects/_last_commit.html.haml_spec.rb b/spec/views/projects/_last_commit.html.haml_spec.rb
deleted file mode 100644
index eea1695b171..00000000000
--- a/spec/views/projects/_last_commit.html.haml_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe 'projects/_last_commit', :view do
- let(:project) { create(:project, :repository) }
-
- context 'when there is a pipeline present for the commit' do
- context 'when pipeline is blocked' do
- let!(:pipeline) do
- create(:ci_pipeline, :blocked, project: project,
- sha: project.commit.id)
- end
-
- it 'shows correct pipeline badge' do
- render 'projects/last_commit', commit: project.commit,
- project: project,
- ref: :master
-
- expect(rendered).to have_text "blocked #{project.commit.short_id}"
- end
- end
- end
-end
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
index 835a93e620e..33eba3e6d3d 100644
--- a/spec/views/projects/tree/show.html.haml_spec.rb
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -21,11 +21,11 @@ describe 'projects/tree/show' do
let(:tree) { repository.tree(commit.id, path) }
before do
+ assign(:id, File.join(ref, path))
assign(:ref, ref)
- assign(:commit, commit)
- assign(:id, commit.id)
- assign(:tree, tree)
assign(:path, path)
+ assign(:last_commit, commit)
+ assign(:tree, tree)
end
it 'displays correctly' do