Skip to content
Snippets Groups Projects
Commit 03b14b48 authored by Mike Greiling's avatar Mike Greiling Committed by Phil Hughes
Browse files

Resolve "Make project and features visibility settings less confusing"

parent 21b16c5a
No related branches found
No related tags found
No related merge requests found
Showing
with 815 additions and 155 deletions
Loading
Loading
@@ -577,6 +577,9 @@ import initChangesDropdown from './init_changes_dropdown';
case 'edit':
shortcut_handler = new ShortcutsNavigation();
new ProjectNew();
import(/* webpackChunkName: 'project_permissions' */ './projects/permissions')
.then(permissions => permissions.default())
.catch(() => {});
break;
case 'new':
new ProjectNew();
Loading
Loading
<script>
import projectFeatureToggle from './project_feature_toggle.vue';
export default {
props: {
name: {
type: String,
required: false,
default: '',
},
options: {
type: Array,
required: false,
default: () => [],
},
value: {
type: Number,
required: false,
default: 0,
},
disabledInput: {
type: Boolean,
required: false,
default: false,
},
},
components: {
projectFeatureToggle,
},
computed: {
featureEnabled() {
return this.value !== 0;
},
displayOptions() {
if (this.featureEnabled) {
return this.options;
}
return [
[0, 'Enable feature to choose access level'],
];
},
displaySelectInput() {
return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2;
},
},
model: {
prop: 'value',
event: 'change',
},
methods: {
toggleFeature(featureEnabled) {
if (featureEnabled === false || this.options.length < 1) {
this.$emit('change', 0);
} else {
const [firstOptionValue] = this.options[this.options.length - 1];
this.$emit('change', firstOptionValue);
}
},
selectOption(e) {
this.$emit('change', Number(e.target.value));
},
},
};
</script>
<template>
<div class="project-feature-controls" :data-for="name">
<input
v-if="name"
type="hidden"
:name="name"
:value="value"
/>
<project-feature-toggle
:value="featureEnabled"
@change="toggleFeature"
:disabledInput="disabledInput"
/>
<div class="select-wrapper">
<select
class="form-control project-repo-select select-control"
@change="selectOption"
:disabled="displaySelectInput"
>
<option
v-for="[optionValue, optionName] in displayOptions"
:key="optionValue"
:value="optionValue"
:selected="optionValue === value"
>
{{optionName}}
</option>
</select>
<i aria-hidden="true" class="fa fa-chevron-down"></i>
</div>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
required: false,
default: '',
},
value: {
type: Boolean,
required: true,
},
disabledInput: {
type: Boolean,
required: false,
default: false,
},
},
model: {
prop: 'value',
event: 'change',
},
methods: {
toggleFeature() {
if (!this.disabledInput) this.$emit('change', !this.value);
},
},
};
</script>
<template>
<label class="toggle-wrapper">
<input
v-if="name"
type="hidden"
:name="name"
:value="value"
/>
<button
type="button"
aria-label="Toggle"
class="project-feature-toggle"
data-enabled-text="Enabled"
data-disabled-text="Disabled"
:class="{ checked: value, disabled: disabledInput }"
@click="toggleFeature"
/>
</label>
</template>
<script>
export default {
props: {
label: {
type: String,
required: false,
default: null,
},
helpPath: {
type: String,
required: false,
default: null,
},
helpText: {
type: String,
required: false,
default: null,
},
},
};
</script>
<template>
<div class="project-feature-row">
<label v-if="label" class="label-light">
{{label}}
<a v-if="helpPath" :href="helpPath" target="_blank">
<i aria-hidden="true" data-hidden="true" class="fa fa-question-circle"></i>
</a>
</label>
<span v-if="helpText" class="help-block">
{{helpText}}
</span>
<slot />
</div>
</template>
<script>
import projectFeatureSetting from './project_feature_setting.vue';
import projectFeatureToggle from './project_feature_toggle.vue';
import projectSettingRow from './project_setting_row.vue';
import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
import { toggleHiddenClassBySelector } from '../external';
export default {
props: {
currentSettings: {
type: Object,
required: true,
},
canChangeVisibilityLevel: {
type: Boolean,
required: false,
default: false,
},
allowedVisibilityOptions: {
type: Array,
required: false,
default: () => [0, 10, 20],
},
lfsAvailable: {
type: Boolean,
required: false,
default: false,
},
registryAvailable: {
type: Boolean,
required: false,
default: false,
},
visibilityHelpPath: {
type: String,
required: false,
},
lfsHelpPath: {
type: String,
required: false,
},
registryHelpPath: {
type: String,
required: false,
},
},
data() {
const defaults = {
visibilityOptions,
visibilityLevel: visibilityOptions.PUBLIC,
issuesAccessLevel: 20,
repositoryAccessLevel: 20,
mergeRequestsAccessLevel: 20,
buildsAccessLevel: 20,
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
requestAccessEnabled: true,
highlightChangesClass: false,
};
return { ...defaults, ...this.currentSettings };
},
components: {
projectFeatureSetting,
projectFeatureToggle,
projectSettingRow,
},
computed: {
featureAccessLevelOptions() {
const options = [
[10, 'Only Project Members'],
];
if (this.visibilityLevel !== visibilityOptions.PRIVATE) {
options.push([20, 'Everyone With Access']);
}
return options;
},
repoFeatureAccessLevelOptions() {
return this.featureAccessLevelOptions.filter(
([value]) => value <= this.repositoryAccessLevel,
);
},
repositoryEnabled() {
return this.repositoryAccessLevel > 0;
},
visibilityLevelDescription() {
return visibilityLevelDescriptions[this.visibilityLevel];
},
},
methods: {
highlightChanges() {
this.highlightChangesClass = true;
this.$nextTick(() => {
this.highlightChangesClass = false;
});
},
visibilityAllowed(option) {
return this.allowedVisibilityOptions.includes(option);
},
},
watch: {
visibilityLevel(value, oldValue) {
if (value === visibilityOptions.PRIVATE) {
// when private, features are restricted to "only team members"
this.issuesAccessLevel = Math.min(10, this.issuesAccessLevel);
this.repositoryAccessLevel = Math.min(10, this.repositoryAccessLevel);
this.mergeRequestsAccessLevel = Math.min(10, this.mergeRequestsAccessLevel);
this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
this.highlightChanges();
} else if (oldValue === visibilityOptions.PRIVATE) {
// if changing away from private, make enabled features more permissive
if (this.issuesAccessLevel > 0) this.issuesAccessLevel = 20;
if (this.repositoryAccessLevel > 0) this.repositoryAccessLevel = 20;
if (this.mergeRequestsAccessLevel > 0) this.mergeRequestsAccessLevel = 20;
if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20;
if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
this.highlightChanges();
}
},
repositoryAccessLevel(value, oldValue) {
if (value < oldValue) {
// sub-features cannot have more premissive access level
this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value);
this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value);
if (value === 0) {
this.containerRegistryEnabled = false;
this.lfsEnabled = false;
}
} else if (oldValue === 0) {
this.mergeRequestsAccessLevel = value;
this.buildsAccessLevel = value;
this.containerRegistryEnabled = true;
this.lfsEnabled = true;
}
},
issuesAccessLevel(value, oldValue) {
if (value === 0) toggleHiddenClassBySelector('.issues-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false);
},
mergeRequestsAccessLevel(value, oldValue) {
if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false);
},
buildsAccessLevel(value, oldValue) {
if (value === 0) toggleHiddenClassBySelector('.builds-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false);
},
},
};
</script>
<template>
<div>
<div class="project-visibility-setting">
<project-setting-row
label="Project visibility"
:help-path="visibilityHelpPath"
>
<div class="project-feature-controls">
<div class="select-wrapper">
<select
name="project[visibility_level]"
v-model="visibilityLevel"
class="form-control select-control"
:disabled="!canChangeVisibilityLevel"
>
<option
:value="visibilityOptions.PRIVATE"
:disabled="!visibilityAllowed(visibilityOptions.PRIVATE)"
>
Private
</option>
<option
:value="visibilityOptions.INTERNAL"
:disabled="!visibilityAllowed(visibilityOptions.INTERNAL)"
>
Internal
</option>
<option
:value="visibilityOptions.PUBLIC"
:disabled="!visibilityAllowed(visibilityOptions.PUBLIC)"
>
Public
</option>
</select>
<i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i>
</div>
</div>
<span class="help-block">{{ visibilityLevelDescription }}</span>
<label v-if="visibilityLevel !== visibilityOptions.PUBLIC" class="request-access">
<input
type="hidden"
name="project[request_access_enabled]"
:value="requestAccessEnabled"
/>
<input type="checkbox" v-model="requestAccessEnabled" />
Allow users to request access
</label>
</project-setting-row>
</div>
<div class="project-feature-settings" :class="{ 'highlight-changes': highlightChangesClass }">
<project-setting-row
label="Issues"
help-text="Lightweight issue tracking system for this project"
>
<project-feature-setting
name="project[project_feature_attributes][issues_access_level]"
:options="featureAccessLevelOptions"
v-model="issuesAccessLevel"
/>
</project-setting-row>
<project-setting-row
label="Repository"
help-text="View and edit files in this project"
>
<project-feature-setting
name="project[project_feature_attributes][repository_access_level]"
:options="featureAccessLevelOptions"
v-model="repositoryAccessLevel"
/>
</project-setting-row>
<div class="project-feature-setting-group">
<project-setting-row
label="Merge requests"
help-text="Submit changes to be merged upstream"
>
<project-feature-setting
name="project[project_feature_attributes][merge_requests_access_level]"
:options="repoFeatureAccessLevelOptions"
v-model="mergeRequestsAccessLevel"
:disabledInput="!repositoryEnabled"
/>
</project-setting-row>
<project-setting-row
label="Pipelines"
help-text="Build, test, and deploy your changes"
>
<project-feature-setting
name="project[project_feature_attributes][builds_access_level]"
:options="repoFeatureAccessLevelOptions"
v-model="buildsAccessLevel"
:disabledInput="!repositoryEnabled"
/>
</project-setting-row>
<project-setting-row
v-if="registryAvailable"
label="Container registry"
:help-path="registryHelpPath"
help-text="Every project can have its own space to store its Docker images"
>
<project-feature-toggle
name="project[container_registry_enabled]"
v-model="containerRegistryEnabled"
:disabledInput="!repositoryEnabled"
/>
</project-setting-row>
<project-setting-row
v-if="lfsAvailable"
label="Git Large File Storage"
:help-path="lfsHelpPath"
help-text="Manages large files such as audio, video, and graphics files"
>
<project-feature-toggle
name="project[lfs_enabled]"
v-model="lfsEnabled"
:disabledInput="!repositoryEnabled"
/>
</project-setting-row>
</div>
<project-setting-row
label="Wiki"
help-text="Pages for project documentation"
>
<project-feature-setting
name="project[project_feature_attributes][wiki_access_level]"
:options="featureAccessLevelOptions"
v-model="wikiAccessLevel"
/>
</project-setting-row>
<project-setting-row
label="Snippets"
help-text="Share code pastes with others out of Git repository"
>
<project-feature-setting
name="project[project_feature_attributes][snippets_access_level]"
:options="featureAccessLevelOptions"
v-model="snippetsAccessLevel"
/>
</project-setting-row>
</div>
</div>
</template>
export const visibilityOptions = {
PRIVATE: 0,
INTERNAL: 10,
PUBLIC: 20,
};
export const visibilityLevelDescriptions = {
[visibilityOptions.PRIVATE]: 'The project is accessible only by members of the project. Access must be granted explicitly to each user.',
[visibilityOptions.INTERNAL]: 'The project can be accessed by any user who is logged in.',
[visibilityOptions.PUBLIC]: 'The project can be accessed by anyone, regardless of authentication.',
};
const selectorCache = [];
// workaround since we don't have a polyfill for classList.toggle 2nd parameter
export function toggleHiddenClass(element, hidden) {
if (hidden) {
element.classList.add('hidden');
} else {
element.classList.remove('hidden');
}
}
// hide external feature-specific settings when a given feature is disabled
export function toggleHiddenClassBySelector(selector, hidden) {
if (!selectorCache[selector]) {
selectorCache[selector] = document.querySelectorAll(selector);
}
selectorCache[selector].forEach(elm => toggleHiddenClass(elm, hidden));
}
import Vue from 'vue';
import settingsPanel from './components/settings_panel.vue';
export default function initProjectPermissionsSettings() {
const mountPoint = document.querySelector('.js-project-permissions-form');
const componentPropsEl = document.querySelector('.js-project-permissions-form-data');
const componentProps = JSON.parse(componentPropsEl.innerHTML);
return new Vue({
el: mountPoint,
render: createElement => createElement(settingsPanel, { props: { ...componentProps } }),
});
}
Loading
Loading
@@ -579,6 +579,11 @@ $project-breadcrumb-color: #999;
$project-private-forks-notice-odd: $green-600;
$project-network-controls-color: #888;
 
