Commit b7adbe7f authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee-2018-09-03' into 'master'

CE upstream - 2018-09-03 15:21 UTC

See merge request gitlab-org/gitlab-ee!7213
parents 2ce50843 fc2a35b0
...@@ -23,6 +23,11 @@ export default { ...@@ -23,6 +23,11 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
wasValidated: false,
};
},
computed: { computed: {
...mapState([ ...mapState([
'badgeInAddForm', 'badgeInAddForm',
...@@ -39,16 +44,6 @@ export default { ...@@ -39,16 +44,6 @@ export default {
return this.badgeInAddForm; return this.badgeInAddForm;
}, },
canSubmit() {
return (
this.badge !== null &&
this.badge.imageUrl &&
this.badge.imageUrl.trim() !== '' &&
this.badge.linkUrl &&
this.badge.linkUrl.trim() !== '' &&
!this.isSaving
);
},
helpText() { helpText() {
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha'] const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
.map(placeholder => `<code>%{${placeholder}}</code>`) .map(placeholder => `<code>%{${placeholder}}</code>`)
...@@ -93,11 +88,18 @@ export default { ...@@ -93,11 +88,18 @@ export default {
}); });
}, },
}, },
submitButtonLabel() { badgeImageUrlExample() {
if (this.isEditing) { const exampleUrl =
return s__('Badges|Save changes'); 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/badge.svg';
} return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
return s__('Badges|Add badge'); exampleUrl,
});
},
badgeLinkUrlExample() {
const exampleUrl = 'https://example.gitlab.com/%{project_path}';
return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
exampleUrl,
});
}, },
}, },
methods: { methods: {
...@@ -109,7 +111,9 @@ export default { ...@@ -109,7 +111,9 @@ export default {
this.stopEditing(); this.stopEditing();
}, },
onSubmit() { onSubmit() {
if (!this.canSubmit) { const form = this.$el;
if (!form.checkValidity()) {
this.wasValidated = true;
return Promise.resolve(); return Promise.resolve();
} }
...@@ -117,6 +121,7 @@ export default { ...@@ -117,6 +121,7 @@ export default {
return this.saveBadge() return this.saveBadge()
.then(() => { .then(() => {
createFlash(s__('Badges|The badge was saved.'), 'notice'); createFlash(s__('Badges|The badge was saved.'), 'notice');
this.wasValidated = false;
}) })
.catch(error => { .catch(error => {
createFlash( createFlash(
...@@ -129,6 +134,7 @@ export default { ...@@ -129,6 +134,7 @@ export default {
return this.addBadge() return this.addBadge()
.then(() => { .then(() => {
createFlash(s__('Badges|A new badge was added.'), 'notice'); createFlash(s__('Badges|A new badge was added.'), 'notice');
this.wasValidated = false;
}) })
.catch(error => { .catch(error => {
createFlash( createFlash(
...@@ -138,47 +144,58 @@ export default { ...@@ -138,47 +144,58 @@ export default {
}); });
}, },
}, },
badgeImageUrlPlaceholder:
'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
}; };
</script> </script>
<template> <template>
<form <form
class="prepend-top-default append-bottom-default" :class="{ 'was-validated': wasValidated }"
class="prepend-top-default append-bottom-default needs-validation"
novalidate
@submit.prevent.stop="onSubmit" @submit.prevent.stop="onSubmit"
> >
<div class="form-group"> <div class="form-group">
<label for="badge-link-url">{{ s__('Badges|Link') }}</label> <label
for="badge-link-url"
class="label-bold"
>{{ s__('Badges|Link') }}</label>
<p v-html="helpText"></p>
<input <input
id="badge-link-url" id="badge-link-url"
v-model="linkUrl" v-model="linkUrl"
:placeholder="$options.badgeLinkUrlPlaceholder" type="URL"
type="text"
class="form-control" class="form-control"
required
@input="debouncedPreview" @input="debouncedPreview"
/> />
<span <div class="invalid-feedback">
class="form-text text-muted" {{ s__('Badges|Please fill in a valid URL') }}
v-html="helpText" </div>
></span> <span class="form-text text-muted">
{{ badgeLinkUrlExample }}
</span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label> <label
for="badge-image-url"
class="label-bold"
>{{ s__('Badges|Badge image URL') }}</label>
<p v-html="helpText"></p>
<input <input
id="badge-image-url" id="badge-image-url"
v-model="imageUrl" v-model="imageUrl"
:placeholder="$options.badgeImageUrlPlaceholder" type="URL"
type="text"
class="form-control" class="form-control"
required
@input="debouncedPreview" @input="debouncedPreview"
/> />
<span <div class="invalid-feedback">
class="form-text text-muted" {{ s__('Badges|Please fill in a valid URL') }}
v-html="helpText" </div>
></span> <span class="form-text text-muted">
{{ badgeImageUrlExample }}
</span>
</div> </div>
<div class="form-group"> <div class="form-group">
...@@ -200,20 +217,32 @@ export default { ...@@ -200,20 +217,32 @@ export default {
>{{ s__('Badges|No image to preview') }}</p> >{{ s__('Badges|No image to preview') }}</p>
</div> </div>
<div class="row-content-block"> <div
v-if="isEditing"
class="row-content-block"
>
<loading-button <loading-button
:disabled="!canSubmit"
:loading="isSaving" :loading="isSaving"
:label="submitButtonLabel" :label="s__('Badges|Save changes')"
type="submit" type="submit"
container-class="btn btn-success" container-class="btn btn-success"
/> />
<button <button
v-if="isEditing"
class="btn btn-cancel" class="btn btn-cancel"
type="button" type="button"
@click="onCancel" @click="onCancel"
>{{ __('Cancel') }}</button> >{{ __('Cancel') }}</button>
</div> </div>
<div
v-else
class="form-group"
>
<loading-button
:loading="isSaving"
:label="s__('Badges|Add badge')"
type="submit"
container-class="btn btn-success"
/>
</div>
</form> </form>
</template> </template>
...@@ -28,7 +28,7 @@ export default { ...@@ -28,7 +28,7 @@ export default {
{{ s__('Badges|Your badges') }} {{ s__('Badges|Your badges') }}
<span <span
v-show="!isLoading" v-show="!isLoading"
class="badge" class="badge badge-pill"
>{{ badges.length }}</span> >{{ badges.length }}</span>
</div> </div>
<loading-icon <loading-icon
......
...@@ -43,13 +43,13 @@ export default { ...@@ -43,13 +43,13 @@ export default {
<badge <badge
:image-url="badge.renderedImageUrl" :image-url="badge.renderedImageUrl"
:link-url="badge.renderedLinkUrl" :link-url="badge.renderedLinkUrl"
class="table-section section-30" class="table-section section-40"
/> />
<span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span> <span class="table-section section-30 str-truncated">{{ badge.linkUrl }}</span>
<div class="table-section section-10"> <div class="table-section section-15">
<span class="badge">{{ badgeKindText }}</span> <span class="badge badge-pill">{{ badgeKindText }}</span>
</div> </div>
<div class="table-section section-10 table-button-footer"> <div class="table-section section-15 table-button-footer">
<div <div
v-if="canEditBadge" v-if="canEditBadge"
class="table-action-buttons"> class="table-action-buttons">
......
...@@ -2,14 +2,13 @@ import groupAvatar from '~/group_avatar'; ...@@ -2,14 +2,13 @@ import groupAvatar from '~/group_avatar';
import TransferDropdown from '~/groups/transfer_dropdown'; import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal'; import initConfirmDangerModal from '~/confirm_danger_modal';
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import { GROUP_BADGE } from '~/badges/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
groupAvatar(); groupAvatar();
new TransferDropdown(); // eslint-disable-line no-new new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal(); initConfirmDangerModal();
});
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
mountBadgeSettings(GROUP_BADGE);
}); });
import { PROJECT_BADGE } from '~/badges/constants';
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit'; import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal'; import initConfirmDangerModal from '~/confirm_danger_modal';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import initProjectLoadingSpinner from '../shared/save_project_loader'; import initProjectLoadingSpinner from '../shared/save_project_loader';
import projectAvatar from '../shared/project_avatar'; import projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions'; import initProjectPermissionsSettings from '../shared/permissions';
...@@ -13,4 +15,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -13,4 +15,5 @@ document.addEventListener('DOMContentLoaded', () => {
projectAvatar(); projectAvatar();
initProjectPermissionsSettings(); initProjectPermissionsSettings();
initConfirmDangerModal(); initConfirmDangerModal();
mountBadgeSettings(PROJECT_BADGE);
}); });
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import { PROJECT_BADGE } from '~/badges/constants';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => {
mountBadgeSettings(PROJECT_BADGE);
});
module Groups
module Settings
class BadgesController < Groups::ApplicationController
include API::Helpers::RelatedResourcesHelpers
before_action :authorize_admin_group!
def index
@badge_api_endpoint = expose_url(api_v4_groups_badges_path(id: @group.id))
end
end
end
end
class GroupsController < Groups::ApplicationController class GroupsController < Groups::ApplicationController
include API::Helpers::RelatedResourcesHelpers
include IssuesAction include IssuesAction
include MergeRequestsAction include MergeRequestsAction
include ParamsBackwardCompatibility include ParamsBackwardCompatibility
...@@ -78,6 +79,7 @@ class GroupsController < Groups::ApplicationController ...@@ -78,6 +79,7 @@ class GroupsController < Groups::ApplicationController
end end
def edit def edit
@badge_api_endpoint = expose_url(api_v4_groups_badges_path(id: @group.id))
end end
def projects def projects
......
module Projects
module Settings
class BadgesController < Projects::ApplicationController
include API::Helpers::RelatedResourcesHelpers
before_action :authorize_admin_project!
def index
@badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
end
end
end
end
class ProjectsController < Projects::ApplicationController class ProjectsController < Projects::ApplicationController
include API::Helpers::RelatedResourcesHelpers
include IssuableCollections include IssuableCollections
include ExtractsPath include ExtractsPath
include PreviewMarkdown include PreviewMarkdown
...@@ -34,6 +35,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -34,6 +35,7 @@ class ProjectsController < Projects::ApplicationController
end end
def edit def edit
@badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
render 'edit' render 'edit'
end end
......
...@@ -25,6 +25,18 @@ ...@@ -25,6 +25,18 @@
.settings-content .settings-content
= render 'groups/settings/permissions' = render 'groups/settings/permissions'
%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
= s_('GroupSettings|Badges')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
= s_('GroupSettings|Customize your group badges.')
= link_to s_('GroupSettings|Learn more about badges.'), help_page_path('user/project/badges')
.settings-content
= render 'shared/badges/badge_settings'
%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) } %section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
%h4 %h4
......
...@@ -130,12 +130,6 @@ ...@@ -130,12 +130,6 @@
%span %span
= _('General') = _('General')
= nav_link(controller: :badges) do
= link_to group_settings_badges_path(@group), title: _('Project Badges') do
%span
= _('Project Badges')
= nav_link(path: 'groups#projects') do = nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: _('Projects') do = link_to projects_group_path(@group), title: _('Projects') do
%span %span
......
...@@ -325,11 +325,6 @@ ...@@ -325,11 +325,6 @@
= link_to project_project_members_path(@project), title: _('Members') do = link_to project_project_members_path(@project), title: _('Members') do
%span %span
= _('Members') = _('Members')
- if can_edit
= nav_link(controller: :badges) do
= link_to project_settings_badges_path(@project), title: _('Badges') do
%span
= _('Badges')
- if can_edit - if can_edit
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: _('Integrations') do = link_to project_settings_integrations_path(@project), title: _('Integrations') do
......
...@@ -102,6 +102,18 @@ ...@@ -102,6 +102,18 @@
= render_if_exists 'projects/service_desk_settings' = render_if_exists 'projects/service_desk_settings'
%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
= s_('ProjectSettings|Badges')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
= s_('ProjectSettings|Customize your project badges.')
= link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
.settings-content
= render 'shared/badges/badge_settings'
= render 'export', project: @project = render 'export', project: @project
%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) } %section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
......
---
title: Add JSON logging for Bitbucket Server importer
merge_request: 21378
author:
type: other
---
title: Move badge settings to general settings
merge_request: 21333
author:
type: changed
...@@ -26,7 +26,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -26,7 +26,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
namespace :settings do namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd' resource :ci_cd, only: [:show], controller: 'ci_cd'
resources :badges, only: [:index]
end end
resource :variables, only: [:show, :update] resource :variables, only: [:show, :update]
......
...@@ -526,7 +526,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -526,7 +526,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :repository, only: [:show], controller: :repository do resource :repository, only: [:show], controller: :repository do
post :create_deploy_token, path: 'deploy_token/create' post :create_deploy_token, path: 'deploy_token/create'
end end
resources :badges, only: [:index]
end end
# Since both wiki and repository routing contains wildcard characters # Since both wiki and repository routing contains wildcard characters
......
...@@ -219,6 +219,15 @@ installations from source. ...@@ -219,6 +219,15 @@ installations from source.
It logs information whenever a [repository check is run][repocheck] on a project. It logs information whenever a [repository check is run][repocheck] on a project.
## `importer.log`
Introduced in GitLab 11.3. This file lives in `/var/log/gitlab/gitlab-rails/importer.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/importer.log` for
installations from source.
Currently it logs the progress of project imports from the Bitbucket Server
importer. Future importers may use this file.
## Reconfigure Logs ## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
......
...@@ -280,5 +280,5 @@ are listed in the descriptions of the relevant settings. ...@@ -280,5 +280,5 @@ are listed in the descriptions of the relevant settings.
| `usage_ping_enabled` | boolean | no | Every week GitLab will report license usage back to GitLab, Inc. | | `usage_ping_enabled` | boolean | no | Every week GitLab will report license usage back to GitLab, Inc. |
| `user_default_external` | boolean | no | Newly registered users will be external by default. | | `user_default_external` | boolean | no | Newly registered users will be external by default. |
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. | | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. |
| `user_show_add_ssh_key_message` | boolean | no | When set to `false`, disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. | | `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. | | `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
...@@ -17,7 +17,7 @@ If you find that you have to add the same badges to several projects, you may wa ...@@ -17,7 +17,7 @@ If you find that you have to add the same badges to several projects, you may wa
To add a new badge to a project: To add a new badge to a project:
1. Navigate to your project's **Settings > Badges**. 1. Navigate to your project's **Settings > General > Badges**.
1. Under "Link", enter the URL that the badges should point to and under 1. Under "Link", enter the URL that the badges should point to and under
"Badge image URL" the URL of the image that should be displayed. "Badge image URL" the URL of the image that should be displayed.
1. Submit the badge by clicking the **Add badge** button. 1. Submit the badge by clicking the **Add badge** button.
...@@ -39,7 +39,7 @@ project, consider adding them on the [project level](#project-badges) or use ...@@ -39,7 +39,7 @@ project, consider adding them on the [project level](#project-badges) or use
To add a new badge to a group: To add a new badge to a group:
1. Navigate to your group's **Settings > Project Badges**. 1. Navigate to your group's **Settings > General > Badges**.
1. Under "Link", enter the URL that the badges should point to and under 1. Under "Link", enter the URL that the badges should point to and under
"Badge image URL" the URL of the image that should be displayed. "Badge image URL" the URL of the image that should be displayed.
1. Submit the badge by clicking the **Add badge** button. 1. Submit the badge by clicking the **Add badge** button.
......
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
attr_reader :recover_missing_commits attr_reader :recover_missing_commits
attr_reader :project, :project_key, :repository_slug, :client, :errors, :users attr_reader :project, :project_key, :repository_slug, :client, :errors, :users
attr_accessor :logger
REMOTE_NAME = 'bitbucket_server'.freeze REMOTE_NAME = 'bitbucket_server'.freeze
BATCH_SIZE = 100 BATCH_SIZE = 100
...@@ -36,6 +37,7 @@ module Gitlab ...@@ -36,6 +37,7 @@ module Gitlab
@errors = [] @errors = []
@users = {} @users = {}
@temp_branches = [] @temp_branches = []
@logger = Gitlab::Import::Logger.build
end end
def execute def execute
...@@ -44,6 +46,8 @@ module Gitlab ...@@ -44,6 +46,8 @@ module Gitlab
delete_temp_branches delete_temp_branches
handle_errors handle_errors
log_info(stage: "complete")
true true
end end
...@@ -118,15 +122,21 @@ module Gitlab ...@@ -118,15 +122,21 @@ module Gitlab
client.create_branch(project_key, repository_slug, branch_name, sha) client.create_branch(project_key, repository_slug, branch_name, sha)
branches_created << temp_branch branches_created << temp_branch
rescue BitbucketServer::Connection::ConnectionError => e rescue BitbucketServer::Connection::ConnectionError => e
Rails.logger.warn("BitbucketServerImporter: Unable to recreate branch for SHA #{sha}: #{e}") log_warn(message: "Unable to recreate branch", sha: sha, error: e.message)
end end
end end
end end
def import_repository def import_repository
log_info(stage: 'import_repository', message: 'starting import')
project.ensure_repository project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: self.class.refmap, remote_name: REMOTE_NAME) project.repository.fetch_as_mirror(project.import_url, refmap: self.class.refmap, remote_name: REMOTE_NAME)
log_info(stage: 'import_repository', message: 'finished import')
rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
log_error(stage: 'import_repository', message: 'failed import', error: e.message)
# Expire cache to prevent scenarios such as: # Expire cache to prevent scenarios such as:
# 1. First import failed, but the repo was imported successfully, so +exists?+ returns true # 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
# 2. Retried import, repo is broken or not imported but +exists?+ still returns true # 2. Retried import, repo is broken or not imported but +exists?+ still returns true
...@@ -157,7 +167,10 @@ module Gitlab ...@@ -157,7 +167,10 @@ module Gitlab
begin begin
import_bitbucket_pull_request(pull_request) import_bitbucket_pull_request(pull_request)
rescue StandardError => e rescue StandardError => e
errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw } backtrace = Gitlab::Profiler.clean_backtrace(e.backtrace)
log_error(stage: 'import_pull_requests', iid: pull_request.iid, error: e.message, backtrace: backtrace)
errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, backtrace: backtrace.join("\n"), raw_response: pull_request.raw }
end end
end end
end end
...@@ -169,12 +182,15 @@ module Gitlab ...@@ -169,12 +182,15 @@ module Gitlab
client.delete_branch(project_key, repository_slug, branch.name, branch.sha) client.delete_branch(project_key, repository_slug, branch.name, branch.sha)
project.repository.delete_branch(branch.name) project.repository.delete_branch(branch.name)
rescue BitbucketServer::Connection::ConnectionError => e rescue BitbucketServer::Connection::ConnectionError => e
log_error(stage: 'delete_temp_branches', branch: branch.name, error: e.message)
@errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message } @errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message }
end end
end end
end end
def import_bitbucket_pull_request(pull_request) def import_bitbucket_pull_request(pull_request)
log_info(stage: 'import_bitbucket_pull_requests', message: 'starting', iid: pull_request.iid)
description = '' description = ''
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author_email) description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author_email)
description += pull_request.description if pull_request.description description += pull_request.description if pull_request.description
...@@ -201,9 +217,13 @@ module Gitlab ...@@ -201,9 +217,13 @@ module Gitlab
merge_request = creator.execute(attributes) merge_request = creator.execute(attributes)
import_pull_request_comments(pull_request, merge_request) if merge_request.persisted? import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
log_info(stage: 'import_bitbucket_pull_requests', message: 'finished', iid: pull_request.iid)
end end
def import_pull_request_comments(pull_request, merge_request) def import_pull_request_comments(pull_request, merge_request)
log_info(stage: 'import_pull_request_comments', message: 'starting', iid: merge_request.iid)
comments, other_activities = client.activities(project_key, repository_slug, pull_request.iid).partition(&:comment?) comments, other_activities = client.activities(project_key, repository_slug, pull_request.iid).partition(&:comment?)
merge_event = other_activities.find(&:merge_event?) merge_event = other_activities.find(&:merge_event?)
...@@ -213,9 +233,16 @@ module Gitlab ...@@ -213,9 +233,16 @@ module Gitlab
import_inline_comments(inline_comments.map(&:comment), merge_request) import_inline_comments(inline_comments.map(&:comment), merge_request)
import_standalone_pr_comments(pr_comments.map(&:comment), merge_request) import_standalone_pr_comments(pr_comments.map(&:comment), merge_request)
log_info(stage: 'import_pull_request_comments', message: 'finished', iid: merge_request.iid,
merge_event_found: merge_event.present?,
inline_comments_count: inline_comments.count,
standalone_pr_comments: pr_comments.count)
end end
def import_merge_event(merge_request, merge_event) def import_merge_event(merge_request, merge_event)
log_info(stage: 'import_merge_event', message: 'starting', iid: merge_request.iid)
committer = merge_event.committer_email committer = merge_event.committer_email
user_id = gitlab_user_id(committer) user_id = gitlab_user_id(committer)
...@@ -223,9 +250,13 @@ module Gitlab ...@@ -223,9 +250,13 @@ module Gitlab
merge_request.update({ merge_commit_sha: merge_event.merge_commit }) merge_request.update({ merge_commit_sha: merge_event.merge_commit })
metric = MergeRequest::Metrics.find_or_initialize_by(merge_request: merge_request) metric = MergeRequest::Metrics.find_or_initialize_by(merge_request: merge_request)
metric.update(merged_by_id: user_id, merged_at: timestamp) metric.update(merged_by_id: user_id, merged_at: timestamp)
log_info(stage: 'import_merge_event', message: 'finished', iid: merge_request.iid)
end end
def import_inline_comments(inline_comments, merge_request) def import_inline_comments(inline_comments, merge_request)
log_info(stage: 'import_inline_comments', message: 'starting', iid: merge_request.iid)
inline_comments.each do |comment| inline_comments.each do |comment|
position = build_position(merge_request, comment) position = build_position(merge_request, comment)
parent = create_diff_note(merge_request, comment, position) parent = create_diff_note(merge_request, comment, position)
...@@ -238,6 +269,8 @@ module Gitlab ...@@ -238,6 +269,8 @@ module Gitlab
create_diff_note(merge_request, reply, position, discussion_id) create_diff_note(merge_request, reply, position, discussion_id)
end end
end end
log_info(stage: 'import_inline_comments', message: 'finished', iid: merge_request.iid)
end end
def create_diff_note(merge_request, comment, position, discussion_id = nil) def create_diff_note(merge_request, comment, position, discussion_id = nil)
...@@ -252,11 +285,14 @@ module Gitlab ...@@ -252,11 +285,14 @@ module Gitlab
return note return note
end end
log_info(stage: 'create_diff_note', message: 'creating fallback DiffNote', iid: merge_request.iid)
# Bitbucket Server supports the ability to comment on any line, not just the # Bitbucket Server supports the ability to comment on any line, not just the
# line in the diff. If we can't add the note as a DiffNote, fallback to creating # line in the diff. If we can't add the note as a DiffNote, fallback to creating
# a regular note. # a regular note.
create_fallback_diff_note(merge_request, comment, position) create_fallback_diff_note(merge_request, comment, position)
rescue StandardError => e rescue StandardError => e
log_error(stage: 'create_diff_note', comment_id: comment.id, error: e.message)
errors << { type: :pull_request, id: comment.id, errors: e.message } errors << { type: :pull_request, id: comment.id, errors: e.message }
nil nil
end end
...@@ -294,7 +330,8 @@ module Gitlab ...@@ -294,7 +330,8 @@ module Gitlab
merge_request.notes.create!(pull_request_comment_attributes(replies)) merge_request.notes.create!(pull_request_comment_attributes(replies))
end end
rescue StandardError => e rescue StandardError => e
errors << { type: :pull_request, iid: comment.id, errors: e.message } log_error(stage: 'import_standalone_pr_comments', merge_request_id: merge_request.id, comment_id: comment.id, error: e.message)
errors << { type: :pull_request, comment_id: comment.id, errors: e.message }
end end
end end
end end
...@@ -324,6 +361,26 @@ module Gitlab ...@@ -324,6 +361,26 @@ module Gitlab
updated_at: comment.updated_at updated_at: comment.updated_at
} }
end end
def log_info(details)
logger.info(log_base_data.merge(details))
end
def log_error(details)
logger.error(log_base_data.merge(details))
end
def log_warn(details)
logger.warn(log_base_data.merge(details))
end
def log_base_data
{
class: self.class.name,
project_id: project.id,
project_path: project.full_path
}
end
end end
end end
end end
module Gitlab
module Import
class Logger < ::Gitlab::JsonLogger
def self.file_name_noext
'importer'
end
end
end
end
...@@ -957,6 +957,9 @@ msgstr "" ...@@ -957,6 +957,9 @@ msgstr ""
msgid "Badges|No image to preview" msgid "Badges|No image to preview"
msgstr "" msgstr ""
msgid "Badges|Please fill in a valid URL"
msgstr ""
msgid "Badges|Project Badge" msgid "Badges|Project Badge"
msgstr "" msgstr ""
...@@ -990,6 +993,9 @@ msgstr "" ...@@ -990,6 +993,9 @@ msgstr ""
msgid "Badges|Your badges" msgid "Badges|Your badges"
msgstr "" msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
msgid "Begin with the selected commit" msgid "Begin with the selected commit"
msgstr "" msgstr ""
...@@ -3721,6 +3727,15 @@ msgstr "" ...@@ -3721,6 +3727,15 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}" msgid "GroupRoadmap|Until %{dateWord}"
msgstr "" msgstr ""
msgid "GroupSettings|Badges"
msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "" msgstr ""
...@@ -5689,15 +5704,24 @@ msgstr "" ...@@ -5689,15 +5704,24 @@ msgstr ""
msgid "ProjectPage|Project ID: %{project_id}" msgid "ProjectPage|Project ID: %{project_id}"
msgstr "" msgstr ""
msgid "ProjectSettings|Badges"
msgstr ""
msgid "ProjectSettings|Contact an admin to change this setting." msgid "ProjectSettings|Contact an admin to change this setting."
msgstr "" msgstr ""
msgid "ProjectSettings|Customize your project badges."
msgstr ""
msgid "ProjectSettings|Failed to protect the tag" msgid "ProjectSettings|Failed to protect the tag"
msgstr "" msgstr ""
msgid "ProjectSettings|Failed to update tag!" msgid "ProjectSettings|Failed to update tag!"
msgstr "" msgstr ""
msgid "ProjectSettings|Learn more about badges."
msgstr ""
msgid "ProjectSettings|Only signed commits can be pushed to this repository." msgid "ProjectSettings|Only signed commits can be pushed to this repository."
msgstr "" msgstr ""
......
...@@ -57,6 +57,16 @@ describe GroupsController do ...@@ -57,6 +57,16 @@ describe GroupsController do
end end
end end
describe 'GET edit' do
it 'sets the badge API endpoint' do
sign_in(owner)
get :edit, id: group.to_param
expect(assigns(:badge_api_endpoint)).not_to be_nil
end
end
describe 'GET #new' do describe 'GET #new' do
context 'when creating subgroups', :nested_groups do context 'when creating subgroups', :nested_groups do
[true, false].each do |can_create_group_status| [true, false].each do |can_create_group_status|
......
...@@ -307,6 +307,19 @@ describe ProjectsController do ...@@ -307,6 +307,19 @@ describe ProjectsController do
end end
end end
describe 'GET edit' do
it 'sets the badge API endpoint' do
sign_in(user)
project.add_maintainer(user)
get :edit,
namespace_id: project.namespace.path,
id: project.path
expect(assigns(:badge_api_endpoint)).not_to be_nil
end
end
describe "#update" do describe "#update" do
render_views render_views
......
...@@ -14,7 +14,7 @@ describe 'Group Badges' do ...@@ -14,7 +14,7 @@ describe 'Group Badges' do
group.add_owner(user) group.add_owner(user)
sign_in(user) sign_in(user)
visit(group_settings_badges_path(group)) visit(edit_group_path(group))
end end
it 'shows a list of badges', :js do it 'shows a list of badges', :js do
......
...@@ -15,7 +15,7 @@ describe 'Project Badges' do ...@@ -15,7 +15,7 @@ describe 'Project Badges' do
group.add_maintainer(user) group.add_maintainer(user)
sign_in(user) sign_in(user)
visit(project_settings_badges_path(project)) visit(edit_project_path(project))
end end
it 'shows a list of badges', :js do it 'shows a list of badges', :js do
......
import Vue from 'vue'; import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import store from '~/badges/store'; import store from '~/badges/store';
import createEmptyBadge from '~/badges/empty_badge';
import BadgeForm from '~/badges/components/badge_form.vue'; import BadgeForm from '~/badges/components/badge_form.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createDummyBadge } from '../dummy_badge'; import { DUMMY_IMAGE_URL, TEST_HOST } from '../../test_constants';
// avoid preview background process
BadgeForm.methods.debouncedPreview = () => {};
describe('BadgeForm component', () => { describe('BadgeForm component', () => {
const Component = Vue.extend(BadgeForm); const Component = Vue.extend(BadgeForm);
let axiosMock;
let vm; let vm;
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<div id="dummy-element"></div> <div id="dummy-element"></div>
`); `);
axiosMock = new MockAdapter(axios);
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
axiosMock.restore();
}); });
describe('methods', () => { describe('methods', () => {
...@@ -38,93 +48,86 @@ describe('BadgeForm component', () => { ...@@ -38,93 +48,86 @@ describe('BadgeForm component', () => {
expect(vm.stopEditing).toHaveBeenCalled(); expect(vm.stopEditing).toHaveBeenCalled();
}); });
}); });
});
describe('onSubmit', () => { const sharedSubmitTests = submitAction => {
describe('if isEditing is true', () => { const imageUrlSelector = '#badge-image-url';
beforeEach(() => { const findImageUrlElement = () => vm.$el.querySelector(imageUrlSelector);
spyOn(vm, 'saveBadge').and.returnValue(Promise.resolve()); const linkUrlSelector = '#badge-link-url';
store.replaceState({ const findLinkUrlElement = () => vm.$el.querySelector(linkUrlSelector);
...store.state, const setValue = (inputElementSelector, url) => {
isSaving: false, const inputElement = vm.$el.querySelector(inputElementSelector);
badgeInEditForm: createDummyBadge(), inputElement.value = url;
}); inputElement.dispatchEvent(new Event('input'));
vm.isEditing = true; };
}); const submitForm = () => {
const submitButton = vm.$el.querySelector('button[type="submit"]');
it('returns immediately if imageUrl is empty', () => { submitButton.click();
store.state.badgeInEditForm.imageUrl = ''; };
const expectInvalidInput = inputElementSelector => {
vm.onSubmit(); const inputElement = vm.$el.querySelector(inputElementSelector);
expect(inputElement).toBeMatchedBy(':invalid');
expect(vm.saveBadge).not.toHaveBeenCalled(); const feedbackElement = vm.$el.querySelector(`${inputElementSelector} + .invalid-feedback`);
}); expect(feedbackElement).toBeVisible();
};
it('returns immediately if linkUrl is empty', () => {
store.state.badgeInEditForm.linkUrl = '';
vm.onSubmit();
expect(vm.saveBadge).not.toHaveBeenCalled();
});
it('returns immediately if isSaving is true', () => {
store.state.isSaving = true;
vm.onSubmit(); beforeEach(() => {
spyOn(vm, submitAction).and.returnValue(Promise.resolve());
store.replaceState({
...store.state,
badgeInAddForm: createEmptyBadge(),
badgeInEditForm: createEmptyBadge(),
isSaving: false,
});
expect(vm.saveBadge).not.toHaveBeenCalled(); setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
}); setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
});
it('calls saveBadge', () => { it('returns immediately if imageUrl is empty', () => {
vm.onSubmit(); setValue(imageUrlSelector, '');
expect(vm.saveBadge).toHaveBeenCalled(); submitForm();
});
});
describe('if isEditing is false', () => { expectInvalidInput(imageUrlSelector);
beforeEach(() => { expect(vm[submitAction]).not.toHaveBeenCalled();
spyOn(vm, 'addBadge').and.returnValue(Promise.resolve()); });
store.replaceState({
...store.state,
isSaving: false,
badgeInAddForm: createDummyBadge(),
});
vm.isEditing = false;
});
it('returns immediately if imageUrl is empty', () => { it('returns immediately if imageUrl is malformed', () => {
store.state.badgeInAddForm.imageUrl = ''; setValue(imageUrlSelector, 'not-a-url');
vm.onSubmit(); submitForm();
expect(vm.addBadge).not.toHaveBeenCalled(); expectInvalidInput(imageUrlSelector);
}); expect(vm[submitAction]).not.toHaveBeenCalled();
});
it('returns immediately if linkUrl is empty', () => { it('returns immediately if linkUrl is empty', () => {
store.state.badgeInAddForm.linkUrl = ''; setValue(linkUrlSelector, '');
vm.onSubmit(); submitForm();
expect(vm.addBadge).not.toHaveBeenCalled(); expectInvalidInput(linkUrlSelector);
}); expect(vm[submitAction]).not.toHaveBeenCalled();
});
it('returns immediately if isSaving is true', () => { it('returns immediately if linkUrl is malformed', () => {
store.state.isSaving = true; setValue(linkUrlSelector, 'not-a-url');
vm.onSubmit(); submitForm();
expect(vm.addBadge).not.toHaveBeenCalled(); expectInvalidInput(linkUrlSelector);
}); expect(vm[submitAction]).not.toHaveBeenCalled();
});
it('calls addBadge', () => { it(`calls ${submitAction}`, () => {
vm.onSubmit(); submitForm();
expect(vm.addBadge).toHaveBeenCalled(); expect(findImageUrlElement()).toBeMatchedBy(':valid');
}); expect(findLinkUrlElement()).toBeMatchedBy(':valid');
}); expect(vm[submitAction]).toHaveBeenCalled();
}); });
}); };
describe('if isEditing is false', () => { describe('if isEditing is false', () => {
beforeEach(() => { beforeEach(() => {
...@@ -138,12 +141,15 @@ describe('BadgeForm component', () => { ...@@ -138,12 +141,15 @@ describe('BadgeForm component', () => {
}); });
it('renders one button', () => { it('renders one button', () => {
const buttons = vm.$el.querySelectorAll('.row-content-block button'); expect(vm.$el.querySelector('.row-content-block')).toBeNull();
const buttons = vm.$el.querySelectorAll('.form-group:last-of-type button');
expect(buttons.length).toBe(1); expect(buttons.length).toBe(1);
const buttonAddElement = buttons[0]; const buttonAddElement = buttons[0];
expect(buttonAddElement).toBeVisible(); expect(buttonAddElement).toBeVisible();
expect(buttonAddElement).toHaveText('Add badge'); expect(buttonAddElement).toHaveText('Add badge');
}); });
sharedSubmitTests('addBadge');
}); });
describe('if isEditing is true', () => { describe('if isEditing is true', () => {
...@@ -167,5 +173,7 @@ describe('BadgeForm component', () => { ...@@ -167,5 +173,7 @@ describe('BadgeForm component', () => {
expect(buttonCancelElement).toBeVisible(); expect(buttonCancelElement).toBeVisible();
expect(buttonCancelElement).toHaveText('Cancel'); expect(buttonCancelElement).toHaveText('Cancel');
}); });
sharedSubmitTests('saveBadge');
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment