Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-10-04 15:06:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-10-04 15:06:14 +0300
commit0d46bf06388d485824bc2f1e736b92b2a8a397e4 (patch)
tree626a835841722463da4def7905b95e874eb77578 /app
parent1f1bdf54e1974f89f3a6ba734ec2c42552e90639 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob/template_selector.js11
-rw-r--r--app/assets/javascripts/gl_dropdown.js1
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js4
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue24
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js56
-rw-r--r--app/assets/javascripts/templates/issuable_template_selectors.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue2
-rw-r--r--app/assets/stylesheets/components/release_block.scss3
-rw-r--r--app/assets/stylesheets/framework/common.scss22
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/concerns/atomic_internal_id.rb74
-rw-r--r--app/views/shared/form_elements/_apply_template_warning.html.haml14
-rw-r--r--app/views/shared/issuable/_form.html.haml1
14 files changed, 186 insertions, 35 deletions
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 02216e4e93d..6cfe354d277 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -27,11 +27,16 @@ export default class TemplateSelector {
search: {
fields: ['name'],
},
- clicked: options => this.fetchFileTemplate(options),
+ clicked: options => this.onDropdownClicked(options),
text: item => item.name,
});
}
+ // Subclasses can override this method to conditionally prevent fetching file templates
+ onDropdownClicked(options) {
+ this.fetchFileTemplate(options);
+ }
+
initAutosizeUpdateEvent() {
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
@@ -81,6 +86,10 @@ export default class TemplateSelector {
}
}
+ getEditorContent() {
+ return this.editor.getValue();
+ }
+
startLoadingSpinner() {
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 437c4941fda..4e1b4f2652c 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -717,6 +717,7 @@ GitLabDropdown = (function() {
selectedObject = this.renderedData[groupName][selectedIndex];
} else {
selectedIndex = el.closest('li').index();
+ this.selectedIndex = selectedIndex;
selectedObject = this.renderedData[selectedIndex];
}
}
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 2205a7bafe3..96e47187fed 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -15,7 +15,9 @@ export default () => {
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
- new IssuableTemplateSelectors();
+ new IssuableTemplateSelectors({
+ warnTemplateOverride: true,
+ });
initSuggestions();
};
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 8f0dc8554e2..e51ab79a51d 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -16,5 +16,7 @@ export default () => {
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
- new IssuableTemplateSelectors();
+ new IssuableTemplateSelectors({
+ warnTemplateOverride: true,
+ });
};
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 7b6bd9913a8..921ada91544 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -6,6 +6,9 @@ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { __, n__, sprintf } from '../../locale';
+import { slugify } from '~/lib/utils/text_utility';
+import { getLocationHash } from '~/lib/utils/url_utility';
+import { scrollToElement } from '~/lib/utils/common_utils';
export default {
name: 'ReleaseBlock',
@@ -26,7 +29,15 @@ export default {
default: () => ({}),
},
},
+ data() {
+ return {
+ isHighlighted: false,
+ };
+ },
computed: {
+ id() {
+ return slugify(this.release.tag_name);
+ },
releasedTimeAgo() {
return sprintf(__('released %{time}'), {
time: this.timeFormated(this.release.released_at),
@@ -62,10 +73,21 @@ export default {
return n__('Milestone', 'Milestones', this.release.milestones.length);
},
},
+ mounted() {
+ const hash = getLocationHash();
+ if (hash && slugify(hash) === this.id) {
+ this.isHighlighted = true;
+ setTimeout(() => {
+ this.isHighlighted = false;
+ }, 2000);
+
+ scrollToElement(this.$el);
+ }
+ },
};
</script>
<template>
- <div :id="release.tag_name" class="card">
+ <div :id="id" :class="{ 'bg-line-target-blue': isHighlighted }" class="card release-block">
<div class="card-body">
<h2 class="card-title mt-0">
{{ release.name }}
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index 78609ce0610..78a1c4fa8a8 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -8,10 +8,13 @@ import { __ } from '~/locale';
export default class IssuableTemplateSelector extends TemplateSelector {
constructor(...args) {
super(...args);
+
this.projectPath = this.dropdown.data('projectPath');
this.namespacePath = this.dropdown.data('namespacePath');
this.issuableType = this.$dropdownContainer.data('issuableType');
this.titleInput = $(`#${this.issuableType}_title`);
+ this.templateWarningEl = $('.js-issuable-template-warning');
+ this.warnTemplateOverride = args[0].warnTemplateOverride;
const initialQuery = {
name: this.dropdown.data('selected'),
@@ -24,14 +27,61 @@ export default class IssuableTemplateSelector extends TemplateSelector {
});
$('.no-template', this.dropdown.parent()).on('click', () => {
- this.currentTemplate.content = '';
- this.setInputValueToTemplateContent();
- $('.dropdown-toggle-text', this.dropdown).text(__('Choose a template'));
+ this.reset();
+ });
+
+ this.templateWarningEl.find('.js-close-btn').on('click', () => {
+ if (this.previousSelectedIndex) {
+ this.dropdown.data('glDropdown').selectRowAtIndex(this.previousSelectedIndex);
+ } else {
+ this.reset();
+ }
+
+ this.templateWarningEl.addClass('hidden');
+ });
+
+ this.templateWarningEl.find('.js-override-template').on('click', () => {
+ this.requestFile(this.overridingTemplate);
+ this.setSelectedIndex();
+
+ this.templateWarningEl.addClass('hidden');
+ this.overridingTemplate = null;
});
}
+ reset() {
+ if (this.currentTemplate) {
+ this.currentTemplate.content = '';
+ }
+
+ this.setInputValueToTemplateContent();
+ $('.dropdown-toggle-text', this.dropdown).text(__('Choose a template'));
+ this.previousSelectedIndex = null;
+ }
+
+ setSelectedIndex() {
+ this.previousSelectedIndex = this.dropdown.data('glDropdown').selectedIndex;
+ }
+
+ onDropdownClicked(query) {
+ const content = this.getEditorContent();
+ const isContentUnchanged =
+ content === '' || (this.currentTemplate && content === this.currentTemplate.content);
+
+ if (!this.warnTemplateOverride || isContentUnchanged) {
+ super.onDropdownClicked(query);
+ this.setSelectedIndex();
+
+ return;
+ }
+
+ this.overridingTemplate = query.selectedObj;
+ this.templateWarningEl.removeClass('hidden');
+ }
+
requestFile(query) {
this.startLoadingSpinner();
+
Api.issueTemplate(
this.namespacePath,
this.projectPath,
diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js b/app/assets/javascripts/templates/issuable_template_selectors.js
index 50e58ec5c46..443b3084113 100644
--- a/app/assets/javascripts/templates/issuable_template_selectors.js
+++ b/app/assets/javascripts/templates/issuable_template_selectors.js
@@ -4,7 +4,7 @@ import $ from 'jquery';
import IssuableTemplateSelector from './issuable_template_selector';
export default class IssuableTemplateSelectors {
- constructor({ $dropdowns, editor } = {}) {
+ constructor({ $dropdowns, editor, warnTemplateOverride } = {}) {
this.$dropdowns = $dropdowns || $('.js-issuable-selector');
this.editor = editor || this.initEditor();
@@ -16,6 +16,7 @@ export default class IssuableTemplateSelectors {
wrapper: $dropdown.closest('.js-issuable-selector-wrap'),
dropdown: $dropdown,
editor: this.editor,
+ warnTemplateOverride,
});
});
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index bb6921225c2..1873e09c370 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -211,7 +211,7 @@ export default {
<template v-else>
<review-app-link
:link="deploymentExternalUrl"
- css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
+ css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
/>
</template>
<visual-review-app-link
diff --git a/app/assets/stylesheets/components/release_block.scss b/app/assets/stylesheets/components/release_block.scss
new file mode 100644
index 00000000000..7e82d0960d7
--- /dev/null
+++ b/app/assets/stylesheets/components/release_block.scss
@@ -0,0 +1,3 @@
+.release-block {
+ transition: background-color 1s linear;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 4cd6763e7d7..922051ab0e9 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -55,6 +55,10 @@
background-color: $gray-light;
}
+.bg-line-target-blue {
+ background: $line-target-blue;
+}
+
.text-break-word {
word-break: break-all;
}
@@ -210,18 +214,26 @@ li.note {
@mixin message($background-color, $border-color, $text-color) {
border-left: 4px solid $border-color;
color: $text-color;
- padding: 10px;
- margin-bottom: 10px;
- background: $background-color;
- padding-left: 20px;
+ padding: $gl-padding $gl-padding-24;
+ margin-bottom: $gl-padding-12;
+ background-color: $background-color;
&.centered {
text-align: center;
}
+
+ .close {
+ svg {
+ width: $gl-font-size-large;
+ height: $gl-font-size-large;
+ }
+
+ color: inherit;
+ }
}
.warning_message {
- @include message($orange-100, $orange-200, $orange-700);
+ @include message($orange-100, $orange-200, $orange-800);
}
.danger_message {
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 7fa290610aa..aed95b4601b 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -25,7 +25,7 @@ module Ci
belongs_to :merge_request, class_name: 'MergeRequest'
belongs_to :external_pull_request
- has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
+ has_internal_id :iid, scope: :project, presence: false, ensure_if: -> { !importing? }, init: ->(s) do
s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count
end
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 95de11a72bf..b510129b35d 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -27,53 +27,73 @@ module AtomicInternalId
extend ActiveSupport::Concern
class_methods do
- def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
+ def has_internal_id(column, scope:, init:, ensure_if: nil, presence: true) # rubocop:disable Naming/PredicateName
# We require init here to retain the ability to recalculate in the absence of a
# InternaLId record (we may delete records in `internal_ids` for example).
raise "has_internal_id requires a init block, none given." unless init
+ raise "has_internal_id needs to be defined on association." unless self.reflect_on_association(scope)
- before_validation :"ensure_#{scope}_#{column}!", on: :create
+ before_validation :"track_#{scope}_#{column}!", on: :create
+ before_validation :"ensure_#{scope}_#{column}!", on: :create, if: ensure_if
validates column, presence: presence
define_method("ensure_#{scope}_#{column}!") do
- scope_value = association(scope).reader
+ scope_value = internal_id_read_scope(scope)
value = read_attribute(column)
-
return value unless scope_value
- scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
- usage = self.class.table_name.to_sym
-
- if value.present? && (@iid_needs_tracking || Feature.enabled?(:iid_always_track, default_enabled: true))
- # The value was set externally, e.g. by the user
- # We update the InternalId record to keep track of the greatest value.
- InternalId.track_greatest(self, scope_attrs, usage, value, init)
-
- @iid_needs_tracking = false
- elsif !value.present?
+ if value.nil?
# We don't have a value yet and use a InternalId record to generate
# the next value.
- value = InternalId.generate_next(self, scope_attrs, usage, init)
+ value = InternalId.generate_next(
+ self,
+ internal_id_scope_attrs(scope),
+ internal_id_scope_usage,
+ init)
write_attribute(column, value)
end
value
end
+ define_method("track_#{scope}_#{column}!") do
+ iid_always_track = Feature.enabled?(:iid_always_track, default_enabled: true)
+ return unless @internal_id_needs_tracking || iid_always_track
+
+ @internal_id_needs_tracking = false
+
+ scope_value = internal_id_read_scope(scope)
+ value = read_attribute(column)
+ return unless scope_value
+
+ if value.present?
+ # The value was set externally, e.g. by the user
+ # We update the InternalId record to keep track of the greatest value.
+ InternalId.track_greatest(
+ self,
+ internal_id_scope_attrs(scope),
+ internal_id_scope_usage,
+ value,
+ init)
+ end
+ end
+
define_method("#{column}=") do |value|
super(value).tap do |v|
# Indicate the iid was set from externally
- @iid_needs_tracking = true
+ @internal_id_needs_tracking = true
end
end
define_method("reset_#{scope}_#{column}") do
if value = read_attribute(column)
- scope_value = association(scope).reader
- scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
- usage = self.class.table_name.to_sym
+ did_reset = InternalId.reset(
+ self,
+ internal_id_scope_attrs(scope),
+ internal_id_scope_usage,
+ value)
- if InternalId.reset(self, scope_attrs, usage, value)
+ if did_reset
write_attribute(column, nil)
end
end
@@ -82,4 +102,18 @@ module AtomicInternalId
end
end
end
+
+ def internal_id_scope_attrs(scope)
+ scope_value = internal_id_read_scope(scope)
+
+ { scope_value.class.table_name.singularize.to_sym => scope_value } if scope_value
+ end
+
+ def internal_id_scope_usage
+ self.class.table_name.to_sym
+ end
+
+ def internal_id_read_scope(scope)
+ association(scope).reader
+ end
end
diff --git a/app/views/shared/form_elements/_apply_template_warning.html.haml b/app/views/shared/form_elements/_apply_template_warning.html.haml
new file mode 100644
index 00000000000..9027264d221
--- /dev/null
+++ b/app/views/shared/form_elements/_apply_template_warning.html.haml
@@ -0,0 +1,14 @@
+.form-group.row.js-template-warning.mb-0.hidden.js-issuable-template-warning
+ .offset-sm-2.col-sm-10
+
+ .warning_message.mb-0{ role: 'alert' }
+ %btn.js-close-btn.close{ type: "button", "aria-hidden": true, "aria-label": _("Close") }
+ = sprite_icon("close")
+
+ %p
+ = _("Applying a template will replace the existing issue description. Any changes you have made will be lost.")
+
+ %button.js-override-template.btn.btn-warning.mr-2{ type: 'button' }
+ = _("Apply template")
+ %button.js-cancel-btn.btn.btn-inverted{ type: 'button' }
+ = _("Cancel")
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 04a70e406ca..5e2b5f95ee3 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -19,6 +19,7 @@
= render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
#js-suggestions{ data: { project_path: @project.full_path } }
+= render 'shared/form_elements/apply_template_warning'
= render 'shared/form_elements/description', model: issuable, form: form, project: project
- if issuable.respond_to?(:confidential)