$feature-toggle-color: #fff;
$feature-toggle-text-color: #fff;
$feature-toggle-color-disabled: #999;
$feature-toggle-color-enabled: #4a8bee;
/*
* Runners
*/
Loading
Loading
Loading
Loading
@@ -10,41 +10,6 @@
.edit-project,
.import-project {
 
.sharing-and-permissions {
.header {
padding-top: $gl-vert-padding;
}
.label-light {
margin-bottom: 0;
}
.help-block {
margin-top: 0;
}
.form-group {
margin-bottom: 5px;
}
> .form-group {
padding-left: 0;
}
select option[disabled] {
display: none;
}
}
select {
transition: background 2s ease-out;
&.highlight-changes {
background: $highlight-changes-color;
transition: none;
}
}
.help-block {
margin-bottom: 10px;
}
Loading
Loading
@@ -90,6 +55,162 @@
}
}
 
.toggle-wrapper {
margin-top: 5px;
}
.project-feature-row > .toggle-wrapper {
margin: 10px 0;
}
.project-visibility-setting,
.project-feature-settings {
border: 1px solid $border-color;
padding: 10px 32px;
@media (max-width: $screen-xs-min) {
padding: 10px 20px;
}
}
.project-visibility-setting .request-access {
line-height: 2;
}
.project-feature-settings {
background: $gray-lighter;
border-top: none;
margin-bottom: 16px;
}
.project-repo-select {
transition: background 2s ease-out;
&:disabled {
opacity: 0.75;
}
.highlight-changes & {
background: $highlight-changes-color;
transition: none;
}
}
.project-feature-controls {
display: flex;
align-items: center;
margin: 8px 0;
max-width: 432px;
.toggle-wrapper {
flex: 0;
margin-right: 10px;
}
.select-wrapper {
flex: 1;
}
}
.project-feature-setting-group {
padding-left: 32px;
.project-feature-controls {
max-width: 400px;
}
@media (max-width: $screen-xs-min) {
padding-left: 20px;
}
}
.project-feature-toggle {
position: relative;
border: none;
outline: 0;
display: block;
width: 100px;
height: 24px;
cursor: pointer;
user-select: none;
background: $feature-toggle-color-disabled;
border-radius: 12px;
padding: 3px;
transition: all .4s ease;
&::selection,
&::before::selection,
&::after::selection {
background: none;
}
&::before {
color: $feature-toggle-text-color;
font-size: 12px;
line-height: 24px;
position: absolute;
top: 0;
left: 25px;
right: 5px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
animation: animate-disabled .2s ease-in;
content: attr(data-disabled-text);
}
&::after {
position: relative;
display: block;
content: "";
width: 22px;
height: 18px;
left: 0;
border-radius: 9px;
background: $feature-toggle-color;
transition: all .2s ease;
}
&.checked {
background: $feature-toggle-color-enabled;
&::before {
left: 5px;
right: 25px;
animation: animate-enabled .2s ease-in;
content: attr(data-enabled-text);
}
&::after {
left: calc(100% - 22px);
}
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
@media (max-width: $screen-xs-min) {
width: 50px;
&::before,
&.checked::before {
display: none;
}
}
@keyframes animate-enabled {
0%, 35% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes animate-disabled {
0%, 35% { opacity: 0; }
100% { opacity: 1; }
}
}
.project-home-panel,
.group-home-panel {
padding-top: 24px;
Loading
Loading
Loading
Loading
@@ -545,6 +545,43 @@ module ProjectsHelper
current_application_settings.restricted_visibility_levels || []
end
 
def project_permissions_settings(project)
feature = project.project_feature
{
visibilityLevel: project.visibility_level,
requestAccessEnabled: !!project.request_access_enabled,
issuesAccessLevel: feature.issues_access_level,
repositoryAccessLevel: feature.repository_access_level,
mergeRequestsAccessLevel: feature.merge_requests_access_level,
buildsAccessLevel: feature.builds_access_level,
wikiAccessLevel: feature.wiki_access_level,
snippetsAccessLevel: feature.snippets_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled
}
end
def project_permissions_panel_data(project)
data = {
currentSettings: project_permissions_settings(project),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('public_access/public_access'),
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/project/container_registry'),
lfsAvailable: Gitlab.config.lfs.enabled && current_user.admin?,
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
}
data.to_json.html_safe
end
def project_allowed_visibility_levels(project)
Gitlab::VisibilityLevel.values.select do |level|
project.visibility_level_allowed?(level) && !restricted_levels.include?(level)
end
end
def find_file_path
return unless @project && !@project.empty_repo?
 
Loading
Loading
- form = local_assigns.fetch(:form)
 
.form-group
.checkbox.builds-feature
.checkbox.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) }
= form.label :only_allow_merge_if_pipeline_succeeds do
= form.check_box :only_allow_merge_if_pipeline_succeeds
%strong Only allow merge requests to be merged if the pipeline succeeds
Loading
Loading
Loading
Loading
@@ -66,90 +66,18 @@
%section.settings.sharing-permissions
.settings-header
%h4
Sharing and permissions
Permissions
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
Enable or disable certain project features and choose access levels.
.settings-content.no-animate{ class: ('expanded' if expanded) }
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
.form_group.sharing-and-permissions
.row.js-visibility-select
.col-md-8
.label-light
= label_tag :project_visibility, 'Project Visibility', class: 'label-light', for: :project_visibility_level
= link_to icon('question-circle'), help_page_path("public_access/public_access")
%span.help-block
.col-md-4.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-8.project-feature
= feature_fields.label :repository_access_level, "Repository", class: 'label-light'
%span.help-block View and edit files in this project
.col-md-4.js-repo-access-level
= project_feature_access_select(:repository_access_level)
.row
.col-md-8.project-feature.nested
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
%span.help-block Submit changes to be merged upstream
.col-md-4
= project_feature_access_select(:merge_requests_access_level)
.row
.col-md-8.project-feature.nested
= feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
%span.help-block Build, test, and deploy your changes
.col-md-4
= project_feature_access_select(:builds_access_level)
.row
.col-md-8.project-feature
= feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
%span.help-block Share code pastes with others out of Git repository
.col-md-4
= project_feature_access_select(:snippets_access_level)
.row
.col-md-8.project-feature
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project
.col-md-4
= project_feature_access_select(:issues_access_level)
.row
.col-md-8.project-feature
= feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
%span.help-block Pages for project documentation
.col-md-4
= project_feature_access_select(:wiki_access_level)
.form-group
= render 'shared/allow_request_access', form: f
- if Gitlab.config.lfs.enabled && current_user.admin?
.row.js-lfs-enabled.form-group.sharing-and-permissions
.col-md-8
= f.label :lfs_enabled, 'Git Large File Storage', class: 'label-light'
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%span.help-block Manages large files such as audio, video and graphics files.
.col-md-4
.select-wrapper
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control project-repo-select select-control', data: { field: 'lfs_enabled' }
= icon('chevron-down')
- if Gitlab.config.registry.enabled
.form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) }
.checkbox
= f.label :container_registry_enabled do
= f.check_box :container_registry_enabled
%strong Container Registry
%br
%span.descr Enable Container Registry for this project
= link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
%script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project)
.js-project-permissions-form
= f.submit 'Save changes', class: "btn btn-save"
 
