Skip to content
Snippets Groups Projects
Unverified Commit b7f76383 authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett
Browse files

Improve project settings page

Prioritize and simplify project settings content.
parent dc884879
No related branches found
No related tags found
No related merge requests found
Showing
with 2664 additions and 364 deletions
import _ from 'underscore';
import $ from 'jquery';
 
class DirtySubmitForm {
constructor(form) {
Loading
Loading
@@ -26,6 +27,7 @@ class DirtySubmitForm {
);
this.form.addEventListener('input', throttledUpdateDirtyInput);
this.form.addEventListener('change', throttledUpdateDirtyInput);
$(this.form).on('change.select2', throttledUpdateDirtyInput);
this.form.addEventListener('submit', event => this.formSubmit(event));
}
 
Loading
Loading
Loading
Loading
@@ -3,17 +3,24 @@ import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initAvatarPicker from '~/avatar_picker';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
 
document.addEventListener('DOMContentLoaded', () => {
initProjectLoadingSpinner();
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
initAvatarPicker();
initProjectPermissionsSettings();
initConfirmDangerModal();
initSettingsPanels();
mountBadgeSettings(PROJECT_BADGE);
initProjectLoadingSpinner();
initProjectPermissionsSettings();
setupProjectEdit();
dirtySubmitFactory(
document.querySelectorAll(
'.js-general-settings-form, .js-mr-settings-form, .js-add-approver-form',
),
);
});
Loading
Loading
@@ -157,6 +157,10 @@ label {
padding-left: 10px;
padding-right: 10px;
appearance: none;
/* stylelint-disable property-no-vendor-prefix */
-webkit-appearance: none;
-moz-appearance: none;
/* stylelint-enable property-no-vendor-prefix */
 
&::-ms-expand {
display: none;
Loading
Loading
Loading
Loading
@@ -39,7 +39,7 @@
 
.settings-header {
position: relative;
padding: 20px 110px 10px 0;
padding: 20px 110px 0 0;
 
h4 {
margin-top: 0;
Loading
Loading
- if ::Gitlab::ExternalAuthorization.enabled?
.form-group
= f.label :external_authorization_classification_label, class: 'label-bold' do
= s_('ExternalAuthorizationService|Classification Label')
%span.light (optional)
.form-group.col-md-9
= f.label :external_authorization_classification_label, _('Classification Label (optional)'), class: 'label-bold'
= f.text_field :external_authorization_classification_label, class: "form-control"
%span.form-text.text-muted
= external_classification_label_help_message
- return unless Gitlab::CurrentSettings.project_export_enabled?
 
- project = local_assigns.fetch(:project)
- expanded = Rails.env.test?
 
%section.settings.no-animate#js-export-project{ class: ('expanded' if expanded) }
.settings-header
%h4
Export project
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
.settings-content
.bs-callout.bs-callout-info
%p.append-bottom-0
%p
The following items will be exported:
%ul
%li Project and wiki repositories
%li Project uploads
%li Project configuration, including services
%li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
%li LFS objects
%p
The following items will NOT be exported:
%ul
%li Job traces and artifacts
%li Container registry images
%li CI variables
%li Webhooks
%li Any encrypted tokens
%p
Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_status == :finished
= link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
= link_to 'Generate new export', generate_new_export_project_path(project),
method: :post, class: "btn btn-default"
- else
= link_to 'Export project', export_project_path(project),
method: :post, class: "btn btn-default"
.sub-section
%h4= _('Export project')
%p= _('Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.')
.bs-callout.bs-callout-info
%p.append-bottom-0
%p= _('The following items will be exported:')
%ul
%li= _('Project and wiki repositories')
%li= _('Project uploads')
%li= _('Project configuration, including services')
%li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities')
%li= _('LFS objects')
%p= _('The following items will NOT be exported:')
%ul
%li= _('Job traces and artifacts')
%li= _('Container registry images')
%li= _('CI variables')
%li= _('Webhooks')
%li= _('Any encrypted tokens')
%p= _('Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.')
- if project.export_status == :finished
= link_to _('Download export'), download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
= link_to _('Generate new export'), generate_new_export_project_path(project),
method: :post, class: "btn btn-default"
- else
= link_to _('Export project'), export_project_path(project),
method: :post, class: "btn btn-default"
This diff is collapsed.
- if @project.feature_available?(:issuable_default_templates)
- expanded = Rails.env.test?
%section.settings.issues-feature.no-animate#js-issue-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:issues_access_level) == 0)] }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Default issue template')
%button.btn.js-settings-toggle= expanded ? _('Collapse') : _('Expand')
%p= _('Set a default template for issue descriptions.')
.settings-content
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "issue-settings-form" }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-issue-settings' }
.row
.form-group.col-md-9
= f.label :issues_template, class: 'label-bold' do
= _('Default description template for issues')
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'setting-a-default-template-for-issues-and-merge-requests'), target: '_blank'
= f.text_area :issues_template, class: "form-control", rows: 3
.text-secondary
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/markdown') }
= _('Description parsed with %{link_start}GitLab Flavored Markdown%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
= f.submit _('Save changes'), class: "btn btn-success"
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project js-general-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-general-settings' }
= form_errors(@project)
%fieldset
.row
.form-group.col-md-5
= f.label :name, class: 'label-bold', for: 'project_name_edit' do
= _('Project name')
= f.text_field :name, class: "form-control", id: "project_name_edit"
.form-group.col-md-7
= f.label :id, class: 'label-bold' do
= _('Project ID')
= f.text_field :id, class: 'form-control w-auto', readonly: true
.row
.form-group.col-md-9
= f.label :tag_list, _('Topics'), class: 'label-bold'
= f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
%p.form-text.text-muted= _('Separate topics with commas.')
.row
.form-group.col-md-9
= f.label :description, _('Project description (optional)'), class: 'label-bold'
= f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
.row= render_if_exists 'projects/classification_policy_settings', f: f
.row= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
.form-group.prepend-top-default.append-bottom-20
.avatar-container.s90
= project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
= f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
= render 'shared/choose_avatar_button', f: f
- if @project.avatar?
%hr
= link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
= f.submit _('Save changes'), class: "btn btn-success mt-4 qa-save-naming-topics-avatar-button"
Loading
Loading
@@ -2,8 +2,7 @@
.modal-dialog
.modal-content
.modal-header
%h3.page-title
Confirmation required
%h3.page-title= _('Confirmation required')
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
 
Loading
Loading
@@ -11,8 +10,7 @@
%p.text-danger.js-confirm-text
 
%p
This action can lead to data loss.
To prevent accidental actions we ask you to confirm your intention.
%span.js-warning-text= _('This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.')
%br
Please type
%code.js-confirm-danger-match= phrase
Loading
Loading
@@ -21,4 +19,4 @@
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
.form-actions
= submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
= submit_tag _('Confirm'), class: "btn btn-danger js-confirm-danger-submit"
This diff is collapsed.
Loading
Loading
@@ -9,24 +9,33 @@ describe 'Projects > Settings > User renames a project' do
visit edit_project_path(project)
end
 
def rename_project(project, name: nil, path: nil)
fill_in('project_name', with: name) if name
fill_in('Path', with: path) if path
click_button('Rename project')
def change_path(project, path)
within('.advanced-settings') do
fill_in('Path', with: path)
click_button('Change path')
end
project.reload
wait_for_edit_project_page_reload
end
def change_name(project, name)
within('.general-settings') do
fill_in('Project name', with: name)
click_button('Save changes')
end
project.reload
wait_for_edit_project_page_reload
end
 
def wait_for_edit_project_page_reload
expect(find('.project-edit-container')).to have_content('Rename repository')
expect(find('.advanced-settings')).to have_content('Change path')
end
 
context 'with invalid characters' do
it 'shows errors for invalid project path/name' do
rename_project(project, name: 'foo&bar', path: 'foo&bar')
expect(page).to have_field 'Project name', with: 'foo&bar'
it 'shows errors for invalid project path' do
change_path(project, 'foo&bar')
expect(page).to have_field 'Path', with: 'foo&bar'
expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
end
end
Loading
Loading
@@ -42,13 +51,13 @@ describe 'Projects > Settings > User renames a project' do
 
context 'when changing project name' do
it 'renames the repository' do
rename_project(project, name: 'bar')
change_name(project, 'bar')
expect(find('.breadcrumbs')).to have_content(project.name)
end
 
context 'with emojis' do
it 'shows error for invalid project name' do
rename_project(project, name: '🚀 foo bar ☁️')
change_name(project, '🚀 foo bar ☁️')
expect(page).to have_field 'Project name', with: '🚀 foo bar ☁️'
expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'."
end
Loading
Loading
@@ -67,7 +76,7 @@ describe 'Projects > Settings > User renames a project' do
end
 
it 'the project is accessible via the new path' do
rename_project(project, path: 'bar')
change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit new_path
 
Loading
Loading
@@ -77,7 +86,7 @@ describe 'Projects > Settings > User renames a project' do
 
it 'the project is accessible via a redirect from the old path' do
old_path = project_path(project)
rename_project(project, path: 'bar')
change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit old_path
 
Loading
Loading
@@ -88,7 +97,7 @@ describe 'Projects > Settings > User renames a project' do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
old_path = project_path(project)
rename_project(project, path: 'bar')
change_path(project, 'bar')
new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
 
Loading
Loading
Loading
Loading
@@ -373,6 +373,21 @@ describe 'Project' do
end
end
 
describe 'edit' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:path) { edit_project_path(project) }
before do
project.add_maintainer(user)
sign_in(user)
visit path
end
it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
{ form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
end
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
Loading
Loading
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
 
def expect_disabled_state(form, submit, is_disabled = true)
def expect_disabled_state(form, submit_selector, is_disabled = true)
disabled_selector = is_disabled == true ? '[disabled]' : ':not([disabled])'
 
form.find(".js-dirty-submit#{disabled_selector}", match: :first)
expect(submit.disabled?).to be is_disabled
form.find("#{submit_selector}#{disabled_selector}")
end
 
selectors.each do |selector|
it "disables #{selector[:form]} submit until there are changes on #{selector[:input]}", :js do
form = find(selector[:form])
submit = form.first('.js-dirty-submit')
submit_selector = selector[:submit] || 'input[type="submit"]'
submit = form.first(submit_selector)
input = form.first(selector[:input])
is_radio = input[:type] == 'radio'
is_checkbox = input[:type] == 'checkbox'
Loading
Loading
@@ -22,15 +21,14 @@ shared_examples 'dirty submit form' do |selector_args|
original_checkable = input if is_checkbox
 
expect(submit.disabled?).to be true
expect(input.checked?).to be false
 
is_checkable ? input.click : input.set("#{original_value} changes")
 
expect_disabled_state(form, submit, false)
expect_disabled_state(form, submit_selector, false)
 
is_checkable ? original_checkable.click : input.set(original_value)
 
expect_disabled_state(form, submit)
expect_disabled_state(form, submit_selector)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment