summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/commons/bootstrap.js60
-rw-r--r--app/assets/javascripts/notifications_dropdown.js1
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss1
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb6
-rw-r--r--app/controllers/concerns/creates_commit.rb25
-rw-r--r--app/controllers/concerns/lfs_request.rb6
-rw-r--r--app/controllers/concerns/membership_actions.rb35
-rw-r--r--app/controllers/concerns/spammable_actions.rb6
-rw-r--r--app/controllers/concerns/uploads_actions.rb4
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/graphql_controller.rb5
-rw-r--r--app/controllers/groups/runners_controller.rb10
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb12
-rw-r--r--app/controllers/import/fogbugz_controller.rb6
-rw-r--r--app/controllers/import/gitea_controller.rb2
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb4
-rw-r--r--app/controllers/import/google_code_controller.rb12
-rw-r--r--app/controllers/projects/blob_controller.rb4
-rw-r--r--app/controllers/projects/branches_controller.rb4
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/group_links_controller.rb2
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/imports_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/lfs_api_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb8
-rw-r--r--app/controllers/projects/mirrors_controller.rb4
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb8
-rw-r--r--app/controllers/projects/runners_controller.rb10
-rw-r--r--app/controllers/projects/services_controller.rb10
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb6
-rw-r--r--app/controllers/projects/tree_controller.rb2
-rw-r--r--app/controllers/projects/triggers_controller.rb14
-rw-r--r--app/controllers/projects/wikis_controller.rb12
-rw-r--r--app/models/diff_note.rb4
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/suggestion.rb46
-rw-r--r--app/services/concerns/suggestible.rb30
-rw-r--r--app/services/merge_requests/refresh_service.rb9
-rw-r--r--app/services/suggestions/apply_service.rb4
-rw-r--r--app/services/suggestions/create_service.rb46
-rw-r--r--app/services/suggestions/outdate_service.rb19
-rw-r--r--changelogs/unreleased/issue_58547.yml5
-rw-r--r--changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-duration-logs.yml5
-rw-r--r--config/initializers/lograge.rb7
-rw-r--r--db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb19
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/high_availability/nfs.md5
-rw-r--r--doc/administration/logs.md13
-rw-r--r--doc/api/suggestions.md2
-rw-r--r--doc/user/profile/account/two_factor_authentication.md7
-rw-r--r--doc/user/project/clusters/index.md16
-rw-r--r--doc/user/project/integrations/webhooks.md30
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/banzai/suggestions_parser.rb2
-rw-r--r--lib/gitlab/diff/suggestion.rb52
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb41
-rw-r--r--lib/gitlab/gitaly_client.rb18
-rw-r--r--lib/gitlab/grape_logging/loggers/perf_logger.rb5
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--locale/gitlab.pot240
-rw-r--r--package.json20
-rw-r--r--qa/README.md31
-rw-r--r--qa/qa.rb2
-rw-r--r--qa/qa/resource/api_fabricator.rb3
-rw-r--r--qa/qa/runtime/address.rb7
-rw-r--r--qa/qa/runtime/feature.rb36
-rw-r--r--qa/qa/scenario/bootable.rb10
-rw-r--r--qa/qa/scenario/shared_attributes.rb12
-rw-r--r--qa/qa/scenario/template.rb31
-rw-r--r--qa/qa/scenario/test/instance/all.rb1
-rw-r--r--qa/qa/scenario/test/instance/smoke.rb1
-rw-r--r--qa/qa/scenario/test/integration/mattermost.rb9
-rw-r--r--qa/qa/support/api.rb3
-rw-r--r--qa/spec/runtime/feature_spec.rb41
-rw-r--r--qa/spec/runtime/scenario_spec.rb8
-rw-r--r--qa/spec/scenario/bootable_spec.rb9
-rw-r--r--qa/spec/scenario/template_spec.rb28
-rw-r--r--qa/spec/scenario/test/integration/github_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/mattermost_spec.rb11
-rw-r--r--qa/spec/shared_examples/scenario_shared_examples.rb40
-rw-r--r--spec/controllers/graphql_controller_spec.rb45
-rw-r--r--spec/factories/suggestions.rb6
-rw-r--r--spec/javascripts/ide/components/error_message_spec.js2
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js74
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js12
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js17
-rw-r--r--spec/javascripts/pipelines/stage_spec.js16
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js44
-rw-r--r--spec/lib/gitlab/diff/suggestion_spec.rb88
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb73
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml11
-rw-r--r--spec/requests/api/suggestions_spec.rb3
-rw-r--r--spec/serializers/suggestion_entity_spec.rb4
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb38
-rw-r--r--spec/services/suggestions/apply_service_spec.rb93
-rw-r--r--spec/services/suggestions/create_service_spec.rb73
-rw-r--r--spec/services/suggestions/outdate_service_spec.rb102
-rw-r--r--spec/services/verify_pages_domain_service_spec.rb142
-rw-r--r--yarn.lock108
108 files changed, 1686 insertions, 527 deletions
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index fba30aea9ae..e5e1cbb1e62 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -16,3 +16,63 @@ $.fn.extend({
.removeClass('disabled');
},
});
+
+/*
+ Starting with bootstrap 4.3.1, bootstrap sanitizes html used for tooltips / popovers.
+ This extends the default whitelists with more elements / attributes:
+ https://getbootstrap.com/docs/4.3/getting-started/javascript/#sanitizer
+ */
+const whitelist = $.fn.tooltip.Constructor.Default.whiteList;
+
+const inputAttributes = ['value', 'type'];
+
+const dataAttributes = [
+ 'data-toggle',
+ 'data-placement',
+ 'data-container',
+ 'data-title',
+ 'data-class',
+ 'data-clipboard-text',
+ 'data-placement',
+];
+
+// Whitelisting data attributes
+whitelist['*'] = [
+ ...whitelist['*'],
+ ...dataAttributes,
+ 'title',
+ 'width height',
+ 'abbr',
+ 'datetime',
+ 'name',
+ 'width',
+ 'height',
+];
+
+// Whitelist missing elements:
+whitelist.label = ['for'];
+whitelist.button = [...inputAttributes];
+whitelist.input = [...inputAttributes];
+
+whitelist.tt = [];
+whitelist.samp = [];
+whitelist.kbd = [];
+whitelist.var = [];
+whitelist.dfn = [];
+whitelist.cite = [];
+whitelist.big = [];
+whitelist.address = [];
+whitelist.dl = [];
+whitelist.dt = [];
+whitelist.dd = [];
+whitelist.abbr = [];
+whitelist.acronym = [];
+whitelist.blockquote = [];
+whitelist.del = [];
+whitelist.ins = [];
+whitelist['gl-emoji'] = [];
+
+// Whitelisting SVG tags and attributes
+whitelist.svg = ['viewBox'];
+whitelist.use = ['xlink:href'];
+whitelist.path = ['d'];
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index e7fa05faa8a..6aed2492084 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -4,6 +4,7 @@ import Flash from './flash';
export default function notificationsDropdown() {
$(document).on('click', '.update-notification', function updateNotificationCallback(e) {
e.preventDefault();
+
if ($(this).is('.is-active') && $(this).data('notificationLevel') === 'custom') {
return;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index efebbd124d0..5d4c84c494d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -711,3 +711,11 @@ $mr-version-controls-height: 56px;
Compare Branches
*/
$compare-branches-sticky-header-height: 68px;
+
+/**
+ Bootstrap 4.2.0 introduced new icons for validating forms.
+ Our design system does not use those, so we are disabling them for now:
+ - Docs: https://getbootstrap.com/docs/4.3/components/forms/#server-side
+ - Issue: https://gitlab.com/gitlab-org/design.gitlab.com/issues/242
+ */
+$enable-validation-icons: false;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index efcc437bd7f..fb4d3f23cd9 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -9,7 +9,6 @@ $input-border-color: $gray-200;
$input-color: $gl-text-color;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
-$input-line-height: 20px;
$btn-line-height: 20px;
$table-accent-bg: $gray-light;
$card-border-color: $border-color;
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 5507328f8ae..d5c4712bd78 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -36,7 +36,7 @@ module AuthenticatesWithTwoFactor
end
def locked_user_redirect(user)
- flash.now[:alert] = 'Invalid Login or password'
+ flash.now[:alert] = _('Invalid Login or password')
render 'devise/sessions/new'
end
@@ -66,7 +66,7 @@ module AuthenticatesWithTwoFactor
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
- flash.now[:alert] = 'Invalid two-factor code.'
+ flash.now[:alert] = _('Invalid two-factor code.')
prompt_for_two_factor(user)
end
end
@@ -83,7 +83,7 @@ module AuthenticatesWithTwoFactor
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
- flash.now[:alert] = 'Authentication via U2F device failed.'
+ flash.now[:alert] = _('Authentication via U2F device failed.')
prompt_for_two_factor(user)
end
end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index b3777fd2b0f..e8e681ce649 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -31,7 +31,7 @@ module CreatesCommit
respond_to do |format|
format.html { redirect_to success_path }
- format.json { render json: { message: "success", filePath: success_path } }
+ format.json { render json: { message: _("success"), filePath: success_path } }
end
else
flash[:alert] = result[:message]
@@ -45,7 +45,7 @@ module CreatesCommit
redirect_to failure_path
end
end
- format.json { render json: { message: "failed", filePath: failure_path } }
+ format.json { render json: { message: _("failed"), filePath: failure_path } }
end
end
end
@@ -60,15 +60,22 @@ module CreatesCommit
private
def update_flash_notice(success_notice)
- flash[:notice] = success_notice || "Your changes have been successfully committed."
+ flash[:notice] = success_notice || _("Your changes have been successfully committed.")
if create_merge_request?
- if merge_request_exists?
- flash[:notice] = nil
- else
- target = different_project? ? "project" : "branch"
- flash[:notice] = flash[:notice] + " You can now submit a merge request to get this change into the original #{target}."
- end
+ flash[:notice] =
+ if merge_request_exists?
+ nil
+ else
+ mr_message =
+ if different_project?
+ _("You can now submit a merge request to get this change into the original project.")
+ else
+ _("You can now submit a merge request to get this change into the original branch.")
+ end
+
+ flash[:notice] += " " + mr_message
+ end
end
end
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 57e444319e0..f7137a04437 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -26,7 +26,7 @@ module LfsRequest
render(
json: {
- message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
+ message: _('Git LFS is not enabled on this GitLab server, contact your admin.'),
documentation_url: help_url
},
status: :not_implemented
@@ -51,7 +51,7 @@ module LfsRequest
def render_lfs_forbidden
render(
json: {
- message: 'Access forbidden. Check your access level.',
+ message: _('Access forbidden. Check your access level.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
@@ -62,7 +62,7 @@ module LfsRequest
def render_lfs_not_found
render(
json: {
- message: 'Not found.',
+ message: _('Not found.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 6402e01ddc0..0b2756c0c6a 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -9,7 +9,7 @@ module MembershipActions
result = Members::CreateService.new(current_user, create_params).execute(membershipable)
if result[:status] == :success
- redirect_to members_page_url, notice: 'Users were successfully added.'
+ redirect_to members_page_url, notice: _('Users were successfully added.')
else
redirect_to members_page_url, alert: result[:message]
end
@@ -35,9 +35,16 @@ module MembershipActions
respond_to do |format|
format.html do
- source = source_type == 'group' ? 'group and any subresources' : source_type
+ message =
+ begin
+ case membershipable
+ when Namespace
+ _("User was successfully removed from group and any subresources.")
+ else
+ _("User was successfully removed from project.")
+ end
+ end
- message = "User was successfully removed from #{source}."
redirect_to members_page_url, notice: message
end
@@ -49,7 +56,7 @@ module MembershipActions
membershipable.request_access(current_user)
redirect_to polymorphic_path(membershipable),
- notice: 'Your request for access has been queued for review.'
+ notice: _('Your request for access has been queued for review.')
end
def approve_access_request
@@ -68,9 +75,9 @@ module MembershipActions
notice =
if member.request?
- "Your access request to the #{source_type} has been withdrawn."
+ _("Your access request to the %{source_type} has been withdrawn.") % { source_type: source_type }
else
- "You left the \"#{membershipable.human_name}\" #{source_type}."
+ _("You left the \"%{membershipable_human_name}\" %{source_type}.") % { membershipable_human_name: membershipable.human_name, source_type: source_type }
end
respond_to do |format|
@@ -90,9 +97,9 @@ module MembershipActions
if member.invite?
member.resend_invite
- redirect_to members_page_url, notice: 'The invitation was successfully resent.'
+ redirect_to members_page_url, notice: _('The invitation was successfully resent.')
else
- redirect_to members_page_url, alert: 'The invitation has already been accepted.'
+ redirect_to members_page_url, alert: _('The invitation has already been accepted.')
end
end
@@ -125,6 +132,16 @@ module MembershipActions
end
def source_type
- @source_type ||= membershipable.class.to_s.humanize(capitalize: false)
+ @source_type ||=
+ begin
+ case membershipable
+ when Namespace
+ _("group")
+ when Project
+ _("project")
+ else
+ raise "Unknown membershipable type: #{membershipable}!"
+ end
+ end
end
end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index c3a1b12af84..a8ffa33f1c7 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -12,9 +12,9 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
+ redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase }
else
- redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.')
end
end
@@ -33,7 +33,7 @@ module SpammableActions
ensure_spam_config_loaded!
if params[:recaptcha_verification]
- flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
end
respond_to do |format|
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 4ec0e94df9a..59f6d3452a3 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -16,7 +16,7 @@ module UploadsActions
end
else
format.json do
- render json: 'Invalid file.', status: :unprocessable_entity
+ render json: _('Invalid file.'), status: :unprocessable_entity
end
end
end
@@ -57,7 +57,7 @@ module UploadsActions
render json: authorized
rescue SocketError
- render json: "Error uploading file", status: :internal_server_error
+ render json: _("Error uploading file"), status: :internal_server_error
end
private
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 3fa582cf25b..f173c263474 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -21,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
format.html do
redirect_to dashboard_todos_path,
status: 302,
- notice: 'Todo was successfully marked as done.'
+ notice: _('Todo was successfully marked as done.')
end
format.js { head :ok }
format.json { render json: todos_counts }
@@ -32,7 +32,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
- format.html { redirect_to dashboard_todos_path, status: 302, notice: 'All todos were marked as done.' }
+ format.html { redirect_to dashboard_todos_path, status: 302, notice: _('All todos were marked as done.') }
format.js { head :ok }
format.json { render json: todos_counts.merge(updated_ids: updated_ids) }
end
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index e147d32be2e..7b5dc22815c 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -12,6 +12,7 @@ class GraphqlController < ApplicationController
protect_from_forgery with: :null_session, only: :execute
before_action :check_graphql_feature_flag!
+ before_action :authorize_access_api!
before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
def execute
@@ -37,6 +38,10 @@ class GraphqlController < ApplicationController
private
+ def authorize_access_api!
+ access_denied!("API not accessible for user.") unless can?(current_user, :access_api)
+ end
+
# Overridden from the ApplicationController to make the response look like
# a GraphQL response. That is nicely picked up in Graphiql.
def render_404
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index dd8fbf7a029..f8e32451b02 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -16,7 +16,7 @@ class Groups::RunnersController < Groups::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
- redirect_to group_runner_path(@group, @runner), notice: 'Runner was successfully updated.'
+ redirect_to group_runner_path(@group, @runner), notice: _('Runner was successfully updated.')
else
render 'edit'
end
@@ -30,17 +30,17 @@ class Groups::RunnersController < Groups::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.')
else
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.')
else
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index f378f7ac79a..c465e622de0 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -13,7 +13,7 @@ module Groups
def reset_registration_token
@group.reset_runners_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('GroupSettings|New runners registration token has been generated!')
redirect_to group_settings_ci_cd_path
end
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 2b1395f364f..293d76ea765 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index f333e43b892..643a3bfed1f 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -25,7 +25,7 @@ class Import::BitbucketServerController < Import::BaseController
repo = bitbucket_client.repo(@project_key, @repo_slug)
unless repo
- return render json: { errors: "Project #{@project_key}/#{@repo_slug} could not be found" }, status: :unprocessable_entity
+ return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity
end
project_name = params[:new_name].presence || repo.name
@@ -41,10 +41,10 @@ class Import::BitbucketServerController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
- rescue BitbucketServer::Connection::ConnectionError => e
- render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
+ rescue BitbucketServer::Connection::ConnectionError => error
+ render json: { errors: _("Unable to connect to server: %{error}") % { error: error } }, status: :unprocessable_entity
end
def configure
@@ -65,8 +65,8 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
- rescue BitbucketServer::Connection::ConnectionError => e
- flash[:alert] = "Unable to connect to server: #{e}"
+ rescue BitbucketServer::Connection::ConnectionError => error
+ flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
clear_session_data
redirect_to new_import_bitbucket_server_path
end
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 5a439e6de78..a37ba682b91 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -14,7 +14,7 @@ class Import::FogbugzController < Import::BaseController
res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
rescue
# If the URI is invalid various errors can occur
- return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL'
+ return redirect_to new_import_fogbugz_path, alert: _('Could not connect to FogBugz, check your URL')
end
session[:fogbugz_token] = res.get_token
session[:fogbugz_uri] = params[:uri]
@@ -29,14 +29,14 @@ class Import::FogbugzController < Import::BaseController
user_map = params[:users]
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
- flash.now[:alert] = 'All users must have a name.'
+ flash.now[:alert] = _('All users must have a name.')
return render 'new_user_map'
end
session[:fogbugz_user_map] = user_map
- flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.'
+ flash[:notice] = _('The user map has been saved. Continue by selecting the projects you want to import.')
redirect_to status_import_fogbugz_path
end
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index 68ad8650dba..a23b2f8139e 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -46,7 +46,7 @@ class Import::GiteaController < Import::GithubController
def provider_auth
if session[access_token_key].blank? || provider_url.blank?
redirect_to new_import_gitea_url,
- alert: 'You need to specify both an Access Token and a Host URL.'
+ alert: _('You need to specify both an Access Token and a Host URL.')
end
end
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 498de0b07b8..5ec8e9e6fc5 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -42,7 +42,7 @@ class Import::GitlabController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 354fba5d204..89889141be6 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -13,7 +13,7 @@ class Import::GitlabProjectsController < Import::BaseController
def create
unless file_is_valid?
- return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive (ending in .gz)." })
+ return redirect_back_or_default(options: { alert: _("You need to upload a GitLab project export archive (ending in .gz).") })
end
@project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
@@ -21,7 +21,7 @@ class Import::GitlabProjectsController < Import::BaseController
if @project.saved?
redirect_to(
project_path(@project),
- notice: "Project '#{@project.name}' is being imported."
+ notice: _("Project '%{project_name}' is being imported.") % { project_name: @project.name }
)
else
redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" })
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 331f06c3dd6..4dddfbcd20d 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -11,18 +11,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
- return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("You need to upload a Google Takeout archive.") })
end
begin
dump = JSON.parse(dump_file.read)
rescue
- return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") })
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
- return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") })
end
session[:google_code_dump] = dump
@@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController
begin
user_map = JSON.parse(user_map_json)
rescue
- flash.now[:alert] = "The entered user map is not a valid JSON user map."
+ flash.now[:alert] = _("The entered user map is not a valid JSON user map.")
return render "new_user_map"
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
- flash.now[:alert] = "The entered user map is not a valid JSON user map."
+ flash.now[:alert] = _("The entered user map is not a valid JSON user map.")
return render "new_user_map"
end
@@ -62,7 +62,7 @@ class Import::GoogleCodeController < Import::BaseController
session[:google_code_user_map] = user_map
- flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import."
+ flash[:notice] = _("The user map has been saved. Continue by selecting the projects you want to import.")
redirect_to status_import_google_code_path
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 0a33856a8d3..909b17e9c8d 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -29,7 +29,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
- create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
+ create_commit(Files::CreateService, success_notice: _("The file has been successfully created."),
success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
failure_view: :new,
failure_path: project_new_blob_path(@project, @ref))
@@ -81,7 +81,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
+ create_commit(Files::DeleteService, success_notice: _("The file has been successfully deleted."),
success_path: -> { after_delete_path },
failure_view: :show,
failure_path: project_blob_path(@project, @id))
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 32b7f3207ef..6ff2e222489 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -115,7 +115,7 @@ class Projects::BranchesController < Projects::ApplicationController
DeleteMergedBranchesService.new(@project, current_user).async_execute
redirect_to project_branches_path(@project),
- notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
+ notice: _('Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.')
end
private
@@ -143,7 +143,7 @@ class Projects::BranchesController < Projects::ApplicationController
def redirect_for_legacy_index_sort_or_search
# Normalize a legacy URL with redirect
if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
- redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.'
+ redirect_to project_branches_filtered_path(@project, state: 'all'), notice: _('Update your bookmarked URLs as filtered/sorted branches URL has been changed.')
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 6824a07dc76..514b03e23b5 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -38,7 +38,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def update
if deploy_key.update(update_params)
- flash[:notice] = 'Deploy key was successfully updated.'
+ flash[:notice] = _('Deploy key was successfully updated.')
redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings')
else
render 'edit'
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index bc942ba9288..dc65f9959db 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -18,7 +18,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
flash[:alert] = result[:message] if result[:http_status] == 409
else
- flash[:alert] = 'Please select a group.'
+ flash[:alert] = _('Please select a group.')
end
redirect_to project_project_members_path(project)
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index bc84418b79f..5fa0339f44d 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -32,7 +32,7 @@ class Projects::HooksController < Projects::ApplicationController
def update
if hook.update(hook_params)
- flash[:notice] = 'Hook was successfully updated.'
+ flash[:notice] = _('Hook was successfully updated.')
redirect_to project_settings_integrations_path(@project)
else
render 'edit'
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 8b33fa85c1e..8ee0bd26daf 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -42,9 +42,9 @@ class Projects::ImportsController < Projects::ApplicationController
def finished_notice
if @project.forked?
- 'The project was successfully forked.'
+ _('The project was successfully forked.')
else
- 'The project was successfully imported.'
+ _('The project was successfully imported.')
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b9d02a62fc3..e9ed5554ab4 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -95,9 +95,9 @@ class Projects::IssuesController < Projects::ApplicationController
if service.discussions_to_resolve.count(&:resolved?) > 0
flash[:notice] = if service.discussion_to_resolve_id
- "Resolved 1 discussion."
+ _("Resolved 1 discussion.")
else
- "Resolved all discussions."
+ _("Resolved all discussions.")
end
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index d5ce790e2d9..35cc32d3e63 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -122,7 +122,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
redirect_to project_job_path(project, @build),
- notice: "Job has been successfully erased!"
+ notice: _("Job has been successfully erased!")
else
respond_422
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 640038818f2..386a1f00bd2 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -132,7 +132,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to(project_labels_path(@project),
- notice: 'Failed to promote label due to internal error. Please contact administrators.')
+ notice: _('Failed to promote label due to internal error. Please contact administrators.'))
end
format.js
end
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index be40077d389..42c415757f9 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -26,7 +26,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def deprecated
render(
json: {
- message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
+ message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'),
documentation_url: "#{Gitlab.config.gitlab.url}/help"
},
status: :not_implemented
@@ -62,7 +62,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
else
object[:error] = {
code: 404,
- message: "Object does not exist on the server or you don't have permissions to access it"
+ message: _("Object does not exist on the server or you don't have permissions to access it")
}
end
end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index 045a4e974fe..011ac9a42f8 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -16,12 +16,12 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
render json: @conflicts_list
elsif @merge_request.can_be_merged?
render json: {
- message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
+ message: _('The merge conflicts for this merge request have already been resolved. Please return to the merge request.'),
type: 'error'
}
else
render json: {
- message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
+ message: _('The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.'),
type: 'error'
}
end
@@ -43,7 +43,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
return render_404 unless @conflicts_list.can_be_resolved_in_ui?
if @merge_request.can_be_merged?
- render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
+ render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') }
return
end
@@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
.new(merge_request)
.execute(current_user, params)
- flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
+ flash[:notice] = _('All merge conflicts were resolved. The merge request can now be merged.')
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e
diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb
index ab7ab13657a..ef330ae00f4 100644
--- a/app/controllers/projects/mirrors_controller.rb
+++ b/app/controllers/projects/mirrors_controller.rb
@@ -18,7 +18,7 @@ class Projects::MirrorsController < Projects::ApplicationController
result = ::Projects::UpdateService.new(project, current_user, mirror_params).execute
if result[:status] == :success
- flash[:notice] = 'Mirroring settings were successfully updated.'
+ flash[:notice] = _('Mirroring settings were successfully updated.')
else
flash[:alert] = project.errors.full_messages.join(', ').html_safe
end
@@ -38,7 +38,7 @@ class Projects::MirrorsController < Projects::ApplicationController
def update_now
if params[:sync_remote]
project.update_remote_mirrors
- flash[:notice] = "The remote repository is being updated..."
+ flash[:notice] = _("The remote repository is being updated...")
end
redirect_to_repository_settings(project, anchor: 'js-push-remote-settings')
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index acf56f0eb6a..fd5b89298a7 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -50,9 +50,11 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id)
if job_id
- flash[:notice] = "Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe
+ link_to_pipelines = view_context.link_to(_('Pipelines page'), project_pipelines_path(@project))
+ message = _("Successfully scheduled a pipeline to run. Go to the %{link_to_pipelines} for details.").html_safe % { link_to_pipelines: link_to_pipelines }
+ flash[:notice] = message.html_safe
else
- flash[:alert] = 'Unable to schedule a pipeline to run immediately'
+ flash[:alert] = _('Unable to schedule a pipeline to run immediately')
end
redirect_to pipeline_schedules_path(@project)
@@ -85,7 +87,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
return unless limiter.throttled?([current_user, schedule], 1)
- flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
+ flash[:alert] = _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
redirect_to pipeline_schedules_path(@project)
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 91f40b90aa8..ca62f54813b 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -15,7 +15,7 @@ class Projects::RunnersController < Projects::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
- redirect_to project_runner_path(@project, @runner), notice: 'Runner was successfully updated.'
+ redirect_to project_runner_path(@project, @runner), notice: _('Runner was successfully updated.')
else
render 'edit'
end
@@ -31,17 +31,17 @@ class Projects::RunnersController < Projects::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.'
+ redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.')
else
- redirect_to project_runners_path(@project), alert: 'Runner was not updated.'
+ redirect_to project_runners_path(@project), alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.'
+ redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.')
else
- redirect_to project_runners_path(@project), alert: 'Runner was not updated.'
+ redirect_to project_runners_path(@project), alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index f1c9d0d0f77..e0df51590ae 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -43,20 +43,20 @@ class Projects::ServicesController < Projects::ApplicationController
if outcome[:success]
{}
else
- { error: true, message: 'Test failed.', service_response: outcome[:result].to_s, test_failed: true }
+ { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true }
end
else
- { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(','), test_failed: false }
+ { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false }
end
rescue Gitlab::HTTP::BlockedUrlError => e
- { error: true, message: 'Test failed.', service_response: e.message, test_failed: true }
+ { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true }
end
def success_message
if @service.active?
- "#{@service.title} activated."
+ _("%{service_title} activated.") % { service_title: @service.title }
else
- "#{@service.title} settings saved, but not activated."
+ _("%{service_title} settings saved, but not activated.") % { service_title: @service.title }
end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index f2f63e986bb..d1c5cef76fa 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -13,7 +13,7 @@ module Projects
Projects::UpdateService.new(project, current_user, update_params).tap do |service|
result = service.execute
if result[:status] == :success
- flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
+ flash[:notice] = _("Pipelines settings for '%{project_name}' were successfully updated.") % { project_name: @project.name }
run_autodevops_pipeline(service)
@@ -39,7 +39,7 @@ module Projects
def reset_registration_token
@project.reset_runners_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('New runners registration token has been generated!')
redirect_to namespace_project_settings_ci_cd_path
end
@@ -58,7 +58,7 @@ module Projects
return unless service.run_auto_devops_pipeline?
if @project.empty_repo?
- flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+ flash[:warning] = _("This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch.")
return
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index edebfc55c17..90d53aa08ea 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -37,7 +37,7 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir
return render_404 unless @commit_params.values.all?
- create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
+ create_commit(Files::CreateDirService, success_notice: _("The directory has been successfully created."),
success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
failure_path: project_tree_path(@project, @ref))
end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index c7b4ebb2b24..284e119ca06 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -16,9 +16,9 @@ class Projects::TriggersController < Projects::ApplicationController
@trigger = project.triggers.create(trigger_params.merge(owner: current_user))
if @trigger.valid?
- flash[:notice] = 'Trigger was created successfully.'
+ flash[:notice] = _('Trigger was created successfully.')
else
- flash[:alert] = 'You could not create a new trigger.'
+ flash[:alert] = _('You could not create a new trigger.')
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -26,9 +26,9 @@ class Projects::TriggersController < Projects::ApplicationController
def take_ownership
if trigger.update(owner: current_user)
- flash[:notice] = 'Trigger was re-assigned.'
+ flash[:notice] = _('Trigger was re-assigned.')
else
- flash[:alert] = 'You could not take ownership of trigger.'
+ flash[:alert] = _('You could not take ownership of trigger.')
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -39,7 +39,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
- redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: 'Trigger was successfully updated.'
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: _('Trigger was successfully updated.')
else
render action: "edit"
end
@@ -47,9 +47,9 @@ class Projects::TriggersController < Projects::ApplicationController
def destroy
if trigger.destroy
- flash[:notice] = "Trigger removed."
+ flash[:notice] = _("Trigger removed.")
else
- flash[:alert] = "Could not remove the trigger."
+ flash[:alert] = _("Could not remove the trigger.")
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), status: :found
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 88dd111132b..da2420633ef 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.valid?
redirect_to(
project_wiki_path(@project, @page),
- notice: 'Wiki was successfully updated.'
+ notice: _('Wiki was successfully updated.')
)
else
render 'edit'
@@ -65,7 +65,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.persisted?
redirect_to(
project_wiki_path(@project, @page),
- notice: 'Wiki was successfully updated.'
+ notice: _('Wiki was successfully updated.')
)
else
render action: "edit"
@@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController
else
redirect_to(
project_wiki_path(@project, :home),
- notice: "Page not found"
+ notice: _("Page not found")
)
end
end
@@ -95,7 +95,7 @@ class Projects::WikisController < Projects::ApplicationController
redirect_to project_wiki_path(@project, :home),
status: 302,
- notice: "Page was successfully deleted"
+ notice: _("Page was successfully deleted")
rescue Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
@@ -118,7 +118,7 @@ class Projects::WikisController < Projects::ApplicationController
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
end
rescue ProjectWiki::CouldNotCreateWikiError
- flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
+ flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
redirect_to project_path(@project)
false
end
@@ -155,7 +155,7 @@ class Projects::WikisController < Projects::ApplicationController
end
def set_encoding_error
- flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+ flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
end
def file_blob
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 87755cf3f3d..feabea9b8ba 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -77,8 +77,8 @@ class DiffNote < Note
def supports_suggestion?
return false unless noteable.supports_suggestion? && on_text?
# We don't want to trigger side-effects of `diff_file` call.
- return false unless file = fetch_diff_file
- return false unless line = file.line_for_position(self.original_position)
+ return false unless file = latest_diff_file
+ return false unless line = file.line_for_position(self.position)
line&.suggestible?
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2c0692e1b45..19557fd476e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -66,6 +66,7 @@ class MergeRequest < ActiveRecord::Base
has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
has_many :merge_request_pipelines, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
+ has_many :suggestions, through: :notes
has_many :merge_request_assignees
# Will be deprecated at https://gitlab.com/gitlab-org/gitlab-ce/issues/59457
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index 09034646bff..22e2f11230d 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -1,11 +1,19 @@
# frozen_string_literal: true
class Suggestion < ApplicationRecord
+ include Suggestible
+
belongs_to :note, inverse_of: :suggestions
validates :note, presence: true
validates :commit_id, presence: true, if: :applied?
- delegate :original_position, :position, :noteable, to: :note
+ delegate :position, :noteable, to: :note
+
+ scope :active, -> { where(outdated: false) }
+
+ def diff_file
+ note.latest_diff_file
+ end
def project
noteable.source_project
@@ -19,37 +27,37 @@ class Suggestion < ApplicationRecord
position.file_path
end
- # For now, suggestions only serve as a way to send patches that
- # will change a single line (being able to apply multiple in the same place),
- # which explains `from_line` and `to_line` being the same line.
- # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
- # when allowing multi-line suggestions.
- def from_line
- position.new_line
- end
- alias_method :to_line, :from_line
-
- def from_original_line
- original_position.new_line
- end
- alias_method :to_original_line, :from_original_line
-
# `from_line_index` and `to_line_index` represents diff/blob line numbers in
# index-like way (N-1).
def from_line_index
from_line - 1
end
- alias_method :to_line_index, :from_line_index
- def appliable?
- return false unless note.supports_suggestion?
+ def to_line_index
+ to_line - 1
+ end
+ def appliable?(cached: true)
!applied? &&
noteable.opened? &&
+ !outdated?(cached: cached) &&
+ note.supports_suggestion? &&
different_content? &&
note.active?
end
+ # Overwrites outdated column
+ def outdated?(cached: true)
+ return super() if cached
+ return true unless diff_file
+
+ from_content != fetch_from_content
+ end
+
+ def target_line
+ position.new_line
+ end
+
private
def different_content?
diff --git a/app/services/concerns/suggestible.rb b/app/services/concerns/suggestible.rb
new file mode 100644
index 00000000000..0b9822b1909
--- /dev/null
+++ b/app/services/concerns/suggestible.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Suggestible
+ extend ActiveSupport::Concern
+
+ # This translates into limiting suggestion changes to `suggestion:-100+100`.
+ MAX_LINES_CONTEXT = 100.freeze
+
+ def fetch_from_content
+ diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ def from_line
+ real_above = [lines_above, MAX_LINES_CONTEXT].min
+ [target_line - real_above, 1].max
+ end
+
+ def to_line
+ real_below = [lines_below, MAX_LINES_CONTEXT].min
+ target_line + real_below
+ end
+
+ def diff_file
+ raise NotImplementedError
+ end
+
+ def target_line
+ raise NotImplementedError
+ end
+end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index f712b8863cd..f5dc5a0256d 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -20,6 +20,7 @@ module MergeRequests
close_upon_missing_source_branch_ref
post_merge_manually_merged
reload_merge_requests
+ outdate_suggestions
reset_merge_when_pipeline_succeeds
mark_pending_todos_done
cache_merge_requests_closing_issues
@@ -125,6 +126,14 @@ module MergeRequests
merge_request.source_branch == @push.branch_name
end
+ def outdate_suggestions
+ outdate_service = Suggestions::OutdateService.new
+
+ merge_requests_for_source_branch.each do |merge_request|
+ outdate_service.execute(merge_request)
+ end
+ end
+
def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
end
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index f778c5aa5f5..8ba50e22b09 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -7,7 +7,7 @@ module Suggestions
end
def execute(suggestion)
- unless suggestion.appliable?
+ unless suggestion.appliable?(cached: false)
return error('Suggestion is not appliable')
end
@@ -15,7 +15,7 @@ module Suggestions
return error('The file has been changed')
end
- diff_file = suggestion.note.latest_diff_file
+ diff_file = suggestion.diff_file
unless diff_file
return error('The file was not found')
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
index c7ac2452c53..1d3338c1b45 100644
--- a/app/services/suggestions/create_service.rb
+++ b/app/services/suggestions/create_service.rb
@@ -9,52 +9,24 @@ module Suggestions
def execute
return unless @note.supports_suggestion?
- diff_file = @note.latest_diff_file
-
- return unless diff_file
-
- suggestions = Banzai::SuggestionsParser.parse(@note.note)
-
- # For single line suggestion we're only looking forward to
- # change the line receiving the comment. Though, in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
- # we'll introduce a ```suggestion:L<x>-<y>, so this will
- # slightly change.
- comment_line = @note.position.new_line
+ suggestions = Gitlab::Diff::SuggestionsParser.parse(@note.note,
+ project: @note.project,
+ position: @note.position)
rows =
suggestions.map.with_index do |suggestion, index|
- from_content = changing_lines(diff_file, comment_line, comment_line)
+ creation_params =
+ suggestion.to_hash.slice(:from_content,
+ :to_content,
+ :lines_above,
+ :lines_below)
- # The parsed suggestion doesn't have information about the correct
- # ending characters (we may have a line break, or not), so we take
- # this information from the last line being changed (last
- # characters).
- endline_chars = line_break_chars(from_content.lines.last)
- to_content = "#{suggestion}#{endline_chars}"
-
- {
- note_id: @note.id,
- from_content: from_content,
- to_content: to_content,
- relative_order: index
- }
+ creation_params.merge!(note_id: @note.id, relative_order: index)
end
rows.in_groups_of(100, false) do |rows|
Gitlab::Database.bulk_insert('suggestions', rows)
end
end
-
- private
-
- def changing_lines(diff_file, from_line, to_line)
- diff_file.new_blob_lines_between(from_line, to_line).join
- end
-
- def line_break_chars(line)
- match = /\r\n|\r|\n/.match(line)
- match[0] if match
- end
end
end
diff --git a/app/services/suggestions/outdate_service.rb b/app/services/suggestions/outdate_service.rb
new file mode 100644
index 00000000000..a33aac9f6b5
--- /dev/null
+++ b/app/services/suggestions/outdate_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Suggestions
+ class OutdateService
+ def execute(merge_request)
+ # rubocop: disable CodeReuse/ActiveRecord
+ suggestions = merge_request.suggestions.active.includes(:note)
+
+ suggestions.find_in_batches(batch_size: 100) do |group|
+ outdatable_suggestion_ids = group.select do |suggestion|
+ suggestion.outdated?(cached: false)
+ end.map(&:id)
+
+ Suggestion.where(id: outdatable_suggestion_ids).update_all(outdated: true)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/changelogs/unreleased/issue_58547.yml b/changelogs/unreleased/issue_58547.yml
new file mode 100644
index 00000000000..553c752e72d
--- /dev/null
+++ b/changelogs/unreleased/issue_58547.yml
@@ -0,0 +1,5 @@
+---
+title: Add API access check to Graphql
+merge_request: 26570
+author:
+type: other
diff --git a/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml b/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml
new file mode 100644
index 00000000000..01bd7ede270
--- /dev/null
+++ b/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml
@@ -0,0 +1,5 @@
+---
+title: Implements the creation strategy for multi-line suggestions
+merge_request: 26057
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-add-gitaly-duration-logs.yml b/changelogs/unreleased/sh-add-gitaly-duration-logs.yml
new file mode 100644
index 00000000000..eea50384278
--- /dev/null
+++ b/changelogs/unreleased/sh-add-gitaly-duration-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Log Gitaly RPC duration to api_json.log and production_json.log
+merge_request: 26652
+author:
+type: other
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 164954d1293..5e790a9eccb 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -28,7 +28,12 @@ unless Sidekiq.server?
}
gitaly_calls = Gitlab::GitalyClient.get_request_count
- payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0
+
+ if gitaly_calls > 0
+ payload[:gitaly_calls] = gitaly_calls
+ payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms
+ end
+
payload[:response] = event.payload[:response] if event.payload[:response]
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
diff --git a/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb
new file mode 100644
index 00000000000..856dfc89fa3
--- /dev/null
+++ b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddMultiLineAttributesToSuggestion < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :suggestions, :lines_above, :integer, default: 0, allow_null: false
+ add_column_with_default :suggestions, :lines_below, :integer, default: 0, allow_null: false
+ add_column_with_default :suggestions, :outdated, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_columns :suggestions, :outdated, :lines_above, :lines_below
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0c997f3b8a2..06f9f5da10d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2039,6 +2039,9 @@ ActiveRecord::Schema.define(version: 20190322132835) do
t.string "commit_id"
t.text "from_content", null: false
t.text "to_content", null: false
+ t.integer "lines_above", default: 0, null: false
+ t.integer "lines_below", default: 0, null: false
+ t.boolean "outdated", default: false, null: false
t.index ["note_id", "relative_order"], name: "index_suggestions_on_note_id_and_relative_order", unique: true, using: :btree
end
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index b1aaa3bca13..f406163aea0 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -71,9 +71,8 @@ bug](https://bugzilla.redhat.com/show_bug.cgi?id=1552203) that may be fixed in
[more recent kernels with this
commit](https://github.com/torvalds/linux/commit/95da1b3a5aded124dd1bda1e3cdb876184813140).
-Users encountering a similar issue may be advised to disable the NFS server
-delegation feature, which is an optimization to reduce the number of network
-round-trips needed to read or write files. To disable NFS server delegations
+GitLab recommends all NFS users disable the NFS server
+delegation feature. To disable NFS server delegations
on an Linux NFS server, do the following:
1. On the NFS server, run:
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 36dee75bd44..3d40cda491a 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -23,16 +23,19 @@ requests from the API are logged to a separate file in `api_json.log`.
Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example:
```json
-{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"queue_duration": 112.47}
+{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"gitaly_duration":7.41,"queue_duration": 112.47}
```
-In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data:
+In this example, you can see this was a GET request for a specific
+issue. Notice each line also contains performance data. All times are in
+milliseconds:
-1. `duration`: total time in milliseconds taken to retrieve the request
-1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse
+1. `duration`: total time taken to retrieve the request
+1. `queue_duration`: total time that the request was queued inside GitLab Workhorse
1. `view`: total time taken inside the Rails views
1. `db`: total time to retrieve data from the database
1. `gitaly_calls`: total number of calls made to Gitaly
+1. `gitaly_duration`: total time taken by Gitaly calls
User clone/fetch activity using http transport appears in this log as `action: git_upload_pack`.
@@ -85,7 +88,7 @@ Introduced in GitLab 10.0, this file lives in
It helps you see requests made directly to the API. For example:
```json
-{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30}
+{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30,"gitaly_duration":5.36}
```
This entry above shows an access to an internal endpoint to check whether an
diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md
index e88d536282a..188989bc94e 100644
--- a/doc/api/suggestions.md
+++ b/doc/api/suggestions.md
@@ -24,8 +24,6 @@ Example response:
```json
{
"id": 36,
- "from_original_line": 10,
- "to_original_line": 10,
"from_line": 10,
"to_line": 10,
"appliable": false,
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index b74bd81d467..8b3ae19b544 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -18,9 +18,10 @@ the second factor of authentication. Once enabled, in addition to supplying your
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
-The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend
-that you set up both methods of two-factor authentication, so you can still access your account
-from other browsers.
+The U2F workflow is [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera, and Firefox.
+
+We recommend that you set up 2FA with both a [one-time password authenticator](#enable-2fa-via-one-time-password-authenticator) and a [U2F device](#enable-2fa-via-u2f-device), so you can still access your account
+if you lose your U2F device.
## Enabling 2FA
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 7fde27b3d22..ed883e6dcdc 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -389,14 +389,20 @@ to obtain the endpoint. You can use either
In order to publish your web application, you first need to find the endpoint which will be either an IP
address or a hostname associated with your load balancer.
-### Let GitLab fetch the external endpoint
+### Automatically determining the external endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
-If you [installed Ingress or Knative](#installing-applications),
-you should see the Ingress Endpoint on this same page within a few minutes.
-If you don't see this, GitLab might not be able to determine the external endpoint of
-your ingress application in which case you should manually determine it.
+After you install [Ingress or Knative](#installing-applications), Gitlab attempts to determine the external endpoint
+and it should be available within a few minutes. If the endpoint doesn't appear
+and your cluster runs on Google Kubernetes Engine:
+
+1. Check your [Kubernetes cluster on Google Kubernetes Engine](https://console.cloud.google.com/kubernetes) to ensure there are no errors on its nodes.
+1. Ensure you have enough [Quotas](https://console.cloud.google.com/iam-admin/quotas) on Google Kubernetes Engine. For more information, see [Resource Quotas](https://cloud.google.com/compute/quotas).
+1. Check [Google Cloud's Status](https://status.cloud.google.com/) to ensure they are not having any disruptions.
+
+If GitLab is still unable to determine the endpoint of your Ingress or Knative application, you can
+manually determine it by following the steps below.
### Manually determining the external endpoint
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index d324e0de0cc..8e1603f9ec9 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -48,7 +48,7 @@ Navigate to the webhooks page by going to your project's
## Use-cases
- You can set up a webhook in GitLab to send a notification to
- [Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example
+ [Slack](https://api.slack.com/incoming-webhooks) every time a job fails.
- You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
every time an issue is created for a specific project or group within GitLab
- You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/).
@@ -1004,7 +1004,7 @@ X-Gitlab-Event: Pipeline Hook
"email": "user@gitlab.com"
}
},
- "builds":[
+ "jobs":[
{
"id": 380,
"stage": "deploy",
@@ -1129,34 +1129,34 @@ X-Gitlab-Event: Pipeline Hook
}
```
-### Build events
+### Job events
-Triggered on status change of a Build.
+Triggered on status change of a job.
**Request Header**:
```
-X-Gitlab-Event: Build Hook
+X-Gitlab-Event: Job Hook
```
**Request Body**:
```json
{
- "object_kind": "build",
+ "object_kind": "job",
"ref": "gitlab-script-trigger",
"tag": false,
"before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
"sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
- "build_id": 1977,
- "build_name": "test",
- "build_stage": "test",
- "build_status": "created",
- "build_started_at": null,
- "build_finished_at": null,
- "build_duration": null,
- "build_allow_failure": false,
- "build_failure_reason": "script_failure",
+ "job_id": 1977,
+ "job_name": "test",
+ "job_stage": "test",
+ "job_status": "created",
+ "job_started_at": null,
+ "job_finished_at": null,
+ "job_duration": null,
+ "job_allow_failure": false,
+ "job_failure_reason": "script_failure",
"project_id": 380,
"project_name": "gitlab-org/gitlab-test",
"user": {
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 6fd267ff2ed..0871ea8d21e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1558,8 +1558,6 @@ module API
class Suggestion < Grape::Entity
expose :id
- expose :from_original_line
- expose :to_original_line
expose :from_line
expose :to_line
expose :appliable?, as: :appliable
diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb
index 09f36635020..0d7f751bfc1 100644
--- a/lib/banzai/suggestions_parser.rb
+++ b/lib/banzai/suggestions_parser.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# TODO: Delete when https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26107
+# exchange this parser by `Gitlab::Diff::SuggestionsParser`.
module Banzai
module SuggestionsParser
# Returns the content of each suggestion code block.
diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb
new file mode 100644
index 00000000000..027c7a31bcf
--- /dev/null
+++ b/lib/gitlab/diff/suggestion.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class Suggestion
+ include Suggestible
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :diff_file, :lines_above, :lines_below,
+ :target_line
+
+ def initialize(text, line:, above:, below:, diff_file:)
+ @text = text
+ @target_line = line
+ @lines_above = above.to_i
+ @lines_below = below.to_i
+ @diff_file = diff_file
+ end
+
+ def to_hash
+ {
+ from_content: from_content,
+ to_content: to_content,
+ lines_above: @lines_above,
+ lines_below: @lines_below
+ }
+ end
+
+ def from_content
+ strong_memoize(:from_content) do
+ fetch_from_content
+ end
+ end
+
+ def to_content
+ # The parsed suggestion doesn't have information about the correct
+ # ending characters (we may have a line break, or not), so we take
+ # this information from the last line being changed (last
+ # characters).
+ endline_chars = line_break_chars(from_content.lines.last)
+ "#{@text}#{endline_chars}"
+ end
+
+ private
+
+ def line_break_chars(line)
+ match = /\r\n|\r|\n/.match(line)
+ match[0] if match
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
index 043bd9a4bcb..c8c03d5d001 100644
--- a/lib/gitlab/diff/suggestions_parser.rb
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -5,6 +5,47 @@ module Gitlab
class SuggestionsParser
# Matches for instance "-1", "+1" or "-1+2".
SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+
+ class << self
+ # Returns an array of Gitlab::Diff::Suggestion which represents each
+ # suggestion in the given text.
+ #
+ def parse(text, position:, project:)
+ return [] unless position.complete?
+
+ html = Banzai.render(text, project: nil, no_original_data: true)
+ doc = Nokogiri::HTML(html)
+ suggestion_nodes = doc.search('pre.suggestion')
+
+ return [] if suggestion_nodes.empty?
+
+ diff_file = position.diff_file(project.repository)
+
+ suggestion_nodes.map do |node|
+ lang_param = node['data-lang-params']
+
+ lines_above, lines_below = nil
+
+ if lang_param && suggestion_params = fetch_suggestion_params(lang_param)
+ lines_above, lines_below =
+ suggestion_params[:above],
+ suggestion_params[:below]
+ end
+
+ Gitlab::Diff::Suggestion.new(node.text,
+ line: position.new_line,
+ above: lines_above.to_i,
+ below: lines_below.to_i,
+ diff_file: diff_file)
+ end
+ end
+
+ private
+
+ def fetch_suggestion_params(lang_param)
+ lang_param.match(SUGGESTION_CONTEXT)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index e3b9a7a1a89..49cff7517e9 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -33,12 +33,6 @@ module Gitlab
MUTEX = Mutex.new
- class << self
- attr_accessor :query_time
- end
-
- self.query_time = 0
-
define_histogram :gitaly_controller_action_duration_seconds do
docstring "Gitaly endpoint histogram by controller and action combination"
base_labels Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil)
@@ -174,6 +168,18 @@ module Gitlab
add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc)
end
+ def self.query_time
+ SafeRequestStore[:gitaly_query_time] ||= 0
+ end
+
+ def self.query_time=(duration)
+ SafeRequestStore[:gitaly_query_time] = duration
+ end
+
+ def self.query_time_ms
+ (self.query_time * 1000).round(2)
+ end
+
def self.current_transaction_labels
Gitlab::Metrics::Transaction.current&.labels || {}
end
diff --git a/lib/gitlab/grape_logging/loggers/perf_logger.rb b/lib/gitlab/grape_logging/loggers/perf_logger.rb
index e3b9c59bd6e..18ea3a8d2f3 100644
--- a/lib/gitlab/grape_logging/loggers/perf_logger.rb
+++ b/lib/gitlab/grape_logging/loggers/perf_logger.rb
@@ -6,7 +6,10 @@ module Gitlab
module Loggers
class PerfLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _)
- { gitaly_calls: Gitlab::GitalyClient.get_request_count }
+ {
+ gitaly_calls: Gitlab::GitalyClient.get_request_count,
+ gitaly_duration: Gitlab::GitalyClient.query_time_ms
+ }
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index a0aab9fcbaf..89667976217 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -33,6 +33,7 @@ project_tree:
- :user
- merge_requests:
- :metrics
+ - :suggestions
- notes:
- :author
- events:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1a0224a44e6..c7755c5c7e2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -144,6 +144,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
msgstr[0] ""
@@ -372,6 +381,9 @@ msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Account"
msgstr ""
@@ -609,9 +621,18 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
@@ -954,6 +975,9 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -2447,6 +2471,15 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
@@ -3370,6 +3403,9 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
@@ -3541,6 +3577,9 @@ msgstr ""
msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
msgid "Failed to remove issue from board, please try again."
msgstr ""
@@ -3786,6 +3825,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -3966,6 +4008,9 @@ msgstr ""
msgid "GroupSettings|Learn more about badges."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
@@ -4130,6 +4175,9 @@ msgstr ""
msgid "Hook was successfully created."
msgstr ""
+msgid "Hook was successfully updated."
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
@@ -4409,12 +4457,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
msgid "Invalid input, please avoid emojis"
msgstr ""
msgid "Invalid pin code"
msgstr ""
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -4478,6 +4535,9 @@ msgstr ""
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
@@ -4986,6 +5046,9 @@ msgstr ""
msgid "Merged"
msgstr ""
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr ""
@@ -5079,6 +5142,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -5384,6 +5450,9 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
@@ -5489,6 +5558,9 @@ msgstr ""
msgid "November"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -5590,6 +5662,12 @@ msgstr ""
msgid "Owner"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -5752,6 +5830,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines page"
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -5896,6 +5980,9 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please select a group."
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
@@ -6217,6 +6304,12 @@ msgstr ""
msgid "Project \"%{name}\" is no longer available. Select another project to continue."
msgstr ""
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -6696,6 +6789,12 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -7019,6 +7118,9 @@ msgstr ""
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
@@ -7603,6 +7705,9 @@ msgstr ""
msgid "Successfully removed email."
msgstr ""
+msgid "Successfully scheduled a pipeline to run. Go to the %{link_to_pipelines} for details."
+msgstr ""
+
msgid "Successfully unblocked"
msgstr ""
@@ -7756,6 +7861,9 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr ""
@@ -7774,9 +7882,24 @@ msgstr ""
msgid "The collection of events added to the data gathered for that stage."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
@@ -7789,6 +7912,12 @@ msgstr ""
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+msgstr ""
+
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr ""
@@ -7798,6 +7927,15 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
msgid "The name %{entryName} is already taken in this directory."
msgstr ""
@@ -7822,6 +7960,15 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -7852,12 +7999,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
msgid "The user is being deleted."
msgstr ""
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -7921,6 +8074,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -8071,6 +8227,9 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
@@ -8107,6 +8266,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -8393,6 +8555,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -8450,6 +8615,9 @@ msgstr ""
msgid "Trending"
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -8459,6 +8627,15 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -8483,9 +8660,15 @@ msgstr ""
msgid "Type"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -8561,6 +8744,9 @@ msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
@@ -8657,6 +8843,12 @@ msgstr ""
msgid "User was successfully created."
msgstr ""
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
msgid "User was successfully updated."
msgstr ""
@@ -8750,6 +8942,9 @@ msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
msgid "Value"
msgstr ""
@@ -8888,6 +9083,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -9107,6 +9305,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -9134,9 +9338,18 @@ msgstr ""
msgid "You cannot impersonate an internal user"
msgstr ""
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -9155,6 +9368,9 @@ msgstr ""
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -9170,6 +9386,15 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
@@ -9242,6 +9467,9 @@ msgstr ""
msgid "Your U2F device was registered!"
msgstr ""
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
msgstr ""
@@ -9263,6 +9491,9 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
@@ -9287,6 +9518,9 @@ msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
@@ -9366,6 +9600,9 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
+msgid "failed"
+msgstr ""
+
msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
@@ -9743,6 +9980,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/package.json b/package.json
index f0f819fdb74..cb063c9782c 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.3.1",
- "@gitlab/csslab": "^1.8.0",
+ "@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.54.0",
"@gitlab/ui": "^3.0.0",
"apollo-boost": "^0.3.1",
@@ -40,7 +40,7 @@
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.5",
- "bootstrap": "4.1.3",
+ "bootstrap": "4.3.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^2.0.1",
"chart.js": "2.7.2",
@@ -91,7 +91,7 @@
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
"pikaday": "^1.6.1",
- "popper.js": "^1.14.3",
+ "popper.js": "^1.14.7",
"prismjs": "^1.6.0",
"prosemirror-markdown": "^1.3.0",
"prosemirror-model": "^1.6.4",
@@ -118,14 +118,14 @@
"underscore": "^1.9.0",
"url-loader": "^1.1.2",
"visibilityjs": "^1.2.4",
- "vue": "^2.5.21",
+ "vue": "^2.6.10",
"vue-apollo": "^3.0.0-beta.28",
- "vue-loader": "^15.4.2",
+ "vue-loader": "^15.7.0",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
- "vue-template-compiler": "^2.5.21",
- "vue-virtual-scroll-list": "^1.2.5",
- "vuex": "^3.0.1",
+ "vue-template-compiler": "^2.6.10",
+ "vue-virtual-scroll-list": "^1.3.1",
+ "vuex": "^3.1.0",
"webpack": "^4.29.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.2.1",
@@ -152,8 +152,8 @@
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
"eslint-plugin-jest": "^22.3.0",
- "gettext-extractor": "^3.3.2",
- "gettext-extractor-vue": "^4.0.1",
+ "gettext-extractor": "^3.4.3",
+ "gettext-extractor-vue": "^4.0.2",
"graphql-tag": "^2.10.0",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
diff --git a/qa/README.md b/qa/README.md
index 735868e7640..7d66f7d5abc 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -55,16 +55,19 @@ You can also supply specific tests to run as another parameter. For example, to
run the repository-related specs, you can execute:
```
-bin/qa Test::Instance::All http://localhost qa/specs/features/repository/
+bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/repository
```
Since the arguments would be passed to `rspec`, you could use all `rspec`
options there. For example, passing `--backtrace` and also line number:
```
-bin/qa Test::Instance::All http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace
+bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace
```
+Note that the separator `--` is required; all subsequent options will be
+ignored by the QA framework and passed to `rspec`.
+
### Overriding the authenticated user
Unless told otherwise, the QA tests will run as the default `root` user seeded
@@ -117,7 +120,7 @@ tests that are expected to fail while a fix is in progress (similar to how
can be used).
```
-bin/qa Test::Instance::All http://localhost --tag quarantine
+bin/qa Test::Instance::All http://localhost -- --tag quarantine
```
If `quarantine` is used with other tags, tests will only be run if they have at
@@ -128,3 +131,25 @@ For example, suppose one test has `:smoke` and `:quarantine` metadata, and
another test has `:ldap` and `:quarantine` metadata. If the tests are run with
`--tag smoke --tag quarantine`, only the first test will run. The test with
`:ldap` will not run even though it also has `:quarantine`.
+
+### Running tests with a feature flag enabled
+
+Tests can be run with with a feature flag enabled by using the command-line
+option `--enable-feature FEATURE_FLAG`. For example, to enable the feature flag
+that enforces Gitaly request limits, you would use the command:
+
+```
+bin/qa Test::Instance::All http://localhost --enable-feature gitaly_enforce_requests_limits
+```
+
+This will instruct the QA framework to enable the `gitaly_enforce_requests_limits`
+feature flag ([via the API](https://docs.gitlab.com/ee/api/features.html)), run
+all the tests in the `Test::Instance::All` scenario, and then disable the
+feature flag again.
+
+Note: the QA framework doesn't currently allow you to easily toggle a feature
+flag during a single test, [as you can in unit tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs),
+but [that capability is planned](https://gitlab.com/gitlab-org/quality/team-tasks/issues/77).
+
+Note also that the `--` separator isn't used because `--enable-feature` is a QA
+framework option, not an `rspec` option. \ No newline at end of file
diff --git a/qa/qa.rb b/qa/qa.rb
index ec8aef31e48..672f4aa928a 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -17,6 +17,7 @@ module QA
autoload :Env, 'qa/runtime/env'
autoload :Address, 'qa/runtime/address'
autoload :Path, 'qa/runtime/path'
+ autoload :Feature, 'qa/runtime/feature'
autoload :Fixtures, 'qa/runtime/fixtures'
autoload :Logger, 'qa/runtime/logger'
@@ -89,6 +90,7 @@ module QA
autoload :Bootable, 'qa/scenario/bootable'
autoload :Actable, 'qa/scenario/actable'
autoload :Template, 'qa/scenario/template'
+ autoload :SharedAttributes, 'qa/scenario/shared_attributes'
##
# Test scenario entrypoints.
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 98eebac0880..de04467ff5b 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -8,9 +8,6 @@ module QA
module ApiFabricator
include Capybara::DSL
- HTTP_STATUS_OK = 200
- HTTP_STATUS_CREATED = 201
-
ResourceNotFoundError = Class.new(RuntimeError)
ResourceFabricationFailedError = Class.new(RuntimeError)
ResourceURLMissingError = Class.new(RuntimeError)
diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb
index ffad3974b02..af0537dc17c 100644
--- a/qa/qa/runtime/address.rb
+++ b/qa/qa/runtime/address.rb
@@ -15,6 +15,13 @@ module QA
@instance.to_s
end
end
+
+ def self.valid?(value)
+ uri = URI.parse(value)
+ uri.is_a?(URI::HTTP) && !uri.host.nil?
+ rescue URI::InvalidURIError
+ false
+ end
end
end
end
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
new file mode 100644
index 00000000000..1b4ae7adbbe
--- /dev/null
+++ b/qa/qa/runtime/feature.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module Feature
+ extend self
+ extend Support::Api
+
+ SetFeatureError = Class.new(RuntimeError)
+
+ def enable(key)
+ QA::Runtime::Logger.info("Enabling feature: #{key}")
+ set_feature(key, true)
+ end
+
+ def disable(key)
+ QA::Runtime::Logger.info("Disabling feature: #{key}")
+ set_feature(key, false)
+ end
+
+ private
+
+ def api_client
+ @api_client ||= Runtime::API::Client.new(:gitlab)
+ end
+
+ def set_feature(key, value)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = post(request.url, { value: value })
+ unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
+ raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/scenario/bootable.rb b/qa/qa/scenario/bootable.rb
index dd12ea6d492..038418be023 100644
--- a/qa/qa/scenario/bootable.rb
+++ b/qa/qa/scenario/bootable.rb
@@ -23,7 +23,7 @@ module QA
arguments.parse!(argv)
- self.perform(Runtime::Scenario.attributes, *arguments.default_argv)
+ self.perform(Runtime::Scenario.attributes, *argv)
end
private
@@ -33,7 +33,13 @@ module QA
end
def options
- @options ||= []
+ # Scenario options/attributes are global. There's only ever one
+ # scenario at a time, but they can be inherited and we want scenarios
+ # to share the attributes of their ancestors. For example, `Mattermost`
+ # inherits from `Test::Instance::All` but if this were an instance
+ # variable then `Mattermost` wouldn't have access to the attributes
+ # in `All`
+ @@options ||= [] # rubocop:disable Style/ClassVars
end
def has_attributes?
diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb
new file mode 100644
index 00000000000..40d5c6b1ff1
--- /dev/null
+++ b/qa/qa/scenario/shared_attributes.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module SharedAttributes
+ include Bootable
+
+ attribute :gitlab_address, '--address URL', 'Address of the instance to test'
+ attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
+ end
+ end
+end
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index cb1a1de6b9a..b8ea26e805e 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -18,19 +18,44 @@ module QA
end
end
- def perform(address, *rspec_options)
- Runtime::Scenario.define(:gitlab_address, address)
+ def perform(options, *args)
+ extract_address(:gitlab_address, options, args)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
+ Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
+
Specs::Runner.perform do |specs|
specs.tty = true
specs.tags = self.class.focus
- specs.options = rspec_options if rspec_options.any?
+ specs.options = args if args.any?
end
+ ensure
+ Runtime::Feature.disable(options[:enable_feature]) if options.key?(:enable_feature)
+ end
+
+ def extract_option(name, options, args)
+ option = if options.key?(name)
+ options[name]
+ else
+ args.shift
+ end
+
+ Runtime::Scenario.define(name, option)
+
+ option
+ end
+
+ # For backwards-compatibility, if the gitlab instance address is not
+ # specified as an option parsed by OptionParser, it can be specified as
+ # the first argument
+ def extract_address(name, options, args)
+ address = extract_option(name, options, args)
+
+ raise ::ArgumentError, "The address provided for `#{name}` is not valid: #{address}" unless Runtime::Address.valid?(address)
end
end
end
diff --git a/qa/qa/scenario/test/instance/all.rb b/qa/qa/scenario/test/instance/all.rb
index a07c26431bd..168ac4c09a1 100644
--- a/qa/qa/scenario/test/instance/all.rb
+++ b/qa/qa/scenario/test/instance/all.rb
@@ -8,6 +8,7 @@ module QA
module Instance
class All < Template
include Bootable
+ include SharedAttributes
end
end
end
diff --git a/qa/qa/scenario/test/instance/smoke.rb b/qa/qa/scenario/test/instance/smoke.rb
index a7d2cb27f27..43f0623483e 100644
--- a/qa/qa/scenario/test/instance/smoke.rb
+++ b/qa/qa/scenario/test/instance/smoke.rb
@@ -8,6 +8,7 @@ module QA
#
class Smoke < Template
include Bootable
+ include SharedAttributes
tags :smoke
end
diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb
index ece6fba75c9..f5072ee227c 100644
--- a/qa/qa/scenario/test/integration/mattermost.rb
+++ b/qa/qa/scenario/test/integration/mattermost.rb
@@ -9,10 +9,13 @@ module QA
class Mattermost < Test::Instance::All
tags :mattermost
- def perform(address, mattermost, *rspec_options)
- Runtime::Scenario.define(:mattermost_address, mattermost)
+ attribute :mattermost_address, '--mattermost-address URL', 'Address of the Mattermost server'
- super(address, *rspec_options)
+ def perform(options, *args)
+ extract_address(:gitlab_address, options, args)
+ extract_address(:mattermost_address, options, args)
+
+ super(options, *args)
end
end
end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 229bfb44fa5..31cff5a241c 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -1,6 +1,9 @@
module QA
module Support
module Api
+ HTTP_STATUS_OK = 200
+ HTTP_STATUS_CREATED = 201
+
def post(url, payload)
RestClient::Request.execute(
method: :post,
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
new file mode 100644
index 00000000000..192299b7857
--- /dev/null
+++ b/qa/spec/runtime/feature_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+describe QA::Runtime::Feature do
+ let(:api_client) { double('QA::Runtime::API::Client') }
+ let(:request) { Struct.new(:url).new('http://api') }
+ let(:response) { Struct.new(:code).new(201) }
+
+ before do
+ allow(described_class).to receive(:api_client).and_return(api_client)
+ end
+
+ describe '.enable' do
+ it 'enables a feature flag' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features/a-flag")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:post)
+ .with(request.url, { value: true })
+ .and_return(response)
+
+ subject.enable('a-flag')
+ end
+ end
+
+ describe '.disable' do
+ it 'disables a feature flag' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features/a-flag")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:post)
+ .with(request.url, { value: false })
+ .and_return(response)
+
+ subject.disable('a-flag')
+ end
+ end
+end
diff --git a/qa/spec/runtime/scenario_spec.rb b/qa/spec/runtime/scenario_spec.rb
index 7009192bcc0..70fc71ffc02 100644
--- a/qa/spec/runtime/scenario_spec.rb
+++ b/qa/spec/runtime/scenario_spec.rb
@@ -13,6 +13,14 @@ describe QA::Runtime::Scenario do
.to eq(my_attribute: 'some-value', another_attribute: 'another-value')
end
+ it 'replaces an existing attribute' do
+ subject.define(:my_attribute, 'some-value')
+ subject.define(:my_attribute, 'another-value')
+
+ expect(subject.my_attribute).to eq 'another-value'
+ expect(subject.attributes).to eq(my_attribute: 'another-value')
+ end
+
it 'raises error when attribute is not known' do
expect { subject.invalid_accessor }
.to raise_error ArgumentError, /invalid_accessor/
diff --git a/qa/spec/scenario/bootable_spec.rb b/qa/spec/scenario/bootable_spec.rb
index 273aac7677e..bd89b21f7fb 100644
--- a/qa/spec/scenario/bootable_spec.rb
+++ b/qa/spec/scenario/bootable_spec.rb
@@ -4,14 +4,21 @@ describe QA::Scenario::Bootable do
.include(described_class)
end
+ before do
+ allow(subject).to receive(:options).and_return([])
+ allow(QA::Runtime::Scenario).to receive(:attributes).and_return({})
+ end
+
it 'makes it possible to define the scenario attribute' do
subject.class_eval do
attribute :something, '--something SOMETHING', 'Some attribute'
attribute :another, '--another ANOTHER', 'Some other attribute'
end
+ # If we run just this test from the command line it fails unless
+ # we include the command line args that we use to select this test.
expect(subject).to receive(:perform)
- .with(something: 'test', another: 'other')
+ .with({ something: 'test', another: 'other' })
subject.launch!(%w[--another other --something test])
end
diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb
new file mode 100644
index 00000000000..f97fc22daf9
--- /dev/null
+++ b/qa/spec/scenario/template_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+describe QA::Scenario::Template do
+ let(:feature) { spy('Runtime::Feature') }
+ let(:release) { spy('Runtime::Release') }
+
+ before do
+ stub_const('QA::Runtime::Release', release)
+ stub_const('QA::Runtime::Feature', feature)
+ allow(QA::Specs::Runner).to receive(:perform)
+ allow(QA::Runtime::Address).to receive(:valid?).and_return(true)
+ end
+
+ it 'allows a feature to be enabled' do
+ subject.perform({ enable_feature: 'a-feature' })
+
+ expect(feature).to have_received(:enable).with('a-feature')
+ end
+
+ it 'ensures an enabled feature is disabled afterwards' do
+ allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test')
+
+ expect { subject.perform({ enable_feature: 'a-feature' }) }.to raise_error('failed test')
+
+ expect(feature).to have_received(:enable).with('a-feature')
+ expect(feature).to have_received(:disable).with('a-feature')
+ end
+end
diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb
index c2aeb1ded1d..6112ba7c694 100644
--- a/qa/spec/scenario/test/integration/github_spec.rb
+++ b/qa/spec/scenario/test/integration/github_spec.rb
@@ -12,7 +12,7 @@ describe QA::Scenario::Test::Integration::Github do
let(:tags) { [:github] }
it 'requires a GitHub access token' do
- subject.perform('gitlab_address')
+ subject.perform(args)
expect(env).to have_received(:require_github_access_token!)
end
diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb
index 59caf2ba2cd..4e75e72f4d2 100644
--- a/qa/spec/scenario/test/integration/mattermost_spec.rb
+++ b/qa/spec/scenario/test/integration/mattermost_spec.rb
@@ -4,14 +4,21 @@ describe QA::Scenario::Test::Integration::Mattermost do
context '#perform' do
it_behaves_like 'a QA scenario class' do
let(:args) { %w[gitlab_address mattermost_address] }
+ let(:args) do
+ {
+ gitlab_address: 'http://gitlab_address',
+ mattermost_address: 'http://mattermost_address'
+ }
+ end
+ let(:named_options) { %w[--address http://gitlab_address --mattermost-address http://mattermost_address] }
let(:tags) { [:mattermost] }
let(:options) { ['path1']}
it 'requires a GitHub access token' do
- subject.perform(*args)
+ subject.perform(args)
expect(attributes).to have_received(:define)
- .with(:mattermost_address, 'mattermost_address')
+ .with(:mattermost_address, 'http://mattermost_address')
end
end
end
diff --git a/qa/spec/shared_examples/scenario_shared_examples.rb b/qa/spec/shared_examples/scenario_shared_examples.rb
index 5fd55d7d96b..697e6cb39c8 100644
--- a/qa/spec/shared_examples/scenario_shared_examples.rb
+++ b/qa/spec/shared_examples/scenario_shared_examples.rb
@@ -2,19 +2,23 @@
shared_examples 'a QA scenario class' do
let(:attributes) { spy('Runtime::Scenario') }
- let(:release) { spy('Runtime::Release') }
let(:runner) { spy('Specs::Runner') }
+ let(:release) { spy('Runtime::Release') }
+ let(:feature) { spy('Runtime::Feature') }
- let(:args) { ['gitlab_address'] }
+ let(:args) { { gitlab_address: 'http://gitlab_address' } }
+ let(:named_options) { %w[--address http://gitlab_address] }
let(:tags) { [] }
let(:options) { %w[path1 path2] }
before do
+ stub_const('QA::Specs::Runner', runner)
stub_const('QA::Runtime::Release', release)
stub_const('QA::Runtime::Scenario', attributes)
- stub_const('QA::Specs::Runner', runner)
+ stub_const('QA::Runtime::Feature', feature)
allow(runner).to receive(:perform).and_yield(runner)
+ allow(QA::Runtime::Address).to receive(:valid?).and_return(true)
end
it 'responds to perform' do
@@ -22,28 +26,48 @@ shared_examples 'a QA scenario class' do
end
it 'sets an address of the subject' do
- subject.perform(*args)
+ subject.perform(args)
- expect(attributes).to have_received(:define).with(:gitlab_address, 'gitlab_address')
+ expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once)
end
it 'performs before hooks' do
- subject.perform(*args)
+ subject.perform(args)
expect(release).to have_received(:perform_before_hooks)
end
it 'sets tags on runner' do
- subject.perform(*args)
+ subject.perform(args)
expect(runner).to have_received(:tags=).with(tags)
end
context 'specifying RSpec options' do
it 'sets options on runner' do
- subject.perform(*args, *options)
+ subject.perform(args, *options)
expect(runner).to have_received(:options=).with(options)
end
end
+
+ context 'with named command-line options' do
+ it 'converts options to attributes' do
+ described_class.launch!(named_options)
+
+ args do |k, v|
+ expect(attributes).to have_received(:define).with(k, v)
+ end
+ end
+
+ it 'raises an error if the option is invalid' do
+ expect { described_class.launch!(['--foo']) }.to raise_error(OptionParser::InvalidOption)
+ end
+
+ it 'passes on options after --' do
+ expect(described_class).to receive(:perform).with(attributes, *%w[--tag quarantine])
+
+ described_class.launch!(named_options.push(*%w[-- --tag quarantine]))
+ end
+ end
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
new file mode 100644
index 00000000000..c19a752b07b
--- /dev/null
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GraphqlController do
+ before do
+ stub_feature_flags(graphql: true)
+ end
+
+ describe 'POST #execute' do
+ context 'when user is logged in' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'returns 200 when user can access API' do
+ post :execute
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'returns access denied template when user cannot access API' do
+ # User cannot access API in a couple of cases
+ # * When user is internal(like ghost users)
+ # * When user is blocked
+ expect(Ability).to receive(:allowed?).with(user, :access_api, :global).and_return(false)
+
+ post :execute
+
+ expect(response.status).to eq(403)
+ expect(response).to render_template('errors/access_denied')
+ end
+ end
+
+ context 'when user is not logged in' do
+ it 'returns 200' do
+ post :execute
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+end
diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb
index 307523cc061..b1427e0211f 100644
--- a/spec/factories/suggestions.rb
+++ b/spec/factories/suggestions.rb
@@ -16,5 +16,11 @@ FactoryBot.define do
applied true
commit_id { RepoHelpers.sample_commit.id }
end
+
+ trait :content_from_repo do
+ after(:build) do |suggestion, evaluator|
+ suggestion.from_content = suggestion.fetch_from_content
+ end
+ end
end
end
diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js
index 430e8e2baa3..80d6c7fd564 100644
--- a/spec/javascripts/ide/components/error_message_spec.js
+++ b/spec/javascripts/ide/components/error_message_spec.js
@@ -84,7 +84,7 @@ describe('IDE error message component', () => {
expect(vm.isLoading).toBe(true);
- vm.$nextTick(() => {
+ setTimeout(() => {
expect(vm.isLoading).toBe(false);
done();
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 7ddc734ff56..1e5b55af4ba 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -121,68 +121,48 @@ describe('IDE store file actions', () => {
store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
});
- it('calls scrollToTab', done => {
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(scrollToTabSpy).toHaveBeenCalled();
-
- done();
- })
- .catch(done.fail);
- });
+ it('calls scrollToTab', () => {
+ const dispatch = jasmine.createSpy();
- it('sets the file active', done => {
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(localFile.active).toBeTruthy();
+ actions.setFileActive(
+ { commit() {}, state: store.state, getters: store.getters, dispatch },
+ localFile.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(dispatch).toHaveBeenCalledWith('scrollToTab');
});
- it('returns early if file is already active', done => {
- localFile.active = true;
+ it('commits SET_FILE_ACTIVE', () => {
+ const commit = jasmine.createSpy();
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(scrollToTabSpy).not.toHaveBeenCalled();
+ actions.setFileActive(
+ { commit, state: store.state, getters: store.getters, dispatch() {} },
+ localFile.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', {
+ path: localFile.path,
+ active: true,
+ });
});
- it('sets current active file to not active', done => {
+ it('sets current active file to not active', () => {
const f = file('newActive');
store.state.entries[f.path] = f;
localFile.active = true;
store.state.openFiles.push(localFile);
- store
- .dispatch('setFileActive', f.path)
- .then(() => {
- expect(localFile.active).toBeFalsy();
+ const commit = jasmine.createSpy();
- done();
- })
- .catch(done.fail);
- });
-
- it('resets location.hash for line highlighting', done => {
- window.location.hash = 'test';
-
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(window.location.hash).not.toBe('test');
+ actions.setFileActive(
+ { commit, state: store.state, getters: store.getters, dispatch() {} },
+ f.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', {
+ path: localFile.path,
+ active: false,
+ });
});
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index d716ece3766..ef876dc2941 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -192,9 +192,9 @@ describe('note_app', () => {
expect(service.updateNote).toHaveBeenCalled();
// Wait for the requests to finish before destroying
- Vue.nextTick()
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ done();
+ });
});
});
@@ -227,9 +227,9 @@ describe('note_app', () => {
expect(service.updateNote).toHaveBeenCalled();
// Wait for the requests to finish before destroying
- Vue.nextTick()
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index 3d2232ff239..95717d659b8 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -55,13 +55,16 @@ describe('pipeline graph action component', () => {
component.$el.click();
- component
- .$nextTick()
- .then(() => {
- expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
- })
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ component
+ .$nextTick()
+ .then(() => {
+ expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
+ })
+ .catch(done.fail);
+
+ done();
+ }, 0);
});
});
});
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 3c8b8032de8..19ae7860333 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -120,13 +120,15 @@ describe('Pipelines stage component', () => {
setTimeout(() => {
component.$el.querySelector('.js-ci-action').click();
- component
- .$nextTick()
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
- })
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ component
+ .$nextTick()
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ })
+ .then(done)
+ .catch(done.fail);
+ }, 0);
}, 0);
});
});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
index 030662b4d90..1eb7cb4bd5b 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
@@ -53,36 +53,32 @@ describe('GkeProjectIdDropdown', () => {
});
it('returns default toggle text', done =>
- vm
- .$nextTick()
- .then(() => {
- vm.setItem(emptyProjectMock);
+ setTimeout(() => {
+ vm.setItem(emptyProjectMock);
- expect(vm.toggleText).toBe(LABELS.DEFAULT);
- done();
- })
- .catch(done.fail));
+ expect(vm.toggleText).toBe(LABELS.DEFAULT);
+
+ done();
+ }));
it('returns project name if project selected', done =>
- vm
- .$nextTick()
- .then(() => {
- expect(vm.toggleText).toBe(selectedProjectMock.name);
- done();
- })
- .catch(done.fail));
+ setTimeout(() => {
+ vm.isLoading = false;
+
+ expect(vm.toggleText).toBe(selectedProjectMock.name);
+
+ done();
+ }));
it('returns empty toggle text', done =>
- vm
- .$nextTick()
- .then(() => {
- vm.$store.commit(SET_PROJECTS, null);
- vm.setItem(emptyProjectMock);
+ setTimeout(() => {
+ vm.$store.commit(SET_PROJECTS, null);
+ vm.setItem(emptyProjectMock);
- expect(vm.toggleText).toBe(LABELS.EMPTY);
- done();
- })
- .catch(done.fail));
+ expect(vm.toggleText).toBe(LABELS.EMPTY);
+
+ done();
+ }));
});
describe('selectItem', () => {
diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb
new file mode 100644
index 00000000000..71fd25df698
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestion_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::Suggestion do
+ shared_examples 'correct suggestion raw content' do
+ it 'returns correct raw data' do
+ expect(suggestion.to_hash).to include(from_content: expected_lines.join,
+ to_content: "#{text}\n",
+ lines_above: above,
+ lines_below: below)
+ end
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+ let(:diff_file) do
+ position.diff_file(project.repository)
+ end
+ let(:text) { "# parsed suggestion content\n# with comments" }
+
+ def blob_lines_data(from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line)
+ end
+
+ def blob_data
+ blob = diff_file.new_blob
+ blob.load_all_data!
+ blob.data
+ end
+
+ let(:suggestion) do
+ described_class.new(text, line: line, above: above, below: below, diff_file: diff_file)
+ end
+
+ describe '#to_hash' do
+ context 'when changing content surpasses the top limit' do
+ let(:line) { 4 }
+ let(:above) { 5 }
+ let(:below) { 2 }
+ let(:expected_above) { line - 1 }
+ let(:expected_below) { below }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when changing content surpasses the amount of lines in the blob (bottom)' do
+ let(:line) { 5 }
+ let(:above) { 1 }
+ let(:below) { blob_data.lines.size + 10 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when lines are within blob lines boundary' do
+ let(:line) { 5 }
+ let(:above) { 2 }
+ let(:below) { 3 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when no extra lines (single-line suggestion)' do
+ let(:line) { 5 }
+ let(:above) { 0 }
+ let(:below) { 0 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
new file mode 100644
index 00000000000..1119ea04995
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::SuggestionsParser do
+ describe '.parse' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ let(:diff_file) do
+ position.diff_file(project.repository)
+ end
+
+ subject do
+ described_class.parse(markdown, project: merge_request.project,
+ position: position)
+ end
+
+ def blob_lines_data(from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ context 'single-line suggestions' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ foo
+ bar
+ ```
+
+ ```
+ nothing
+ ```
+
+ ```suggestion
+ xpto
+ baz
+ ```
+
+ ```thing
+ this is not a suggestion, it's a thing
+ ```
+ MARKDOWN
+ end
+
+ it 'returns a list of Gitlab::Diff::Suggestion' do
+ expect(subject).to all(be_a(Gitlab::Diff::Suggestion))
+ expect(subject.size).to eq(2)
+ end
+
+ it 'parsed suggestion has correct data' do
+ from_line, to_line = position.new_line, position.new_line
+
+ expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " foo\n bar\n",
+ lines_above: 0,
+ lines_below: 0)
+
+ expect(subject.second.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " xpto\n baz\n",
+ lines_above: 0,
+ lines_below: 0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 5299ab297f6..e418516569a 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -101,6 +101,7 @@ merge_requests:
- latest_merge_request_diff
- merge_request_pipelines
- merge_request_assignees
+- suggestions
merge_request_diff:
- merge_request
- merge_request_diff_commits
@@ -352,3 +353,5 @@ resource_label_events:
- label
error_tracking_setting:
- project
+suggestions:
+- note
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ee96e5c4d42..496567b0036 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -608,3 +608,14 @@ ErrorTracking::ProjectErrorTrackingSetting:
- project_id
- project_name
- organization_name
+Suggestion:
+- id
+- note_id
+- relative_order
+- applied
+- commit_id
+- from_content
+- to_content
+- outdated
+- lines_above
+- lines_below
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
index 3c2842e5725..5b07e598b8d 100644
--- a/spec/requests/api/suggestions_spec.rb
+++ b/spec/requests/api/suggestions_spec.rb
@@ -42,8 +42,7 @@ describe API::Suggestions do
expect(response).to have_gitlab_http_status(200)
expect(json_response)
- .to include('id', 'from_original_line', 'to_original_line',
- 'from_line', 'to_line', 'appliable', 'applied',
+ .to include('id', 'from_line', 'to_line', 'appliable', 'applied',
'from_content', 'to_content')
end
end
diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb
index 047571f161c..d38fc2b132b 100644
--- a/spec/serializers/suggestion_entity_spec.rb
+++ b/spec/serializers/suggestion_entity_spec.rb
@@ -13,8 +13,8 @@ describe SuggestionEntity do
subject { entity.as_json }
it 'exposes correct attributes' do
- expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line,
- :to_line, :appliable, :applied, :from_content, :to_content)
+ expect(subject).to include(:id, :from_line, :to_line, :appliable,
+ :applied, :from_content, :to_content)
end
it 'exposes current user abilities' do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 43ceb1dcbee..6c8ff163692 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -97,6 +97,15 @@ describe MergeRequests::RefreshService do
}
end
+ it 'outdates MR suggestions' do
+ expect_next_instance_of(Suggestions::OutdateService) do |service|
+ expect(service).to receive(:execute).with(@merge_request).and_call_original
+ expect(service).to receive(:execute).with(@another_merge_request).and_call_original
+ end
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ end
+
context 'when source branch ref does not exists' do
before do
DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
@@ -329,14 +338,16 @@ describe MergeRequests::RefreshService do
context 'push to fork repo source branch' do
let(:refresh_service) { service.new(@fork_project, @user) }
- context 'open fork merge request' do
- before do
- allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
- end
+ def refresh
+ allow(refresh_service).to receive(:execute_hooks)
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+ context 'open fork merge request' do
it 'executes hooks with update action' do
+ refresh
+
expect(refresh_service).to have_received(:execute_hooks)
.with(@fork_merge_request, 'update', old_rev: @oldrev)
@@ -347,21 +358,30 @@ describe MergeRequests::RefreshService do
expect(@build_failed_todo).to be_pending
expect(@fork_build_failed_todo).to be_pending
end
+
+ it 'outdates opened forked MR suggestions' do
+ expect_next_instance_of(Suggestions::OutdateService) do |service|
+ expect(service).to receive(:execute).with(@fork_merge_request).and_call_original
+ end
+
+ refresh
+ end
end
context 'closed fork merge request' do
before do
@fork_merge_request.close!
- allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
end
it 'do not execute hooks with update action' do
+ refresh
+
expect(refresh_service).not_to have_received(:execute_hooks)
end
it 'updates merge request to closed state' do
+ refresh
+
expect(@merge_request.notes).to be_empty
expect(@merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index fe85b5c9065..80b5dcac6c7 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -5,6 +5,41 @@ require 'spec_helper'
describe Suggestions::ApplyService do
include ProjectForksHelper
+ shared_examples 'successfully creates commit and updates suggestion' do
+ def apply(suggestion)
+ result = subject.execute(suggestion)
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'updates the file with the new contents' do
+ apply(suggestion)
+
+ blob = project.repository.blob_at_branch(merge_request.source_branch,
+ position.new_path)
+
+ expect(blob.data).to eq(expected_content)
+ end
+
+ it 'updates suggestion applied and commit_id columns' do
+ expect { apply(suggestion) }
+ .to change(suggestion, :applied)
+ .from(false).to(true)
+ .and change(suggestion, :commit_id)
+ .from(nil)
+ end
+
+ it 'created commit has users email and name' do
+ apply(suggestion)
+
+ commit = project.repository.commit
+
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ expect(commit.author_name).to eq(user.name)
+ end
+ end
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :commit_email) }
@@ -17,9 +52,8 @@ describe Suggestions::ApplyService do
end
let(:suggestion) do
- create(:suggestion, note: diff_note,
- from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n",
- to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
+ create(:suggestion, :content_from_repo, note: diff_note,
+ to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
end
subject { described_class.new(user) }
@@ -84,39 +118,7 @@ describe Suggestions::ApplyService do
project.add_maintainer(user)
end
- it 'updates the file with the new contents' do
- subject.execute(suggestion)
-
- blob = project.repository.blob_at_branch(merge_request.source_branch,
- position.new_path)
-
- expect(blob.data).to eq(expected_content)
- end
-
- it 'returns success status' do
- result = subject.execute(suggestion)
-
- expect(result[:status]).to eq(:success)
- end
-
- it 'updates suggestion applied and commit_id columns' do
- expect { subject.execute(suggestion) }
- .to change(suggestion, :applied)
- .from(false).to(true)
- .and change(suggestion, :commit_id)
- .from(nil)
- end
-
- it 'created commit has users email and name' do
- subject.execute(suggestion)
-
- commit = project.repository.commit
-
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
- expect(commit.author_name).to eq(user.name)
- end
+ it_behaves_like 'successfully creates commit and updates suggestion'
context 'when it fails to apply because the file was changed' do
it 'returns error message' do
@@ -212,11 +214,13 @@ describe Suggestions::ApplyService do
end
def apply_suggestion(suggestion)
- suggestion.note.reload
+ suggestion.reload
merge_request.reload
merge_request.clear_memoized_shas
result = subject.execute(suggestion)
+ expect(result[:status]).to eq(:success)
+
refresh = MergeRequests::RefreshService.new(project, user)
refresh.execute(merge_request.diff_head_sha,
suggestion.commit_id,
@@ -241,7 +245,7 @@ describe Suggestions::ApplyService do
suggestion_2_changes = { old_line: 24,
new_line: 31,
- from_content: " @cmd_output << stderr.read\n",
+ from_content: " @cmd_output << stderr.read\n",
to_content: "# v2 change\n",
path: path }
@@ -368,7 +372,18 @@ describe Suggestions::ApplyService do
result = subject.execute(suggestion)
- expect(result).to eq(message: 'The file was not found',
+ expect(result).to eq(message: 'Suggestion is not appliable',
+ status: :error)
+ end
+ end
+
+ context 'suggestion is eligible to be outdated' do
+ it 'returns error message' do
+ expect(suggestion).to receive(:outdated?) { true }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'Suggestion is not appliable',
status: :error)
end
end
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index 1b4b15b8eaa..ce4990a34a4 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -40,6 +40,14 @@ describe Suggestions::CreateService do
```thing
this is not a suggestion, it's a thing
```
+
+ ```suggestion:-3+2
+ # multi-line suggestion 1
+ ```
+
+ ```suggestion:-5
+ # multi-line suggestion 1
+ ```
MARKDOWN
end
@@ -54,7 +62,7 @@ describe Suggestions::CreateService do
end
it 'does not try to parse suggestions' do
- expect(Banzai::SuggestionsParser).not_to receive(:parse)
+ expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse)
subject.execute
end
@@ -71,7 +79,7 @@ describe Suggestions::CreateService do
it 'does not try to parse suggestions' do
allow(note).to receive(:on_text?) { false }
- expect(Banzai::SuggestionsParser).not_to receive(:parse)
+ expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse)
subject.execute
end
@@ -87,7 +95,9 @@ describe Suggestions::CreateService do
end
it 'creates no suggestion when diff file is not found' do
- expect(note).to receive(:latest_diff_file) { nil }
+ expect_next_instance_of(DiffNote) do |diff_note|
+ expect(diff_note).to receive(:latest_diff_file).twice { nil }
+ end
expect { subject.execute }.not_to change(Suggestion, :count)
end
@@ -101,43 +111,44 @@ describe Suggestions::CreateService do
note: markdown)
end
- context 'single line suggestions' do
- it 'persists suggestion records' do
- expect { subject.execute }
- .to change { note.suggestions.count }
- .from(0)
- .to(2)
- end
+ let(:expected_suggestions) do
+ Gitlab::Diff::SuggestionsParser.parse(markdown,
+ project: note.project,
+ position: note.position)
+ end
- it 'persists original from_content lines and suggested lines' do
- subject.execute
+ it 'persists suggestion records' do
+ expect { subject.execute }.to change { note.suggestions.count }
+ .from(0).to(expected_suggestions.size)
+ end
- suggestions = note.suggestions.order(:relative_order)
+ it 'persists suggestions data correctly' do
+ subject.execute
- suggestion_1 = suggestions.first
- suggestion_2 = suggestions.last
+ suggestions = note.suggestions.order(:relative_order)
- expect(suggestion_1).to have_attributes(from_content: " vars = {\n",
- to_content: " foo\n bar\n")
+ suggestions.zip(expected_suggestions) do |suggestion, expected_suggestion|
+ expected_data = expected_suggestion.to_hash
- expect(suggestion_2).to have_attributes(from_content: " vars = {\n",
- to_content: " xpto\n baz\n")
+ expect(suggestion.from_content).to eq(expected_data[:from_content])
+ expect(suggestion.to_content).to eq(expected_data[:to_content])
+ expect(suggestion.lines_above).to eq(expected_data[:lines_above])
+ expect(suggestion.lines_below).to eq(expected_data[:lines_below])
end
+ end
- context 'outdated position note' do
- let!(:outdated_diff) { merge_request.merge_request_diff }
- let!(:latest_diff) { merge_request.create_merge_request_diff }
- let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) }
- let(:position) { build_position(diff_refs: latest_diff.diff_refs) }
+ context 'outdated position note' do
+ let!(:outdated_diff) { merge_request.merge_request_diff }
+ let!(:latest_diff) { merge_request.create_merge_request_diff }
+ let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) }
+ let(:position) { build_position(diff_refs: latest_diff.diff_refs) }
- it 'uses the correct position when creating the suggestion' do
- expect(note.position)
- .to receive(:diff_file)
- .with(project_with_repo.repository)
- .and_call_original
+ it 'uses the correct position when creating the suggestion' do
+ expect(Gitlab::Diff::SuggestionsParser).to receive(:parse)
+ .with(note.note, project: note.project, position: note.position)
+ .and_call_original
- subject.execute
- end
+ subject.execute
end
end
end
diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb
new file mode 100644
index 00000000000..bcc627013d8
--- /dev/null
+++ b/spec/services/suggestions/outdate_service_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Suggestions::OutdateService do
+ describe '#execute' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.target_project }
+ let(:user) { merge_request.author }
+ let(:file_path) { 'files/ruby/popen.rb' }
+ let(:branch_name) { project.default_branch }
+ let(:diff_file) { suggestion.diff_file }
+ let(:position) { build_position(file_path, comment_line) }
+ let(:note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ def build_position(path, line)
+ Gitlab::Diff::Position.new(old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: line,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ def commit_changes(file_path, new_content)
+ params = {
+ file_path: file_path,
+ commit_message: "Update File",
+ file_content: new_content,
+ start_project: project,
+ start_branch: project.default_branch,
+ branch_name: branch_name
+ }
+
+ Files::UpdateService.new(project, user, params).execute
+ end
+
+ def update_file_line(diff_file, change_line, content)
+ new_lines = diff_file.new_blob.data.lines
+ new_lines[change_line..change_line] = content
+ result = commit_changes(diff_file.file_path, new_lines.join)
+ newrev = result[:result]
+
+ expect(result[:status]).to eq(:success)
+ expect(newrev).to be_present
+
+ # Ensure all memoized data is cleared in order
+ # to generate the new merge_request_diff.
+ MergeRequest.find(merge_request.id).reload_diff(user)
+
+ note.reload
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new.execute(merge_request) }
+
+ context 'when there is a change within multi-line suggestion range' do
+ let(:comment_line) { 9 }
+ let(:lines_above) { 8 } # suggesting to change lines 1..9
+ let(:change_line) { 2 } # line 2 is within the range
+ let!(:suggestion) do
+ create(:suggestion, :content_from_repo, note: note, lines_above: lines_above)
+ end
+
+ it 'updates the outdatable suggestion record' do
+ update_file_line(diff_file, change_line, "# foo\nbar\n")
+
+ # Make sure note is still active
+ expect(note.active?).to be(true)
+
+ expect { subject }.to change { suggestion.reload.outdated }
+ .from(false).to(true)
+ end
+ end
+
+ context 'when there is no change within multi-line suggestion range' do
+ let(:comment_line) { 9 }
+ let(:lines_above) { 3 } # suggesting to change lines 6..9
+ let(:change_line) { 2 } # line 2 is not within the range
+ let!(:suggestion) do
+ create(:suggestion, :content_from_repo, note: note, lines_above: lines_above)
+ end
+
+ subject { described_class.new.execute(merge_request) }
+
+ it 'does not outdates suggestion record' do
+ update_file_line(diff_file, change_line, "# foo\nbar\n")
+
+ # Make sure note is still active
+ expect(note.active?).to be(true)
+
+ expect { subject }.not_to change { suggestion.reload.outdated }.from(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb
index d974cc0226f..ddf9d2b4917 100644
--- a/spec/services/verify_pages_domain_service_spec.rb
+++ b/spec/services/verify_pages_domain_service_spec.rb
@@ -9,88 +9,130 @@ describe VerifyPagesDomainService do
subject(:service) { described_class.new(domain) }
describe '#execute' do
- context 'verification code recognition (verified domain)' do
- where(:domain_sym, :code_sym) do
- :domain | :verification_code
- :domain | :keyed_verification_code
+ where(:domain_sym, :code_sym) do
+ :domain | :verification_code
+ :domain | :keyed_verification_code
- :verification_domain | :verification_code
- :verification_domain | :keyed_verification_code
- end
-
- with_them do
- set(:domain) { create(:pages_domain) }
+ :verification_domain | :verification_code
+ :verification_domain | :keyed_verification_code
+ end
- let(:domain_name) { domain.send(domain_sym) }
- let(:verification_code) { domain.send(code_sym) }
+ with_them do
+ let(:domain_name) { domain.send(domain_sym) }
+ let(:verification_code) { domain.send(code_sym) }
+ shared_examples 'verifies and enables the domain' do
it 'verifies and enables the domain' do
- stub_resolver(domain_name => ['something else', verification_code])
-
expect(service.execute).to eq(status: :success)
+
expect(domain).to be_verified
expect(domain).to be_enabled
end
+ end
- it 'verifies and enables when the code is contained partway through a TXT record' do
- stub_resolver(domain_name => "something #{verification_code} else")
+ shared_examples 'successful enablement and verification' do
+ context 'when txt record contains verification code' do
+ before do
+ stub_resolver(domain_name => ['something else', verification_code])
+ end
- expect(service.execute).to eq(status: :success)
- expect(domain).to be_verified
- expect(domain).to be_enabled
+ include_examples 'verifies and enables the domain'
end
- it 'does not verify when the code is not present' do
- stub_resolver(domain_name => 'something else')
-
- expect(service.execute).to eq(error_status)
+ context 'when txt record contains verification code with other text' do
+ before do
+ stub_resolver(domain_name => "something #{verification_code} else")
+ end
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ include_examples 'verifies and enables the domain'
end
end
- context 'verified domain' do
- set(:domain) { create(:pages_domain) }
+ context 'when domain is disabled(or new)' do
+ let(:domain) { create(:pages_domain, :disabled) }
- it 'unverifies (but does not disable) when the right code is not present' do
- stub_resolver(domain.domain => 'something else')
+ include_examples 'successful enablement and verification'
- expect(service.execute).to eq(error_status)
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ shared_examples 'unverifies and disables domain' do
+ it 'unverifies and disables domain' do
+ expect(service.execute).to eq(error_status)
+
+ expect(domain).not_to be_verified
+ expect(domain).not_to be_enabled
+ end
end
- it 'unverifies (but does not disable) when no records are present' do
- stub_resolver
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
- expect(service.execute).to eq(error_status)
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ include_examples 'unverifies and disables domain'
+ end
+
+ context 'when no txt records are present' do
+ before do
+ stub_resolver
+ end
+
+ include_examples 'unverifies and disables domain'
end
end
- context 'expired domain' do
- set(:domain) { create(:pages_domain, :expired) }
+ context 'when domain is verified' do
+ let(:domain) { create(:pages_domain) }
- it 'verifies and enables when the right code is present' do
- stub_resolver(domain.domain => domain.keyed_verification_code)
+ include_examples 'successful enablement and verification'
- expect(service.execute).to eq(status: :success)
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
- expect(domain).to be_verified
- expect(domain).to be_enabled
+ it 'unverifies but does not disable domain' do
+ expect(service.execute).to eq(error_status)
+ expect(domain).not_to be_verified
+ expect(domain).to be_enabled
+ end
end
- it 'disables when the right code is not present' do
- error_status[:message] += '. It is now disabled.'
+ context 'when no txt records are present' do
+ before do
+ stub_resolver
+ end
- stub_resolver
+ it 'unverifies but does not disable domain' do
+ expect(service.execute).to eq(error_status)
+ expect(domain).not_to be_verified
+ expect(domain).to be_enabled
+ end
+ end
+ end
- expect(service.execute).to eq(error_status)
+ context 'when domain is expired' do
+ let(:domain) { create(:pages_domain, :expired) }
- expect(domain).not_to be_verified
- expect(domain).not_to be_enabled
+ context 'when the right code is present' do
+ before do
+ stub_resolver(domain_name => domain.keyed_verification_code)
+ end
+
+ include_examples 'verifies and enables the domain'
+ end
+
+ context 'when the right code is not present' do
+ before do
+ stub_resolver
+ end
+
+ it 'disables domain' do
+ error_status[:message] += '. It is now disabled.'
+
+ expect(service.execute).to eq(error_status)
+
+ expect(domain).not_to be_verified
+ expect(domain).not_to be_enabled
+ end
end
end
diff --git a/yarn.lock b/yarn.lock
index 4a7443c0bd8..2bdbca103b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -638,12 +638,12 @@
lodash "^4.17.11"
to-fast-properties "^2.0.0"
-"@gitlab/csslab@^1.8.0":
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.8.0.tgz#54a2457fdc80f006665f0e578a5532780954ccfa"
- integrity sha512-RZylRElufH1kwsBQlIDaVcrcXMyD5IEGrU6ABUd8W3LG8/F9jJ4Y3Ys7EPTpK/qFJyx86AutTtFGRxRNlMx85w==
+"@gitlab/csslab@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c"
+ integrity sha512-Zjayzokm7E2wgxUR/pxIMocdiBB5XHt2PEemdzD8qD+aQmMpMxSyIEMQk5Jq0Wgv+Rd5WXTolTw3kmb9l8ZeJg==
dependencies:
- bootstrap "4.1.3"
+ bootstrap "^4.1.3"
"@gitlab/eslint-config@^1.4.0":
version "1.4.0"
@@ -707,10 +707,10 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
-"@types/glob@^5":
- version "5.0.35"
- resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a"
- integrity sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==
+"@types/glob@5 - 7":
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
+ integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
dependencies:
"@types/events" "*"
"@types/minimatch" "*"
@@ -791,7 +791,7 @@
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
-"@vue/component-compiler-utils@^2.0.0", "@vue/component-compiler-utils@^2.4.0":
+"@vue/component-compiler-utils@^2.4.0", "@vue/component-compiler-utils@^2.5.1":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b"
integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==
@@ -1681,10 +1681,10 @@ bootstrap-vue@^2.0.0-rc.11:
popper.js "^1.12.9"
vue-functional-data-merge "^2.0.5"
-bootstrap@4.1.3, bootstrap@^4.1.1:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
- integrity sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==
+bootstrap@4.3.1, bootstrap@^4.1.1, bootstrap@^4.1.3:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
+ integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
boxen@^1.2.1:
version "1.3.0"
@@ -4546,27 +4546,27 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
-gettext-extractor-vue@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.1.tgz#69d2737eb8f1938803ffcf9317133ed59fb2372f"
- integrity sha512-UnkWVO5jQQrs17L7HSlKj3O7U8C4+AQFzE05MK/I+JkMZdQdB6JMjA0IK0c4GObSlkgx4aiCCG6zWqIBnDR95w==
+gettext-extractor-vue@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.2.tgz#16e1cdbdaf37e5bdf3cb0aff63685bdc5e74e906"
+ integrity sha512-tnTAU1TdQFREv4Q4hfBDuB329eugeFsYmV7lE9U1jkZEyxcf4oPgimLHNZVNaEUg4+JJwhB8B9HIeqbcbSW32g==
dependencies:
bluebird "^3.5.1"
glob "^7.1.2"
- vue-template-compiler "^2.5.0"
+ vue-template-compiler "^2.5.20"
-gettext-extractor@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.3.2.tgz#d5172ba8d175678bd40a5abe7f908fa2a9d9473b"
- integrity sha1-1RcrqNF1Z4vUClq+f5CPoqnZRzs=
+gettext-extractor@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.4.3.tgz#882679cefc71888eb6e69297e6b2dc14c0384fef"
+ integrity sha512-YSNdTCHmzm58Rc21thtXj7jRIOlqINftM3XbtvNK28C88i35EnEB89iOeV9Vetv7wcb/wiPAtcq/6iSnt2pMyw==
dependencies:
- "@types/glob" "^5"
+ "@types/glob" "5 - 7"
"@types/parse5" "^5"
css-selector-parser "^1.3"
glob "5 - 7"
parse5 "^5"
pofile "^1"
- typescript "^2"
+ typescript "2 - 3"
glob-parent@^3.1.0:
version "3.1.0"
@@ -8121,10 +8121,10 @@ pofile@^1:
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==
-popper.js@^1.12.9, popper.js@^1.14.3:
- version "1.14.3"
- resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
- integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=
+popper.js@^1.12.9, popper.js@^1.14.7:
+ version "1.14.7"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e"
+ integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==
portfinder@^1.0.9:
version "1.0.13"
@@ -10512,10 +10512,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@^2:
- version "2.9.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
- integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
+"typescript@2 - 3":
+ version "3.3.4000"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0"
+ integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.5"
@@ -10938,12 +10938,12 @@ vue-jest@^4.0.0-beta.2:
source-map "^0.5.6"
ts-jest "^23.10.5"
-vue-loader@^15.4.2:
- version "15.4.2"
- resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2"
- integrity sha512-nVV27GNIA9MeoD8yQ3dkUzwlAaAsWeYSWZHsu/K04KCD339lW0Jv2sJWsjj3721SP7sl2lYdPmjcHgkWQSp5bg==
+vue-loader@^15.4.2, vue-loader@^15.7.0:
+ version "15.7.0"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3"
+ integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==
dependencies:
- "@vue/component-compiler-utils" "^2.0.0"
+ "@vue/component-compiler-utils" "^2.5.1"
hash-sum "^1.0.2"
loader-utils "^1.1.0"
vue-hot-reload-api "^2.3.0"
@@ -10969,10 +10969,10 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.21:
- version "2.5.21"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
- integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
+vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
+ integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -10982,20 +10982,20 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
-vue-virtual-scroll-list@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a"
- integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno=
+vue-virtual-scroll-list@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.3.1.tgz#efcb83d3a3dcc69cd886fa4de1130a65493e8f76"
+ integrity sha512-PMTxiK9/P1LtgoWWw4n1QnmDDkYqIdWWCNdt1L4JD9g6rwDgnsGsSV10bAnd5n7DQLHGWHjRex+zAbjXWT8t0g==
-vue@^2.5.21:
- version "2.5.21"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
- integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
+vue@^2.5.21, vue@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
+ integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
-vuex@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
- integrity sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w==
+vuex@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9"
+ integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==
w3c-hr-time@^1.0.1:
version "1.0.1"