%section.settings.merge-requests-feature{ style: ("display: none;" if @project.project_feature.send(:merge_requests_access_level) == 0) }
%section.settings.merge-requests-feature{ class: ("hidden" if @project.project_feature.send(:merge_requests_access_level) == 0) }
.settings-header
%h4
Merge request settings
Loading
Loading
---
title: Redesign project feature permissions settings
merge_request: 14062
author:
type: changed
Loading
Loading
@@ -6,7 +6,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps
 
step 'change project settings' do
fill_in 'project_name_edit', with: 'NewName'
select 'Disabled', from: 'project_project_feature_attributes_issues_access_level'
end
 
step 'I save project' do
Loading
Loading
Loading
Loading
@@ -89,7 +89,7 @@ module SharedProject
step 'I should see project settings' do
expect(current_path).to eq edit_project_path(@project)
expect(page).to have_content("Project name")
expect(page).to have_content("Sharing and permissions")
expect(page).to have_content("Permissions")
end
 
def current_project
Loading
Loading
require 'rails_helper'
 
feature 'Project edit', js: true do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let(:project) { create(:project) }
 
before do
project.team << [user, :master]
sign_in(user)
context 'feature visibility' do
before do
project.team << [user, :master]
sign_in(user)
 
visit edit_project_path(project)
end
visit edit_project_path(project)
end
 
