diff options
33 files changed, 329 insertions, 173 deletions
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/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/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/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/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/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' } |