summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-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/pipeline_schedules/components/interval_pattern_input.js118
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js16
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js18
-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/pages/issuable.scss9
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss16
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss21
-rw-r--r--app/helpers/blob_helper.rb15
-rw-r--r--app/models/blob.rb4
-rw-r--r--app/models/blob_viewer/contributing.rb10
-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/views/admin/health_check/show.html.haml19
-rw-r--r--app/views/projects/blob/viewers/_contributing.html.haml9
-rw-r--r--app/views/projects/boards/components/sidebar/_assignee.html.haml3
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml14
-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/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/update-admin-health-page.yml5
-rw-r--r--doc/administration/environment_variables.md1
-rw-r--r--doc/user/group/subgroups/index.md8
-rw-r--r--spec/features/dashboard/issues_spec.rb11
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb9
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request.json1
-rw-r--r--spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js42
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js16
-rw-r--r--spec/models/user_spec.rb28
-rw-r--r--spec/serializers/merge_request_entity_spec.rb17
39 files changed, 370 insertions, 183 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 38de411ebb7..a78234aded0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,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/pipeline_schedules/components/interval_pattern_input.js b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js
index 152e75b747e..4d623763ca7 100644
--- a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js
+++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js
@@ -24,9 +24,6 @@ export default {
};
},
computed: {
- showUnsetWarning() {
- return this.cronInterval === '';
- },
intervalIsPreset() {
return _.contains(this.cronIntervalPresets, this.cronInterval);
},
@@ -63,67 +60,75 @@ export default {
},
template: `
<div class="interval-pattern-form-group">
- <input
- id="custom"
- class="label-light"
- type="radio"
- :name="inputNameAttribute"
- :value="cronInterval"
- :checked="isEditable"
- @click="toggleCustomInput(true)"
- />
+ <div class="cron-preset-radio-input">
+ <input
+ id="custom"
+ class="label-light"
+ type="radio"
+ :name="inputNameAttribute"
+ :value="cronInterval"
+ :checked="isEditable"
+ @click="toggleCustomInput(true)"
+ />
- <label for="custom">
- Custom
- </label>
+ <label for="custom">
+ Custom
+ </label>
- <span class="cron-syntax-link-wrap">
- (<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>)
- </span>
+ <span class="cron-syntax-link-wrap">
+ (<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>)
+ </span>
+ </div>
- <input
- id="every-day"
- class="label-light"
- type="radio"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyDay"
- @click="toggleCustomInput(false)"
- />
+ <div class="cron-preset-radio-input">
+ <input
+ id="every-day"
+ class="label-light"
+ type="radio"
+ v-model="cronInterval"
+ :name="inputNameAttribute"
+ :value="cronIntervalPresets.everyDay"
+ @click="toggleCustomInput(false)"
+ />
- <label class="label-light" for="every-day">
- Every day (at 4:00am)
- </label>
+ <label class="label-light" for="every-day">
+ Every day (at 4:00am)
+ </label>
+ </div>
- <input
- id="every-week"
- class="label-light"
- type="radio"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyWeek"
- @click="toggleCustomInput(false)"
- />
+ <div class="cron-preset-radio-input">
+ <input
+ id="every-week"
+ class="label-light"
+ type="radio"
+ v-model="cronInterval"
+ :name="inputNameAttribute"
+ :value="cronIntervalPresets.everyWeek"
+ @click="toggleCustomInput(false)"
+ />
- <label class="label-light" for="every-week">
- Every week (Sundays at 4:00am)
- </label>
+ <label class="label-light" for="every-week">
+ Every week (Sundays at 4:00am)
+ </label>
+ </div>
- <input
- id="every-month"
- class="label-light"
- type="radio"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyMonth"
- @click="toggleCustomInput(false)"
- />
+ <div class="cron-preset-radio-input">
+ <input
+ id="every-month"
+ class="label-light"
+ type="radio"
+ v-model="cronInterval"
+ :name="inputNameAttribute"
+ :value="cronIntervalPresets.everyMonth"
+ @click="toggleCustomInput(false)"
+ />
- <label class="label-light" for="every-month">
- Every month (on the 1st at 4:00am)
- </label>
+ <label class="label-light" for="every-month">
+ Every month (on the 1st at 4:00am)
+ </label>
+ </div>
- <div class="cron-interval-input-wrapper col-md-6">
+ <div class="cron-interval-input-wrapper">
<input
id="schedule_cron"
class="form-control inline cron-interval-input"
@@ -135,9 +140,6 @@ export default {
:disabled="!isEditable"
/>
</div>
- <span class="cron-unset-status col-md-3" v-if="showUnsetWarning">
- Schedule not yet set
- </span>
</div>
`,
};
diff --git a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js b/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js
index 22e746ad2c3..0c3926d76b5 100644
--- a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js
+++ b/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js
@@ -3,7 +3,7 @@ export default class TargetBranchDropdown {
this.$dropdown = $('.js-target-branch-dropdown');
this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text');
this.$input = $('#schedule_ref');
- this.initialValue = this.$input.val();
+ this.initDefaultBranch();
this.initDropdown();
}
@@ -29,13 +29,23 @@ export default class TargetBranchDropdown {
}
setDropdownToggle() {
- if (this.initialValue) {
- this.$dropdownToggle.text(this.initialValue);
+ const initialValue = this.$input.val();
+
+ this.$dropdownToggle.text(initialValue);
+ }
+
+ initDefaultBranch() {
+ const initialValue = this.$input.val();
+ const defaultBranch = this.$dropdown.data('defaultBranch');
+
+ if (!initialValue) {
+ this.$input.val(defaultBranch);
}
}
updateInputValue({ selectedObj, e }) {
e.preventDefault();
+
this.$input.val(selectedObj.name);
gl.pipelineScheduleFieldErrors.updateFormValidityState();
}
diff --git a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js b/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js
index c70e0502cf8..95ed9c7dc21 100644
--- a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js
@@ -1,12 +1,14 @@
/* eslint-disable class-methods-use-this */
+const defaultTimezone = 'UTC';
+
export default class TimezoneDropdown {
constructor() {
this.$dropdown = $('.js-timezone-dropdown');
this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text');
this.$input = $('#schedule_cron_timezone');
this.timezoneData = this.$dropdown.data('data');
- this.initialValue = this.$input.val();
+ this.initDefaultTimezone();
this.initDropdown();
}
@@ -42,12 +44,20 @@ export default class TimezoneDropdown {
return `[UTC ${this.formatUtcOffset(item.offset)}] ${item.name}`;
}
- setDropdownToggle() {
- if (this.initialValue) {
- this.$dropdownToggle.text(this.initialValue);
+ initDefaultTimezone() {
+ const initialValue = this.$input.val();
+
+ if (!initialValue) {
+ this.$input.val(defaultTimezone);
}
}
+ setDropdownToggle() {
+ const initialValue = this.$input.val();
+
+ this.$dropdownToggle.text(initialValue);
+ }
+
updateInputValue({ selectedObj, e }) {
e.preventDefault();
this.$input.val(selectedObj.identifier);
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/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/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 0fee54a0d19..ab417948931 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -31,14 +31,6 @@
margin-right: 10px;
font-size: 12px;
}
-
- .cron-unset-status {
- padding-top: 16px;
- margin-left: -16px;
- color: $gl-text-color-secondary;
- font-size: 12px;
- font-weight: 600;
- }
}
.pipeline-schedule-table-row {
@@ -69,3 +61,16 @@
color: $gl-text-color;
}
}
+
+.cron-preset-radio-input {
+ display: inline-block;
+
+ @media (max-width: $screen-md-max) {
+ display: block;
+ margin: 0 0 5px 5px;
+ }
+
+ input {
+ margin-right: 3px;
+ }
+}
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7eb3512378c..622e14e21ff 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/models/blob.rb b/app/models/blob.rb
index 63a81c0e3bd..e0c539a17cf 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -39,7 +39,9 @@ class Blob < SimpleDelegator
AUXILIARY_VIEWERS = [
BlobViewer::GitlabCiYml,
BlobViewer::RouteMap,
- BlobViewer::License
+
+ BlobViewer::License,
+ BlobViewer::Contributing
].freeze
attr_reader :project
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/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/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/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/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/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index d6f4f1a206c..bbed10039af 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -5,29 +5,29 @@
= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f|
= form_errors(@schedule)
.form-group
- .col-md-6
+ .col-md-9
= f.label :description, 'Description', class: 'label-light'
= f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: 'Provide a short description for this pipeline'
.form-group
- .col-md-12
+ .col-md-9
= f.label :cron, 'Interval Pattern', class: 'label-light'
#interval-pattern-input{ data: { initial_interval: @schedule.cron } }
.form-group
- .col-md-6
+ .col-md-9
= f.label :cron_timezone, 'Cron Timezone', class: 'label-light'
= dropdown_tag("Select a timezone", options: { toggle_class: 'btn js-timezone-dropdown', title: "Select a timezone", filter: true, placeholder: "Filter", data: { data: timezone_data } } )
= f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true
.form-group
- .col-md-6
+ .col-md-9
= f.label :ref, 'Target Branch', class: 'label-light'
- = dropdown_tag("Select target branch", options: { toggle_class: 'btn js-target-branch-dropdown git-revision-dropdown-toggle', dropdown_class: 'git-revision-dropdown', title: "Select target branch", filter: true, placeholder: "Filter", data: { data: @project.repository.branch_names } } )
+ = dropdown_tag("Select target branch", options: { toggle_class: 'btn js-target-branch-dropdown git-revision-dropdown-toggle', dropdown_class: 'git-revision-dropdown', title: "Select target branch", filter: true, placeholder: "Filter", data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
- .col-md-6
+ .col-md-9
= f.label :active, 'Activated', class: 'label-light'
%div
= f.check_box :active, required: false, value: @schedule.active?
- active
+ Active
.footer-block.row-content-block
= f.submit 'Save pipeline schedule', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', pipeline_schedules_path(@project), class: 'btn btn-cancel'
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/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/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/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/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index ce5da07c61a..a4726673fc4 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -71,8 +71,10 @@ structure.
- You need to be an Owner of a group in order to be able to create
a subgroup. For more information check the [permissions table][permissions].
- For a list of words that are not allowed to be used as group names see the
- [`namespace_validator.rb` file][reserved] under the `RESERVED` and
- `WILDCARD_ROUTES` lists.
+ [`dynamic_path_validator.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `WILDCARD_ROUTES` and `GROUP_ROUTES` lists:
+ - `TOP_LEVEL_ROUTES`: are names that are reserved as usernames or top level groups
+ - `WILDCARD_ROUTES`: are names that are reserved for child groups or projects.
+ - `GROUP_ROUTES`: are names that are reserved for all groups or projects.
To create a subgroup:
@@ -161,4 +163,4 @@ Here's a list of what you can't do with subgroups:
[ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772
[permissions]: ../../permissions.md#group
-[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/validators/namespace_validator.rb
+[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/validators/dynamic_path_validator.rb
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/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index fe9f94db574..03a30bfb996 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -70,6 +70,11 @@ feature 'Pipeline Schedules', :feature do
describe 'POST /projects/pipeline_schedules/new', js: true do
let(:visit_page) { visit_new_pipeline_schedule }
+ it 'sets defaults for timezone and target branch' do
+ expect(page).to have_button('master')
+ expect(page).to have_button('UTC')
+ end
+
it 'it creates a new scheduled pipeline' do
fill_in_schedule_form
save_pipeline_schedule
@@ -118,12 +123,12 @@ feature 'Pipeline Schedules', :feature do
end
def select_timezone
- click_button 'Select a timezone'
+ find('.js-timezone-dropdown').click
click_link 'American Samoa'
end
def select_target_branch
- click_button 'Select target branch'
+ find('.js-target-branch-dropdown').click
click_link 'master'
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/pipeline_schedules/interval_pattern_input_spec.js b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
index 08fa6ca9057..845b371d90c 100644
--- a/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
+++ b/spec/javascripts/pipeline_schedules/interval_pattern_input_spec.js
@@ -36,20 +36,6 @@ describe('Interval Pattern Input Component', function () {
expect(this.intervalPatternComponent.initialCronInterval).toBe(this.initialCronInterval);
});
- it('sets showUnsetWarning to false', function (done) {
- Vue.nextTick(() => {
- expect(this.intervalPatternComponent.showUnsetWarning).toBe(false);
- done();
- });
- });
-
- it('does not render showUnsetWarning', function (done) {
- Vue.nextTick(() => {
- expect(this.intervalPatternComponent.$el.outerHTML).not.toContain('Schedule not yet set');
- done();
- });
- });
-
it('sets isEditable to true', function (done) {
Vue.nextTick(() => {
expect(this.intervalPatternComponent.isEditable).toBe(true);
@@ -72,20 +58,6 @@ describe('Interval Pattern Input Component', function () {
expect(this.intervalPatternComponent).toBeDefined();
});
- it('sets showUnsetWarning to false', function (done) {
- Vue.nextTick(() => {
- expect(this.intervalPatternComponent.showUnsetWarning).toBe(false);
- done();
- });
- });
-
- it('does not render showUnsetWarning', function (done) {
- Vue.nextTick(() => {
- expect(this.intervalPatternComponent.$el.outerHTML).not.toContain('Schedule not yet set');
- done();
- });
- });
-
it('sets isEditable to false', function (done) {
Vue.nextTick(() => {
expect(this.intervalPatternComponent.isEditable).toBe(false);
@@ -113,20 +85,6 @@ describe('Interval Pattern Input Component', function () {
expect(this.intervalPatternComponent.initialCronInterval).toBe(defaultInitialCronInterval);
});
- it('sets showUnsetWarning to true', function (done) {
- Vue.nextTick(() => {
- expect(this.intervalPatternComponent.showUnsetWarning).toBe(true);
- done();
- });
- });
-
- it('renders showUnsetWarning to true', function (done) {
- Vue.nextTick(() => {
- expect(this.intervalPatternComponent.$el.outerHTML).toContain('Schedule not yet set');
- done();
- });
- });
-
it('sets isEditable to true', function (done) {
Vue.nextTick(() => {
expect(this.intervalPatternComponent.isEditable).toBe(true);
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/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' }