context 'feature visibility' do
context 'merge requests select' do
it 'hides merge requests section' do
select('Disabled', from: 'project_project_feature_attributes_merge_requests_access_level')
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
 
expect(page).to have_selector('.merge-requests-feature', visible: false)
end
Loading
Loading
@@ -30,7 +31,7 @@ feature 'Project edit', js: true do
 
context 'builds select' do
it 'hides builds select section' do
select('Disabled', from: 'project_project_feature_attributes_builds_access_level')
find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
 
expect(page).to have_selector('.builds-feature', visible: false)
end
Loading
Loading
@@ -44,4 +45,18 @@ feature 'Project edit', js: true do
end
end
end
context 'LFS enabled setting' do
before do
sign_in(admin)
end
it 'displays the correct elements' do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
visit edit_project_path(project)
expect(page).to have_content('Git Large File Storage')
expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true)
end
end
end
Loading
Loading
@@ -19,21 +19,16 @@ describe 'Edit Project Settings' do
it 'toggles visibility' do
visit edit_project_path(project)
 
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
# disable by clicking toggle
toggle_feature_off("project[project_feature_attributes][#{tool_name}_access_level]")
page.within('.sharing-permissions') do
click_button 'Save changes'
end
wait_for_requests
expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
 
select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level"
page.within('.sharing-permissions') do
click_button 'Save changes'
end
wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level"
# re-enable by clicking toggle again
toggle_feature_on("project[project_feature_attributes][#{tool_name}_access_level]")
page.within('.sharing-permissions') do
click_button 'Save changes'
end
Loading
Loading
@@ -176,19 +171,19 @@ describe 'Edit Project Settings' do
end
 
