Skip to content
Snippets Groups Projects
Unverified Commit d0bb16fc authored by Robert Speicher's avatar Robert Speicher
Browse files

Merge branch 'master' into 11-7-stable

parents f2fee7bc 710f2ec5
No related branches found
No related tags found
No related merge requests found
Showing
with 235 additions and 24 deletions
Loading
Loading
@@ -221,13 +221,13 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
// eslint-disable-next-line no-template-curly-in-string
insertTpl: '${atwho-at}${id}',
insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(issues) {
Loading
Loading
@@ -238,6 +238,7 @@ class GfmAutoComplete {
return {
id: i.iid,
title: sanitize(i.title),
reference: i.reference,
search: `${i.iid} ${i.title}`,
};
});
Loading
Loading
@@ -287,13 +288,13 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
// eslint-disable-next-line no-template-curly-in-string
insertTpl: '${atwho-at}${id}',
insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(merges) {
Loading
Loading
@@ -304,6 +305,7 @@ class GfmAutoComplete {
return {
id: m.iid,
title: sanitize(m.title),
reference: m.reference,
search: `${m.iid} ${m.title}`,
};
});
Loading
Loading
@@ -397,7 +399,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
Loading
Loading
@@ -596,8 +598,12 @@ GfmAutoComplete.Labels = {
};
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
templateFunction(id, title) {
return `<li><small>${id}</small> ${_.escape(title)}</li>`;
insertTemplateFunction(value) {
// eslint-disable-next-line no-template-curly-in-string
return value.reference || '${atwho-at}${id}';
},
templateFunction({ id, title, reference }) {
return `<li><small>${reference || id}</small> ${_.escape(title)}</li>`;
},
};
// Milestones
Loading
Loading
Loading
Loading
@@ -6,6 +6,7 @@ export default {
components: {
GlAreaChart,
},
inheritAttrs: false,
props: {
graphData: {
type: Object,
Loading
Loading
@@ -25,6 +26,11 @@ export default {
);
},
},
alertData: {
type: Object,
required: false,
default: () => ({}),
},
},
computed: {
chartData() {
Loading
Loading
@@ -74,9 +80,6 @@ export default {
const [date, value] = params;
return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)];
},
onCreated(chart) {
this.$emit('created', chart);
},
},
};
</script>
Loading
Loading
@@ -88,10 +91,11 @@ export default {
<div class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
v-bind="$attrs"
:data="chartData"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
@created="onCreated"
:thresholds="alertData"
/>
</div>
</template>
Loading
Loading
@@ -144,6 +144,9 @@ export default {
}
},
methods: {
getGraphAlerts(graphId) {
return this.alertData ? this.alertData[graphId] || {} : {};
},
getGraphsData() {
this.state = 'loading';
Promise.all([
Loading
Loading
@@ -223,6 +226,8 @@ export default {
:tags-path="tagsPath"
:show-legend="showLegend"
:small-graph="forceSmallGraph"
:alert-data="getGraphAlerts(graphData.id)"
group-id="monitor-area-chart"
>
<!-- EE content -->
{{ null }}
Loading
Loading
Loading
Loading
@@ -101,3 +101,41 @@ body.modal-open {
margin: 0;
}
}
.issues-import-modal,
.issues-export-modal {
.modal-header {
justify-content: flex-start;
.import-export-svg-container {
flex-grow: 1;
height: 56px;
padding: $gl-btn-padding $gl-btn-padding 0;
> svg {
float: right;
height: 100%;
}
}
}
.modal-body {
padding: 0;
.modal-subheader {
justify-content: flex-start;
align-items: center;
border-bottom: 1px solid $modal-border-color;
padding: 14px;
}
.modal-text {
padding: $gl-padding-24 $gl-padding;
min-height: $modal-body-height;
}
}
.checkmark {
color: $green-400;
}
}
Loading
Loading
@@ -656,6 +656,7 @@ $border-color-settings: #e1e1e1;
Modals
*/
$modal-body-height: 134px;
$modal-border-color: #e9ecef;
 
$priority-label-empty-state-width: 114px;
 
Loading
Loading
Loading
Loading
@@ -155,6 +155,14 @@ ul.related-merge-requests > li {
}
}
 
.issues-nav-controls {
font-size: 0;
.btn-group:empty {
display: none;
}
}
.issuable-email-modal-btn {
padding: 0;
color: $blue-600;
Loading
Loading
Loading
Loading
@@ -7,12 +7,12 @@ module UploadsActions
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
 
def create
link_to_file = UploadService.new(model, params[:file], uploader_class).execute
uploader = UploadService.new(model, params[:file], uploader_class).execute
 
respond_to do |format|
if link_to_file
if uploader
format.json do
render json: { link: link_to_file }
render json: { link: uploader.to_h }
end
else
format.json do
Loading
Loading
Loading
Loading
@@ -10,7 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
include SpammableActions
 
def self.issue_except_actions
%i[index calendar new create bulk_update]
%i[index calendar new create bulk_update import_csv]
end
 
def self.set_issuables_index_only_actions
Loading
Loading
@@ -37,6 +37,8 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
 
before_action :authorize_import_issues!, only: [:import_csv]
before_action :set_suggested_issues_feature_flags, only: [:new]
 
respond_to :html
Loading
Loading
@@ -175,6 +177,20 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
 
def import_csv
return render_404 unless Feature.enabled?(:issues_import_csv)
if uploader = UploadService.new(project, params[:file]).execute
ImportIssuesCsvWorker.perform_async(current_user.id, project.id, uploader.upload.id)
flash[:notice] = _("Your issues are being imported. Once finished, you'll get a confirmation email.")
else
flash[:alert] = _("File upload error.")
end
redirect_to project_issues_path(project)
end
protected
 
# rubocop: disable CodeReuse/ActiveRecord
Loading
Loading
Loading
Loading
@@ -6,6 +6,8 @@ module Projects
before_action :check_license
before_action :authorize_update_environment!
 
helper_method :error_tracking_setting
def show
end
 
Loading
Loading
@@ -22,13 +24,18 @@ module Projects
 
private
 
def error_tracking_setting
@error_tracking_setting ||= project.error_tracking_setting ||
project.build_error_tracking_setting
end
def update_params
params.require(:project).permit(permitted_project_params)
end
 
# overridden in EE
def permitted_project_params
{}
{ error_tracking_setting_attributes: [:enabled, :api_url, :token] }
end
 
def check_license
Loading
Loading
Loading
Loading
@@ -285,7 +285,7 @@ module ProjectsHelper
 
# overridden in EE
def settings_operations_available?
false
Feature.enabled?(:error_tracking, @project) && can?(current_user, :read_environment, @project)
end
 
private
Loading
Loading
@@ -549,6 +549,7 @@ module ProjectsHelper
services#edit
repository#show
ci_cd#show
operations#show
badges#index
pages#show
]
Loading
Loading
Loading
Loading
@@ -77,6 +77,17 @@ module Emails
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
end
 
def import_issues_csv_email(user_id, project_id, results)
@user = User.find(user_id)
@project = Project.find(project_id)
@results = results
mail(to: @user.notification_email, subject: subject('Imported issues')) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
end
private
 
def setup_issue_mail(issue_id, recipient_id)
Loading
Loading
Loading
Loading
@@ -76,6 +76,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.changed_milestone_issue_email(user.id, issue.id, milestone, user.id)
end
 
def import_issues_csv_email
Notify.import_issues_csv_email(user, project, { success: 3, errors: [5, 6, 7], valid_file: true })
end
def closed_merge_request_email
Notify.closed_merge_request_email(user.id, issue.id, user.id).message
end
Loading
Loading
# frozen_string_literal: true
module ErrorTracking
class ProjectErrorTrackingSetting < ActiveRecord::Base
belongs_to :project
validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true }
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm'
end
end
Loading
Loading
@@ -456,6 +456,10 @@ class Note < ActiveRecord::Base
Upload.find_by(model: self, path: paths)
end
 
def parent
project
end
private
 
def keep_around_commit
Loading
Loading
Loading
Loading
@@ -187,6 +187,7 @@ class Project < ActiveRecord::Base
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :project_repository, inverse_of: :project
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
 
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
Loading
Loading
@@ -295,6 +296,8 @@ class Project < ActiveRecord::Base
allow_destroy: true,
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
 
accepts_nested_attributes_for :error_tracking_setting, update_only: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
Loading
Loading
@@ -1786,6 +1789,24 @@ class Project < ActiveRecord::Base
handle_update_attribute_error(e, value)
end
 
# Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
#
# @return [Boolean] true when set to read_only or false when an existing git transfer is in progress
def set_repository_read_only!
with_lock do
break false if git_transfer_in_progress?
update_column(:repository_read_only, true)
end
end
# Set repository as writable again
def set_repository_writable!
with_lock do
update_column(repository_read_only, false)
end
end
def pushes_since_gc
Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i }
end
Loading
Loading
@@ -1900,15 +1921,17 @@ class Project < ActiveRecord::Base
def migrate_to_hashed_storage!
return unless storage_upgradable?
 
update!(repository_read_only: true)
if repo_reference_count > 0 || wiki_reference_count > 0
if git_transfer_in_progress?
ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
else
ProjectMigrateHashedStorageWorker.perform_async(id)
end
end
 
def git_transfer_in_progress?
repo_reference_count > 0 || wiki_reference_count > 0
end
def storage_version=(value)
super
 
Loading
Loading
Loading
Loading
@@ -222,6 +222,8 @@ class ProjectPolicy < BasePolicy
rule { owner | admin | guest | group_member }.prevent :request_access
rule { ~request_access_enabled }.prevent :request_access
 
rule { can?(:developer_access) & can?(:create_issue) }.enable :import_issues
rule { can?(:developer_access) }.policy do
enable :admin_merge_request
enable :admin_milestone
Loading
Loading
# frozen_string_literal: true
module Issues
class ImportCsvService
def initialize(user, project, csv_io)
@user = user
@project = project
@csv_io = csv_io
@results = { success: 0, error_lines: [], parse_error: false }
end
def execute
process_csv
email_results_to_user
@results
end
private
def process_csv
csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
CSV.new(csv_data, col_sep: detect_col_sep(csv_data.lines.first), headers: true).each.with_index(2) do |row, line_no|
issue = Issues::CreateService.new(@project, @user, title: row[0], description: row[1]).execute
if issue.persisted?
@results[:success] += 1
else
@results[:error_lines].push(line_no)
end
end
rescue ArgumentError, CSV::MalformedCSVError
@results[:parse_error] = true
end
def email_results_to_user
Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_now
end
def detect_col_sep(header)
if header.include?(",")
","
elsif header.include?(";")
";"
elsif header.include?("\t")
"\t"
else
raise CSV::MalformedCSVError
end
end
end
end
Loading
Loading
@@ -31,7 +31,7 @@ module Notes
return if command_params.empty?
return unless supported?(note)
 
self.class.noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
self.class.noteable_update_service(note).new(note.parent, current_user, command_params).execute(note.noteable)
end
end
end
Loading
Loading
@@ -2,6 +2,8 @@
 
module Projects
module HashedStorage
RepositoryMigrationError = Class.new(StandardError)
class MigrateRepositoryService < BaseService
include Gitlab::ShellAdapter
 
Loading
Loading
@@ -16,6 +18,8 @@ module Projects
end
 
def execute
try_to_set_repository_read_only!
@old_storage_version = project.storage_version
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
project.ensure_storage_path_exists
Loading
Loading
@@ -48,6 +52,16 @@ module Projects
 
private
 
def try_to_set_repository_read_only!
# Mitigate any push operation to start during migration
unless project.set_repository_read_only!
migration_error = "Target repository '#{old_disk_path}' cannot be made read-only as there is a git transfer in progress"
logger.error migration_error
raise RepositoryMigrationError, migration_error
end
end
# rubocop: disable CodeReuse/ActiveRecord
def has_wiki?
gitlab_shell.exists?(project.repository_storage, "#{old_wiki_disk_path}.git")
Loading
Loading
Loading
Loading
@@ -12,7 +12,7 @@ module Projects
private
 
def project_update_params
{}
params.slice(:error_tracking_setting_attributes)
end
end
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment