summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/dropzone_input.js252
-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/stylesheets/pages/note_form.scss43
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss21
-rw-r--r--app/helpers/icons_helper.rb5
-rw-r--r--app/services/members/authorized_destroy_service.rb15
-rw-r--r--app/views/admin/health_check/show.html.haml19
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml14
-rw-r--r--app/views/shared/notes/_hints.html.haml27
11 files changed, 350 insertions, 198 deletions
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index b3a76fbb43e..988c484ed85 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -5,104 +5,154 @@ require('./preview_markdown');
window.DropzoneInput = (function() {
function DropzoneInput(form) {
- var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, uploads_path, showError, showSpinner, uploadFile, uploadProgress;
+ var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile;
Dropzone.autoDiscover = false;
- alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
- alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
- divHover = "<div class=\"div-dropzone-hover\"></div>";
- divSpinner = "<div class=\"div-dropzone-spinner\"></div>";
- divAlert = "<div class=\"" + alertClass + "\"></div>";
- iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>";
- iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
- uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
- btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
- uploads_path = window.uploads_path || null;
- max_file_size = gon.max_file_size || 10;
- form_textarea = $(form).find(".js-gfm-input");
- form_textarea.wrap("<div class=\"div-dropzone\"></div>");
- form_textarea.on('paste', (function(_this) {
+ divHover = '<div class="div-dropzone-hover"></div>';
+ iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
+ $attachButton = form.find('.button-attach-file');
+ $attachingFileMessage = form.find('.attaching-file-message');
+ $cancelButton = form.find('.button-cancel-uploading-files');
+ $retryLink = form.find('.retry-uploading-link');
+ $uploadProgress = form.find('.uploading-progress');
+ $uploadingErrorContainer = form.find('.uploading-error-container');
+ $uploadingErrorMessage = form.find('.uploading-error-message');
+ $uploadingProgressContainer = form.find('.uploading-progress-container');
+ uploadsPath = window.uploads_path || null;
+ maxFileSize = gon.max_file_size || 10;
+ formTextarea = form.find('.js-gfm-input');
+ formTextarea.wrap('<div class="div-dropzone"></div>');
+ formTextarea.on('paste', (function(_this) {
return function(event) {
return handlePaste(event);
};
})(this));
- $mdArea = $(form_textarea).closest('.md-area');
- $(form).setupMarkdownPreview();
- form_dropzone = $(form).find('.div-dropzone');
- form_dropzone.parent().addClass("div-dropzone-wrapper");
- form_dropzone.append(divHover);
- form_dropzone.find(".div-dropzone-hover").append(iconPaperclip);
- form_dropzone.append(divSpinner);
- form_dropzone.find(".div-dropzone-spinner").append(iconSpinner);
- form_dropzone.find(".div-dropzone-spinner").append(uploadProgress);
- form_dropzone.find(".div-dropzone-spinner").css({
- "opacity": 0,
- "display": "none"
- });
- if (!uploads_path) return;
+ // Add dropzone area to the form.
+ $mdArea = formTextarea.closest('.md-area');
+ form.setupMarkdownPreview();
+ $formDropzone = form.find('.div-dropzone');
+ $formDropzone.parent().addClass('div-dropzone-wrapper');
+ $formDropzone.append(divHover);
+ $formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
+
+ if (!uploadsPath) return;
- dropzone = form_dropzone.dropzone({
- url: uploads_path,
- dictDefaultMessage: "",
+ dropzone = $formDropzone.dropzone({
+ url: uploadsPath,
+ dictDefaultMessage: '',
clickable: true,
- paramName: "file",
- maxFilesize: max_file_size,
+ paramName: 'file',
+ maxFilesize: maxFileSize,
uploadMultiple: false,
headers: {
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
previewContainer: false,
processing: function() {
- return $(".div-dropzone-alert").alert("close");
+ return $('.div-dropzone-alert').alert('close');
},
dragover: function() {
$mdArea.addClass('is-dropzone-hover');
- form.find(".div-dropzone-hover").css("opacity", 0.7);
+ form.find('.div-dropzone-hover').css('opacity', 0.7);
},
dragleave: function() {
$mdArea.removeClass('is-dropzone-hover');
- form.find(".div-dropzone-hover").css("opacity", 0);
+ form.find('.div-dropzone-hover').css('opacity', 0);
},
drop: function() {
$mdArea.removeClass('is-dropzone-hover');
- form.find(".div-dropzone-hover").css("opacity", 0);
- form_textarea.focus();
+ form.find('.div-dropzone-hover').css('opacity', 0);
+ formTextarea.focus();
},
success: function(header, response) {
const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
const shouldPad = processingFileCount >= 1;
pasteText(response.link.markdown, shouldPad);
+ // Show 'Attach a file' link only when all files have been uploaded.
+ if (!processingFileCount) $attachButton.removeClass('hide');
},
- error: function(temp) {
- var checkIfMsgExists, errorAlert;
- errorAlert = $(form).find('.error-alert');
- checkIfMsgExists = errorAlert.children().length;
- if (checkIfMsgExists === 0) {
- errorAlert.append(divAlert);
- $(".div-dropzone-alert").append(btnAlert + "Attaching the file failed.");
- }
+ error: function(file, errorMessage = 'Attaching the file failed.', xhr) {
+ // If 'error' event is fired by dropzone, the second parameter is error message.
+ // If the 'errorMessage' parameter is empty, the default error message is set.
+ // If the 'error' event is fired by backend (xhr) error response, the third parameter is
+ // xhr object (xhr.responseText is error message).
+ // On error we hide the 'Attach' and 'Cancel' buttons
+ // and show an error.
+
+ // If there's xhr error message, let's show it instead of dropzone's one.
+ const message = xhr ? xhr.responseText : errorMessage;
+
+ $uploadingErrorContainer.removeClass('hide');
+ $uploadingErrorMessage.html(message);
+ $attachButton.addClass('hide');
+ $cancelButton.addClass('hide');
},
totaluploadprogress: function(totalUploadProgress) {
- uploadProgress.text(Math.round(totalUploadProgress) + "%");
+ updateAttachingMessage(this.files, $attachingFileMessage);
+ $uploadProgress.text(Math.round(totalUploadProgress) + '%');
+ },
+ sending: function(file) {
+ // DOM elements already exist.
+ // Instead of dynamically generating them,
+ // we just either hide or show them.
+ $attachButton.addClass('hide');
+ $uploadingErrorContainer.addClass('hide');
+ $uploadingProgressContainer.removeClass('hide');
+ $cancelButton.removeClass('hide');
},
- sending: function() {
- form_dropzone.find(".div-dropzone-spinner").css({
- "opacity": 0.7,
- "display": "inherit"
- });
+ removedfile: function() {
+ $attachButton.removeClass('hide');
+ $cancelButton.addClass('hide');
+ $uploadingProgressContainer.addClass('hide');
+ $uploadingErrorContainer.addClass('hide');
},
queuecomplete: function() {
- uploadProgress.text("");
- $(".dz-preview").remove();
- $(".markdown-area").trigger("input");
- $(".div-dropzone-spinner").css({
- "opacity": 0,
- "display": "none"
- });
+ $('.dz-preview').remove();
+ $('.markdown-area').trigger('input');
+
+ $uploadingProgressContainer.addClass('hide');
+ $cancelButton.addClass('hide');
}
});
- child = $(dropzone[0]).children("textarea");
+
+ child = $(dropzone[0]).children('textarea');
+
+ // removeAllFiles(true) stops uploading files (if any)
+ // and remove them from dropzone files queue.
+ $cancelButton.on('click', (e) => {
+ const target = e.target.closest('form').querySelector('.div-dropzone');
+
+ e.preventDefault();
+ e.stopPropagation();
+ Dropzone.forElement(target).removeAllFiles(true);
+ });
+
+ // If 'error' event is fired, we store a failed files,
+ // clear dropzone files queue, change status of failed files to undefined,
+ // and add that files to the dropzone files queue again.
+ // addFile() adds file to dropzone files queue and upload it.
+ $retryLink.on('click', (e) => {
+ const dropzoneInstance = Dropzone.forElement(e.target.closest('form').querySelector('.div-dropzone'));
+ const failedFiles = dropzoneInstance.files;
+
+ e.preventDefault();
+
+ // 'true' parameter of removeAllFiles() cancels uploading of files that are being uploaded at the moment.
+ dropzoneInstance.removeAllFiles(true);
+
+ failedFiles.map((failedFile, i) => {
+ const file = failedFile;
+
+ if (file.status === Dropzone.ERROR) {
+ file.status = undefined;
+ file.accepted = undefined;
+ }
+
+ return dropzoneInstance.addFile(file);
+ });
+ });
+
handlePaste = function(event) {
var filename, image, pasteEvent, text;
pasteEvent = event.originalEvent;
@@ -110,25 +160,27 @@ window.DropzoneInput = (function() {
image = isImage(pasteEvent);
if (image) {
event.preventDefault();
- filename = getFilename(pasteEvent) || "image.png";
- text = "{{" + filename + "}}";
+ filename = getFilename(pasteEvent) || 'image.png';
+ text = `{{${filename}}}`;
pasteText(text);
return uploadFile(image.getAsFile(), filename);
}
}
};
+
isImage = function(data) {
var i, item;
i = 0;
while (i < data.clipboardData.items.length) {
item = data.clipboardData.items[i];
- if (item.type.indexOf("image") !== -1) {
+ if (item.type.indexOf('image') !== -1) {
return item;
}
i += 1;
}
return false;
};
+
pasteText = function(text, shouldPad) {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text;
@@ -142,31 +194,33 @@ window.DropzoneInput = (function() {
$(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
- return form_textarea.trigger("input");
+ return formTextarea.trigger('input');
};
+
getFilename = function(e) {
var value;
if (window.clipboardData && window.clipboardData.getData) {
- value = window.clipboardData.getData("Text");
+ value = window.clipboardData.getData('Text');
} else if (e.clipboardData && e.clipboardData.getData) {
- value = e.clipboardData.getData("text/plain");
+ value = e.clipboardData.getData('text/plain');
}
value = value.split("\r");
return value.first();
};
+
uploadFile = function(item, filename) {
var formData;
formData = new FormData();
- formData.append("file", item, filename);
+ formData.append('file', item, filename);
return $.ajax({
- url: uploads_path,
- type: "POST",
+ url: uploadsPath,
+ type: 'POST',
data: formData,
- dataType: "json",
+ dataType: 'json',
processData: false,
contentType: false,
headers: {
- "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
beforeSend: function() {
showSpinner();
@@ -183,44 +237,54 @@ window.DropzoneInput = (function() {
}
});
};
+
+ updateAttachingMessage = (files, messageContainer) => {
+ let attachingMessage;
+ const filesCount = files.filter(function(file) {
+ return file.status === 'uploading' ||
+ file.status === 'queued';
+ }).length;
+
+ // Dinamycally change uploading files text depending on files number in
+ // dropzone files queue.
+ if (filesCount > 1) {
+ attachingMessage = 'Attaching ' + filesCount + ' files -';
+ } else {
+ attachingMessage = 'Attaching a file -';
+ }
+
+ messageContainer.text(attachingMessage);
+ };
+
insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) {
- return val.replace("{{" + filename + "}}", url);
+ return val.replace(`{{${filename}}}`, url);
});
};
+
appendToTextArea = function(url) {
return $(child).val(function(index, val) {
return val + url + "\n";
});
};
+
showSpinner = function(e) {
- return form.find(".div-dropzone-spinner").css({
- "opacity": 0.7,
- "display": "inherit"
- });
+ return $uploadingProgressContainer.removeClass('hide');
};
+
closeSpinner = function() {
- return form.find(".div-dropzone-spinner").css({
- "opacity": 0,
- "display": "none"
- });
+ return $uploadingProgressContainer.addClass('hide');
};
+
showError = function(message) {
- var checkIfMsgExists, errorAlert;
- errorAlert = $(form).find('.error-alert');
- checkIfMsgExists = errorAlert.children().length;
- if (checkIfMsgExists === 0) {
- errorAlert.append(divAlert);
- return $(".div-dropzone-alert").append(btnAlert + message);
- }
+ $uploadingErrorContainer.removeClass('hide');
+ $uploadingErrorMessage.html(message);
};
- closeAlertMessage = function() {
- return form.find(".div-dropzone-alert").alert("close");
- };
- form.find(".markdown-selector").click(function(e) {
+
+ form.find('.markdown-selector').click(function(e) {
e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click();
- form_textarea.focus();
+ formTextarea.focus();
});
}
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/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 62f654ed343..9db26f99a75 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -277,6 +277,7 @@
.toolbar-text {
font-size: 14px;
line-height: 16px;
+ margin-top: 2px;
@media (min-width: $screen-md-min) {
float: left;
@@ -402,3 +403,45 @@
}
}
}
+
+.uploading-container {
+ float: right;
+
+ @media (max-width: $screen-xs-max) {
+ float: left;
+ margin-top: 5px;
+ }
+}
+
+.uploading-error-icon,
+.uploading-error-message {
+ color: $gl-text-red;
+}
+
+.uploading-error-message {
+ @media (max-width: $screen-xs-max) {
+ &::after {
+ content: "\a";
+ white-space: pre;
+ }
+ }
+}
+
+.uploading-progress {
+ margin-right: 5px;
+}
+
+.attach-new-file,
+.button-attach-file,
+.retry-uploading-link {
+ color: $gl-link-color;
+ padding: 0;
+ background: none;
+ border: 0;
+ font-size: 14px;
+ line-height: 16px;
+}
+
+.markdown-selector {
+ color: $gl-link-color;
+}
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/icons_helper.rb b/app/helpers/icons_helper.rb
index ef96a554b7e..f29faeca22d 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -7,9 +7,10 @@ module IconsHelper
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
- if (options.keys & %w[aria-hidden aria-label]).empty?
- # Add `aria-hidden` if there are no aria's set
+ if (options.keys & %w[aria-hidden aria-label data-hidden]).empty?
+ # Add 'aria-hidden' and 'data-hidden' if they are not set in options.
options['aria-hidden'] = true
+ options['data-hidden'] = true
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb
index 7912cac65d3..9e84e2a8f62 100644
--- a/app/services/members/authorized_destroy_service.rb
+++ b/app/services/members/authorized_destroy_service.rb
@@ -29,7 +29,7 @@ module Members
issue_ids = IssuesFinder.new(user, group_id: member.source_id, assignee_id: member.user_id).
execute.pluck(:id)
- IssueAssignee.destroy_all(issue_id: issue_ids, user_id: member.user_id)
+ IssueAssignee.delete_all(issue_id: issue_ids, user_id: member.user_id)
MergeRequestsFinder.new(user, group_id: member.source_id, assignee_id: member.user_id).
execute.
@@ -37,10 +37,15 @@ module Members
else
project = member.source
- IssueAssignee.destroy_all(
- user_id: member.user_id,
- issue_id: project.issues.opened.assigned_to(member.user).select(:id)
- )
+ # SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X
+ issues = Issue.unscoped.select(1).
+ where('issues.id = issue_assignees.issue_id').
+ where(project_id: project.id)
+
+ # DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
+ IssueAssignee.unscoped.
+ where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues).
+ delete_all
project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
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/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/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 81d97eabe65..7ce6130de60 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -9,6 +9,27 @@
- else
is
supported
- %button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' }
- = icon('file-image-o', class: 'toolbar-button-icon')
- Attach a file
+
+ %span.uploading-container
+ %span.uploading-progress-container.hide
+ = icon('file-image-o', class: 'toolbar-button-icon')
+ %span.attaching-file-message
+ -# Populated by app/assets/javascripts/dropzone_input.js
+ %span.uploading-progress 0%
+ %span.uploading-spinner
+ = icon('spinner spin', class: 'toolbar-button-icon')
+
+ %span.uploading-error-container.hide
+ %span.uploading-error-icon
+ = icon('file-image-o', class: 'toolbar-button-icon')
+ %span.uploading-error-message
+ -# Populated by app/assets/javascripts/dropzone_input.js
+ %button.retry-uploading-link{ type: 'button' } Try again
+ or
+ %button.attach-new-file.markdown-selector{ type: 'button' } attach a new file
+
+ %button.markdown-selector.button-attach-file{ type: 'button', tabindex: '-1' }
+ = icon('file-image-o', class: 'toolbar-button-icon')
+ Attach a file
+
+ %button.btn.btn-default.btn-xs.hide.button-cancel-uploading-files{ type: 'button' } Cancel