it "disables repository related features" do
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
toggle_feature_off('project[project_feature_attributes][repository_access_level]')
 
page.within('.sharing-permissions') do
click_button "Save changes"
end
 
expect(find(".sharing-permissions")).to have_selector("select.disabled", count: 2)
expect(find(".sharing-permissions")).to have_selector(".project-feature-toggle.disabled", count: 2)
end
 
it "shows empty features project homepage" do
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
select "Disabled", from: "project_project_feature_attributes_issues_access_level"
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
toggle_feature_off('project[project_feature_attributes][repository_access_level]')
toggle_feature_off('project[project_feature_attributes][issues_access_level]')
toggle_feature_off('project[project_feature_attributes][wiki_access_level]')
 
page.within('.sharing-permissions') do
click_button "Save changes"
Loading
Loading
@@ -201,9 +196,9 @@ describe 'Edit Project Settings' do
end
 
it "hides project activity tabs" do
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
select "Disabled", from: "project_project_feature_attributes_issues_access_level"
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
toggle_feature_off('project[project_feature_attributes][repository_access_level]')
toggle_feature_off('project[project_feature_attributes][issues_access_level]')
toggle_feature_off('project[project_feature_attributes][wiki_access_level]')
 
page.within('.sharing-permissions') do
click_button "Save changes"
Loading
Loading
@@ -222,7 +217,7 @@ describe 'Edit Project Settings' do
 
