Merge branch '24032-changed-visibility-level-to-public-but-project-is-not-public' into 'master'
Update project visibility level ui to use dropdowns Closes #24296, #24123, and #24032 See merge request !7645
This commit is contained in:
commit
97475d0b8d
|
@ -14,11 +14,21 @@
|
||||||
return $('.save-project-loader').show();
|
return $('.save-project-loader').show();
|
||||||
};
|
};
|
||||||
})(this));
|
})(this));
|
||||||
|
|
||||||
|
this.initVisibilitySelect();
|
||||||
|
|
||||||
this.toggleSettings();
|
this.toggleSettings();
|
||||||
this.toggleSettingsOnclick();
|
this.toggleSettingsOnclick();
|
||||||
this.toggleRepoVisibility();
|
this.toggleRepoVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProjectNew.prototype.initVisibilitySelect = function() {
|
||||||
|
const visibilityContainer = document.querySelector('.js-visibility-select');
|
||||||
|
if (!visibilityContainer) return;
|
||||||
|
const visibilitySelect = new gl.VisibilitySelect(visibilityContainer);
|
||||||
|
visibilitySelect.init();
|
||||||
|
};
|
||||||
|
|
||||||
ProjectNew.prototype.toggleSettings = function() {
|
ProjectNew.prototype.toggleSettings = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
(() => {
|
||||||
|
const gl = window.gl || (window.gl = {});
|
||||||
|
|
||||||
|
class VisibilitySelect {
|
||||||
|
constructor(container) {
|
||||||
|
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
|
||||||
|
this.container = container;
|
||||||
|
this.helpBlock = this.container.querySelector('.help-block');
|
||||||
|
this.select = this.container.querySelector('select');
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.select) {
|
||||||
|
this.updateHelpText();
|
||||||
|
this.select.addEventListener('change', this.updateHelpText.bind(this));
|
||||||
|
} else {
|
||||||
|
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHelpText() {
|
||||||
|
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.VisibilitySelect = VisibilitySelect;
|
||||||
|
})();
|
|
@ -9,9 +9,10 @@
|
||||||
.new_project,
|
.new_project,
|
||||||
.edit-project {
|
.edit-project {
|
||||||
|
|
||||||
fieldset {
|
.sharing-and-permissions {
|
||||||
|
.header {
|
||||||
&.features {
|
padding-top: $gl-vert-padding;
|
||||||
|
}
|
||||||
|
|
||||||
.label-light {
|
.label-light {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -20,7 +21,6 @@
|
||||||
.help-block {
|
.help-block {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
@ -905,10 +905,18 @@ pre.light-well {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-feature-nested {
|
.project-feature {
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
@media (min-width: $screen-sm-min) {
|
@media (min-width: $screen-sm-min) {
|
||||||
padding-left: 45px;
|
padding-left: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.nested {
|
||||||
|
@media (min-width: $screen-sm-min) {
|
||||||
|
padding-left: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-repo-select {
|
.project-repo-select {
|
||||||
|
|
|
@ -409,4 +409,15 @@ module ProjectsHelper
|
||||||
def project_issues(project)
|
def project_issues(project)
|
||||||
IssuesFinder.new(current_user, project_id: project.id).execute
|
IssuesFinder.new(current_user, project_id: project.id).execute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def visibility_select_options(project, selected_level)
|
||||||
|
levels_options_array = Gitlab::VisibilityLevel.values.map do |level|
|
||||||
|
[
|
||||||
|
visibility_level_label(level),
|
||||||
|
{ data: { description: visibility_level_description(level, project) } },
|
||||||
|
level
|
||||||
|
]
|
||||||
|
end
|
||||||
|
options_for_select(levels_options_array, selected_level)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
- if can_change_visibility_level?(@project, current_user)
|
||||||
|
= form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select')
|
||||||
|
- else
|
||||||
|
.info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
|
||||||
|
= visibility_level_icon(@project.visibility_level)
|
||||||
|
%strong
|
||||||
|
= visibility_level_label(@project.visibility_level)
|
|
@ -21,76 +21,68 @@
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :default_branch, "Default Branch", class: 'label-light'
|
= f.label :default_branch, "Default Branch", class: 'label-light'
|
||||||
= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
|
= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
|
||||||
.form-group.project-visibility-level-holder
|
|
||||||
= f.label :visibility_level, class: 'label-light' do
|
|
||||||
Visibility Level
|
|
||||||
= link_to "(?)", help_page_path("public_access/public_access")
|
|
||||||
- if can_change_visibility_level?(@project, current_user)
|
|
||||||
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
|
|
||||||
- else
|
|
||||||
.info
|
|
||||||
= visibility_level_icon(@project.visibility_level)
|
|
||||||
%strong
|
|
||||||
= visibility_level_label(@project.visibility_level)
|
|
||||||
.light= visibility_level_description(@project.visibility_level, @project)
|
|
||||||
|
|
||||||
.form-group
|
|
||||||
= render 'shared/allow_request_access', form: f
|
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :tag_list, "Tags", class: 'label-light'
|
= f.label :tag_list, "Tags", class: 'label-light'
|
||||||
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
|
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
|
||||||
%p.help-block Separate tags with commas.
|
%p.help-block Separate tags with commas.
|
||||||
%hr
|
%hr
|
||||||
%fieldset.features.append-bottom-0
|
%fieldset.append-bottom-0
|
||||||
%h5.prepend-top-0
|
%h5.prepend-top-0
|
||||||
Feature Visibility
|
Sharing & Permissions
|
||||||
|
.form_group.prepend-top-20.sharing-and-permissions
|
||||||
= f.fields_for :project_feature do |feature_fields|
|
.row.js-visibility-select
|
||||||
.form_group.prepend-top-20
|
|
||||||
.row
|
|
||||||
.col-md-9
|
.col-md-9
|
||||||
|
%label.label-light
|
||||||
|
= label_tag :project_visibility, 'Project Visibility', class: 'label-light'
|
||||||
|
= link_to "(?)", help_page_path("public_access/public_access")
|
||||||
|
%span.help-block
|
||||||
|
.col-md-3.visibility-select-container
|
||||||
|
= render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level)
|
||||||
|
= f.fields_for :project_feature do |feature_fields|
|
||||||
|
%fieldset.features
|
||||||
|
.row
|
||||||
|
.col-md-9.project-feature
|
||||||
= feature_fields.label :repository_access_level, "Repository", class: 'label-light'
|
= feature_fields.label :repository_access_level, "Repository", class: 'label-light'
|
||||||
%span.help-block Push files to be stored in this project
|
%span.help-block View and edit files in this project
|
||||||
.col-md-3.js-repo-access-level
|
.col-md-3.js-repo-access-level
|
||||||
= project_feature_access_select(:repository_access_level)
|
= project_feature_access_select(:repository_access_level)
|
||||||
|
|
||||||
.col-sm-12
|
|
||||||
.row
|
.row
|
||||||
.col-md-9.project-feature-nested
|
.col-md-9.project-feature.nested
|
||||||
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
|
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
|
||||||
%span.help-block Submit changes to be merged upstream
|
%span.help-block Submit changes to be merged upstream
|
||||||
.col-md-3
|
.col-md-3
|
||||||
= project_feature_access_select(:merge_requests_access_level)
|
= project_feature_access_select(:merge_requests_access_level)
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-md-9.project-feature-nested
|
.col-md-9.project-feature.nested
|
||||||
= feature_fields.label :builds_access_level, "Builds", class: 'label-light'
|
= feature_fields.label :builds_access_level, "Builds", class: 'label-light'
|
||||||
%span.help-block Submit, test and deploy your changes before merge
|
%span.help-block Submit, test and deploy your changes before merge
|
||||||
.col-md-3
|
.col-md-3
|
||||||
= project_feature_access_select(:builds_access_level)
|
= project_feature_access_select(:builds_access_level)
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-md-9
|
.col-md-9.project-feature
|
||||||
= feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
|
= feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
|
||||||
%span.help-block Share code pastes with others out of Git repository
|
%span.help-block Share code pastes with others out of Git repository
|
||||||
.col-md-3
|
.col-md-3
|
||||||
= project_feature_access_select(:snippets_access_level)
|
= project_feature_access_select(:snippets_access_level)
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-md-9
|
.col-md-9.project-feature
|
||||||
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
|
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
|
||||||
%span.help-block Lightweight issue tracking system for this project
|
%span.help-block Lightweight issue tracking system for this project
|
||||||
.col-md-3
|
.col-md-3
|
||||||
= project_feature_access_select(:issues_access_level)
|
= project_feature_access_select(:issues_access_level)
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-md-9
|
.col-md-9.project-feature
|
||||||
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
|
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
|
||||||
%span.help-block Pages for project documentation
|
%span.help-block Pages for project documentation
|
||||||
.col-md-3
|
.col-md-3
|
||||||
= project_feature_access_select(:wiki_access_level)
|
= project_feature_access_select(:wiki_access_level)
|
||||||
|
.form-group
|
||||||
|
= render 'shared/allow_request_access', form: f
|
||||||
- if Gitlab.config.lfs.enabled && current_user.admin?
|
- if Gitlab.config.lfs.enabled && current_user.admin?
|
||||||
.row
|
.row
|
||||||
.col-md-9
|
.col-md-9
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Updated project visibility settings UX
|
||||||
|
merge_request: 7645
|
||||||
|
author:
|
|
@ -97,7 +97,7 @@ module SharedProject
|
||||||
step 'I should see project settings' do
|
step 'I should see project settings' do
|
||||||
expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
|
expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
|
||||||
expect(page).to have_content("Project name")
|
expect(page).to have_content("Project name")
|
||||||
expect(page).to have_content("Feature Visibility")
|
expect(page).to have_content("Sharing & Permissions")
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_project
|
def current_project
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'Visibility settings', feature: true, js: true do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) }
|
||||||
|
|
||||||
|
context 'as owner' do
|
||||||
|
before do
|
||||||
|
login_as(user)
|
||||||
|
visit edit_namespace_project_path(project.namespace, project)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'project visibility select is available' do
|
||||||
|
visibility_select_container = find('.js-visibility-select')
|
||||||
|
|
||||||
|
expect(visibility_select_container.find('.visibility-select').value).to eq project.visibility_level.to_s
|
||||||
|
expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.'
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'project visibility description updates on change' do
|
||||||
|
visibility_select_container = find('.js-visibility-select')
|
||||||
|
visibility_select = visibility_select_container.find('.visibility-select')
|
||||||
|
visibility_select.select('Private')
|
||||||
|
|
||||||
|
expect(visibility_select.value).to eq '0'
|
||||||
|
expect(visibility_select_container).to have_content 'Project access must be granted explicitly to each user.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as master' do
|
||||||
|
let(:master_user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.team << [master_user, :master]
|
||||||
|
login_as(master_user)
|
||||||
|
visit edit_namespace_project_path(project.namespace, project)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'project visibility is locked' do
|
||||||
|
visibility_select_container = find('.js-visibility-select')
|
||||||
|
|
||||||
|
expect(visibility_select_container).not_to have_select '.visibility-select'
|
||||||
|
expect(visibility_select_container).to have_content 'Public'
|
||||||
|
expect(visibility_select_container).to have_content 'The project can be cloned without any authentication.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*= require visibility_select */
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const VisibilitySelect = gl.VisibilitySelect;
|
||||||
|
|
||||||
|
describe('VisibilitySelect', function () {
|
||||||
|
const lockedElement = document.createElement('div');
|
||||||
|
lockedElement.dataset.helpBlock = 'lockedHelpBlock';
|
||||||
|
|
||||||
|
const checkedElement = document.createElement('div');
|
||||||
|
checkedElement.dataset.description = 'checkedDescription';
|
||||||
|
|
||||||
|
const mockElements = {
|
||||||
|
container: document.createElement('div'),
|
||||||
|
select: document.createElement('div'),
|
||||||
|
'.help-block': document.createElement('div'),
|
||||||
|
'.js-locked': lockedElement,
|
||||||
|
'option:checked': checkedElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
spyOn(Element.prototype, 'querySelector').and.callFake(selector => mockElements[selector]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#constructor', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.visibilitySelect = new VisibilitySelect(mockElements.container);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the container member', function () {
|
||||||
|
expect(this.visibilitySelect.container).toEqual(mockElements.container);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queries and sets the helpBlock member', function () {
|
||||||
|
expect(Element.prototype.querySelector).toHaveBeenCalledWith('.help-block');
|
||||||
|
expect(this.visibilitySelect.helpBlock).toEqual(mockElements['.help-block']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queries and sets the select member', function () {
|
||||||
|
expect(Element.prototype.querySelector).toHaveBeenCalledWith('select');
|
||||||
|
expect(this.visibilitySelect.select).toEqual(mockElements.select);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if there is no container element provided', function () {
|
||||||
|
it('throws an error', function () {
|
||||||
|
expect(() => new VisibilitySelect()).toThrowError('VisibilitySelect requires a container element as argument 1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#init', function () {
|
||||||
|
describe('if there is a select', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.visibilitySelect = new VisibilitySelect(mockElements.container);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls updateHelpText', function () {
|
||||||
|
spyOn(VisibilitySelect.prototype, 'updateHelpText');
|
||||||
|
this.visibilitySelect.init();
|
||||||
|
expect(this.visibilitySelect.updateHelpText).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a change event listener', function () {
|
||||||
|
spyOn(this.visibilitySelect.select, 'addEventListener');
|
||||||
|
this.visibilitySelect.init();
|
||||||
|
expect(this.visibilitySelect.select.addEventListener.calls.argsFor(0)).toContain('change');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if there is no select', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
mockElements.select = undefined;
|
||||||
|
this.visibilitySelect = new VisibilitySelect(mockElements.container);
|
||||||
|
this.visibilitySelect.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the helpBlock text to the locked `data-help-block` messaged', function () {
|
||||||
|
expect(this.visibilitySelect.helpBlock.textContent)
|
||||||
|
.toEqual(lockedElement.dataset.helpBlock);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
mockElements.select = document.createElement('div');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#updateHelpText', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.visibilitySelect = new VisibilitySelect(mockElements.container);
|
||||||
|
this.visibilitySelect.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the helpBlock text to the selected options `data-description`', function () {
|
||||||
|
expect(this.visibilitySelect.helpBlock.textContent)
|
||||||
|
.toEqual(checkedElement.dataset.description);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
Loading…
Reference in New Issue