# Regression spec for https://gitlab.com/gitlab-org/gitlab-ce/issues/25272
it "hides comments activity tab only on disabled issues, merge requests and repository" do
select "Disabled", from: "project_project_feature_attributes_issues_access_level"
toggle_feature_off('project[project_feature_attributes][issues_access_level]')
 
save_changes_and_check_activity_tab do
expect(page).to have_content("Comments")
Loading
Loading
@@ -230,7 +225,7 @@ describe 'Edit Project Settings' do
 
visit edit_project_path(project)
 
select "Disabled", from: "project_project_feature_attributes_merge_requests_access_level"
toggle_feature_off('project[project_feature_attributes][merge_requests_access_level]')
 
save_changes_and_check_activity_tab do
expect(page).to have_content("Comments")
Loading
Loading
@@ -238,7 +233,7 @@ describe 'Edit Project Settings' do
 
visit edit_project_path(project)
 
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
toggle_feature_off('project[project_feature_attributes][repository_access_level]')
 
save_changes_and_check_activity_tab do
expect(page).not_to have_content("Comments")
Loading
Loading
@@ -275,4 +270,12 @@ describe 'Edit Project Settings' do
expect(page).not_to have_selector('.project-stats')
end
end
def toggle_feature_off(feature_name)
find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle.checked").click
end
def toggle_feature_on(feature_name)
find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle:not(.checked)").click
end
end
Loading
Loading
@@ -19,8 +19,8 @@ feature 'Project settings > Merge Requests', :js do
expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
 
select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level"
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
click_on('Save changes')
end
 
Loading
Loading
@@ -39,8 +39,8 @@ feature 'Project settings > Merge Requests', :js do
expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
 
select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level"
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
click_on('Save changes')
end
 
Loading
Loading
@@ -60,8 +60,8 @@ feature 'Project settings > Merge Requests', :js do
expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
 
select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level"
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
click_on('Save changes')
end
 
Loading
Loading
Loading
Loading
@@ -11,19 +11,19 @@ feature 'Visibility settings', js: true do
end
 
scenario 'project visibility select is available' do
visibility_select_container = find('.js-visibility-select')
visibility_select_container = find('.project-visibility-setting')
 
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 accessed without any authentication.'
expect(visibility_select_container.find('select').value).to eq project.visibility_level.to_s
expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of 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_container = find('.project-visibility-setting')
visibility_select = visibility_select_container.find('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.'
expect(visibility_select_container).to have_content 'Access must be granted explicitly to each user.'
end
end
 
Loading
Loading
@@ -37,11 +37,10 @@ feature 'Visibility settings', js: true do
end
 
scenario 'project visibility is locked' do
visibility_select_container = find('.js-visibility-select')
visibility_select_container = find('.project-visibility-setting')
 
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 accessed without any authentication.'
expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled'
expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.'
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