Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
ccac30cd
Commit
ccac30cd
authored
Jul 02, 2017
by
Bob Van Landuyt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move in CE-js for protected branches.
And move the EE-specifics into a separate bundle
parent
a02d0af0
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
871 additions
and
606 deletions
+871
-606
app/assets/javascripts/protected_branches/ee/protected_branch_access_dropdown.js
...protected_branches/ee/protected_branch_access_dropdown.js
+439
-0
app/assets/javascripts/protected_branches/ee/protected_branch_create.js
...ascripts/protected_branches/ee/protected_branch_create.js
+122
-0
app/assets/javascripts/protected_branches/ee/protected_branch_dropdown.js
...cripts/protected_branches/ee/protected_branch_dropdown.js
+83
-0
app/assets/javascripts/protected_branches/ee/protected_branch_edit.js
...avascripts/protected_branches/ee/protected_branch_edit.js
+145
-0
app/assets/javascripts/protected_branches/ee/protected_branch_edit_list.js
...ripts/protected_branches/ee/protected_branch_edit_list.js
+18
-0
app/assets/javascripts/protected_branches/ee/protected_branches_bundle.js
...cripts/protected_branches/ee/protected_branches_bundle.js
+5
-0
app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
...ts/protected_branches/protected_branch_access_dropdown.js
+12
-420
app/assets/javascripts/protected_branches/protected_branch_create.js
...javascripts/protected_branches/protected_branch_create.js
+18
-85
app/assets/javascripts/protected_branches/protected_branch_edit.js
...s/javascripts/protected_branches/protected_branch_edit.js
+24
-100
app/views/projects/protected_branches/_index.html.haml
app/views/projects/protected_branches/_index.html.haml
+4
-1
config/webpack.config.js
config/webpack.config.js
+1
-0
No files found.
app/assets/javascripts/protected_branches/ee/protected_branch_access_dropdown.js
0 → 100644
View file @
ccac30cd
/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, no-underscore-dangle, no-continue, no-restricted-syntax, guard-for-in, no-new, class-methods-use-this, consistent-return, max-len */
/* global Flash */
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
const
PUSH_ACCESS_LEVEL
=
'
push_access_levels
'
;
const
LEVEL_TYPES
=
{
ROLE
:
'
role
'
,
USER
:
'
user
'
,
GROUP
:
'
group
'
};
gl
.
ProtectedBranchAccessDropdown
=
class
{
constructor
(
options
)
{
const
self
=
this
;
const
{
$dropdown
,
onSelect
,
onHide
,
accessLevel
,
accessLevelsData
}
=
options
;
this
.
isAllowedToPushDropdown
=
false
;
this
.
groups
=
[];
this
.
accessLevel
=
accessLevel
;
this
.
accessLevelsData
=
accessLevelsData
.
roles
;
this
.
$dropdown
=
$dropdown
;
this
.
$wrap
=
this
.
$dropdown
.
closest
(
`.
${
this
.
accessLevel
}
-container`
);
this
.
usersPath
=
'
/autocomplete/users.json
'
;
this
.
groupsPath
=
'
/autocomplete/project_groups.json
'
;
this
.
defaultLabel
=
this
.
$dropdown
.
data
(
'
defaultLabel
'
);
this
.
setSelectedItems
([]);
this
.
persistPreselectedItems
();
if
(
PUSH_ACCESS_LEVEL
===
this
.
accessLevel
)
{
this
.
isAllowedToPushDropdown
=
true
;
this
.
noOneObj
=
this
.
accessLevelsData
[
2
];
}
$dropdown
.
glDropdown
({
selectable
:
true
,
filterable
:
true
,
filterRemote
:
true
,
data
:
this
.
getData
.
bind
(
this
),
multiSelect
:
$dropdown
.
hasClass
(
'
js-multiselect
'
),
renderRow
:
this
.
renderRow
.
bind
(
this
),
toggleLabel
:
this
.
toggleLabel
.
bind
(
this
),
hidden
()
{
if
(
onHide
)
{
onHide
();
}
},
clicked
(
opts
)
{
const
{
$el
,
e
}
=
opts
;
const
item
=
opts
.
selectedObj
;
e
.
preventDefault
();
if
(
$el
.
is
(
'
.is-active
'
))
{
if
(
self
.
isAllowedToPushDropdown
)
{
if
(
item
.
id
===
self
.
noOneObj
.
id
)
{
// remove all others selected items
self
.
accessLevelsData
.
forEach
((
level
)
=>
{
if
(
level
.
id
!==
item
.
id
)
{
self
.
removeSelectedItem
(
level
);
}
});
// remove selected item visually
self
.
$wrap
.
find
(
`.item-
${
item
.
type
}
`
).
removeClass
(
'
is-active
'
);
}
else
{
const
$noOne
=
self
.
$wrap
.
find
(
`.is-active.item-
${
item
.
type
}
:contains('No one')`
);
if
(
$noOne
.
length
)
{
$noOne
.
removeClass
(
'
is-active
'
);
self
.
removeSelectedItem
(
self
.
noOneObj
);
}
}
// make element active right away
$el
.
addClass
(
`is-active item-
${
item
.
type
}
`
);
}
// Add "No one"
self
.
addSelectedItem
(
item
);
}
else
{
self
.
removeSelectedItem
(
item
);
}
if
(
onSelect
)
{
onSelect
(
item
,
$el
,
self
);
}
}
});
}
persistPreselectedItems
()
{
const
itemsToPreselect
=
this
.
$dropdown
.
data
(
'
preselectedItems
'
);
if
(
typeof
itemsToPreselect
===
'
undefined
'
||
!
itemsToPreselect
.
length
)
{
return
;
}
itemsToPreselect
.
forEach
((
item
)
=>
{
item
.
persisted
=
true
;
});
this
.
setSelectedItems
(
itemsToPreselect
);
}
setSelectedItems
(
items
)
{
this
.
items
=
items
.
length
?
items
:
[];
}
getSelectedItems
()
{
return
this
.
items
.
filter
((
item
)
=>
!
item
.
_destroy
);
}
getAllSelectedItems
()
{
return
this
.
items
;
}
// Return dropdown as input data ready to submit
getInputData
()
{
const
accessLevels
=
[];
const
selectedItems
=
this
.
getAllSelectedItems
();
selectedItems
.
forEach
((
item
)
=>
{
const
obj
=
{};
if
(
typeof
item
.
id
!==
'
undefined
'
)
{
obj
.
id
=
item
.
id
;
}
if
(
typeof
item
.
_destroy
!==
'
undefined
'
)
{
obj
.
_destroy
=
item
.
_destroy
;
}
if
(
item
.
type
===
LEVEL_TYPES
.
ROLE
)
{
obj
.
access_level
=
item
.
access_level
;
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
USER
)
{
obj
.
user_id
=
item
.
user_id
;
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
GROUP
)
{
obj
.
group_id
=
item
.
group_id
;
}
accessLevels
.
push
(
obj
);
});
return
accessLevels
;
}
addSelectedItem
(
selectedItem
)
{
let
itemToAdd
=
{};
// If the item already exists, just use it
let
index
=
-
1
;
const
selectedItems
=
this
.
getAllSelectedItems
();
for
(
let
i
=
0
;
i
<
selectedItems
.
length
;
i
+=
1
)
{
if
(
selectedItem
.
id
===
selectedItems
[
i
].
access_level
)
{
index
=
i
;
continue
;
}
}
if
(
index
!==
-
1
&&
selectedItems
[
index
].
_destroy
)
{
delete
selectedItems
[
index
].
_destroy
;
return
;
}
itemToAdd
.
type
=
selectedItem
.
type
;
if
(
selectedItem
.
type
===
LEVEL_TYPES
.
USER
)
{
itemToAdd
=
{
user_id
:
selectedItem
.
id
,
name
:
selectedItem
.
name
||
'
_name1
'
,
username
:
selectedItem
.
username
||
'
_username1
'
,
avatar_url
:
selectedItem
.
avatar_url
||
'
_avatar_url1
'
,
type
:
LEVEL_TYPES
.
USER
};
}
else
if
(
selectedItem
.
type
===
LEVEL_TYPES
.
ROLE
)
{
itemToAdd
=
{
access_level
:
selectedItem
.
id
,
type
:
LEVEL_TYPES
.
ROLE
};
}
else
if
(
selectedItem
.
type
===
LEVEL_TYPES
.
GROUP
)
{
itemToAdd
=
{
group_id
:
selectedItem
.
id
,
type
:
LEVEL_TYPES
.
GROUP
};
}
this
.
items
.
push
(
itemToAdd
);
}
removeSelectedItem
(
itemToDelete
)
{
let
index
=
-
1
;
const
selectedItems
=
this
.
getAllSelectedItems
();
// To find itemToDelete on selectedItems, first we need the index
for
(
let
i
=
0
;
i
<
selectedItems
.
length
;
i
+=
1
)
{
const
currentItem
=
selectedItems
[
i
];
if
(
currentItem
.
type
!==
itemToDelete
.
type
)
{
continue
;
}
if
(
currentItem
.
type
===
LEVEL_TYPES
.
USER
&&
currentItem
.
user_id
===
itemToDelete
.
id
)
{
index
=
i
;
}
else
if
(
currentItem
.
type
===
LEVEL_TYPES
.
ROLE
&&
currentItem
.
access_level
===
itemToDelete
.
id
)
{
index
=
i
;
}
else
if
(
currentItem
.
type
===
LEVEL_TYPES
.
GROUP
&&
currentItem
.
group_id
===
itemToDelete
.
id
)
{
index
=
i
;
}
if
(
index
>
-
1
)
{
break
;
}
}
// if ItemToDelete is not really selected do nothing
if
(
index
===
-
1
)
{
return
;
}
if
(
selectedItems
[
index
].
persisted
)
{
// If we toggle an item that has been already marked with _destroy
if
(
selectedItems
[
index
].
_destroy
)
{
delete
selectedItems
[
index
].
_destroy
;
}
else
{
selectedItems
[
index
].
_destroy
=
'
1
'
;
}
}
else
{
selectedItems
.
splice
(
index
,
1
);
}
}
toggleLabel
()
{
const
currentItems
=
this
.
getSelectedItems
();
const
types
=
_
.
groupBy
(
currentItems
,
(
item
)
=>
item
.
type
);
const
label
=
[];
if
(
currentItems
.
length
)
{
for
(
const
LEVEL_TYPE
in
LEVEL_TYPES
)
{
const
typeName
=
LEVEL_TYPES
[
LEVEL_TYPE
];
const
numberOfTypes
=
types
[
typeName
]
?
types
[
typeName
].
length
:
0
;
const
text
=
numberOfTypes
===
1
?
typeName
:
`
${
typeName
}
s`
;
label
.
push
(
`
${
numberOfTypes
}
${
text
}
`
);
}
}
else
{
label
.
push
(
this
.
defaultLabel
);
}
this
.
$dropdown
.
find
(
'
.dropdown-toggle-text
'
).
toggleClass
(
'
is-default
'
,
!
currentItems
.
length
);
return
label
.
join
(
'
,
'
);
}
getData
(
query
,
callback
)
{
this
.
getUsers
(
query
).
done
((
usersResponse
)
=>
{
if
(
this
.
groups
.
length
)
{
callback
(
this
.
consolidateData
(
usersResponse
,
this
.
groups
));
}
else
{
this
.
getGroups
(
query
).
done
((
groupsResponse
)
=>
{
// Cache groups to avoid multiple requests
this
.
groups
=
groupsResponse
;
callback
(
this
.
consolidateData
(
usersResponse
,
groupsResponse
));
});
}
}).
error
(()
=>
{
new
Flash
(
'
Failed to load users.
'
);
});
}
consolidateData
(
usersResponse
,
groupsResponse
)
{
let
consolidatedData
=
[];
const
map
=
[];
let
roles
=
[];
const
users
=
[];
let
groups
=
[];
const
selectedItems
=
this
.
getSelectedItems
();
// ID property is handled differently locally from the server
//
// For Groups
// In dropdown: `id`
// For submit: `group_id`
//
// For Roles
// In dropdown: `id`
// For submit: `access_level`
//
// For Users
// In dropdown: `id`
// For submit: `user_id`
/*
* Build groups
*/
groups
=
groupsResponse
.
map
((
group
)
=>
{
group
.
type
=
LEVEL_TYPES
.
GROUP
;
return
group
;
});
/*
* Build roles
*/
roles
=
this
.
accessLevelsData
.
map
((
level
)
=>
{
level
.
type
=
LEVEL_TYPES
.
ROLE
;
return
level
;
});
/*
* Build users
*/
for
(
let
x
=
0
;
x
<
selectedItems
.
length
;
x
+=
1
)
{
const
current
=
selectedItems
[
x
];
if
(
current
.
type
!==
LEVEL_TYPES
.
USER
)
{
continue
;
}
// Collect selected users
users
.
push
({
id
:
current
.
user_id
,
name
:
current
.
name
,
username
:
current
.
username
,
avatar_url
:
current
.
avatar_url
,
type
:
LEVEL_TYPES
.
USER
,
});
// Save identifiers for easy-checking more later
map
.
push
(
LEVEL_TYPES
.
USER
+
current
.
user_id
);
}
// Has to be checked against server response
// because the selected item can be in filter results
for
(
let
i
=
0
;
i
<
usersResponse
.
length
;
i
+=
1
)
{
const
u
=
usersResponse
[
i
];
// Add is it has not been added
if
(
map
.
indexOf
(
LEVEL_TYPES
.
USER
+
u
.
id
)
===
-
1
)
{
u
.
type
=
LEVEL_TYPES
.
USER
;
users
.
push
(
u
);
}
}
if
(
roles
.
length
)
{
consolidatedData
=
consolidatedData
.
concat
([{
header
:
'
Roles
'
,
}],
roles
);
}
if
(
groups
.
length
)
{
if
(
roles
.
length
)
{
consolidatedData
=
consolidatedData
.
concat
([
'
divider
'
]);
}
consolidatedData
=
consolidatedData
.
concat
([{
header
:
'
Groups
'
,
}],
groups
);
}
if
(
users
.
length
)
{
consolidatedData
=
consolidatedData
.
concat
([
'
divider
'
],
[{
header
:
'
Users
'
,
}],
users
);
}
return
consolidatedData
;
}
getUsers
(
query
)
{
return
$
.
ajax
({
dataType
:
'
json
'
,
url
:
this
.
buildUrl
(
this
.
usersPath
),
data
:
{
search
:
query
,
per_page
:
20
,
active
:
true
,
project_id
:
gon
.
current_project_id
,
push_code
:
true
,
}
});
}
getGroups
()
{
return
$
.
ajax
({
dataType
:
'
json
'
,
url
:
this
.
buildUrl
(
this
.
groupsPath
),
data
:
{
project_id
:
gon
.
current_project_id
}
});
}
buildUrl
(
url
)
{
if
(
gon
.
relative_url_root
!=
null
)
{
url
=
gon
.
relative_url_root
.
replace
(
/
\/
$/
,
''
)
+
url
;
}
return
url
;
}
renderRow
(
item
)
{
let
criteria
=
{};
// Dectect if the current item is already saved so we can add
// the `is-active` class so the item looks as marked
if
(
item
.
type
===
LEVEL_TYPES
.
USER
)
{
criteria
=
{
user_id
:
item
.
id
};
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
ROLE
)
{
criteria
=
{
access_level
:
item
.
id
};
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
GROUP
)
{
criteria
=
{
group_id
:
item
.
id
};
}
const
isActive
=
_
.
findWhere
(
this
.
getSelectedItems
(),
criteria
)
?
'
is-active
'
:
''
;
if
(
item
.
type
===
LEVEL_TYPES
.
USER
)
{
return
this
.
userRowHtml
(
item
,
isActive
);
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
ROLE
)
{
return
this
.
roleRowHtml
(
item
,
isActive
);
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
GROUP
)
{
return
this
.
groupRowHtml
(
item
,
isActive
);
}
}
userRowHtml
(
user
,
isActive
)
{
const
avatarHtml
=
`<img src='
${
user
.
avatar_url
}
' class='avatar avatar-inline' width='30'>`
;
const
nameHtml
=
`<strong class='dropdown-menu-user-full-name'>
${
user
.
name
}
</strong>`
;
const
usernameHtml
=
`<span class='dropdown-menu-user-username'>
${
user
.
username
}
</span>`
;
return
`<li><a href='#' class='
${
isActive
?
'
is-active
'
:
''
}
'>
${
avatarHtml
}
${
nameHtml
}
${
usernameHtml
}
</a></li>`
;
}
groupRowHtml
(
group
,
isActive
)
{
const
avatarHtml
=
group
.
avatar_url
?
`<img src='
${
group
.
avatar_url
}
' class='avatar avatar-inline' width='30'>`
:
''
;
const
groupnameHtml
=
`<span class='dropdown-menu-group-groupname'>
${
group
.
name
}
</span>`
;
return
`<li><a href='#' class='
${
isActive
?
'
is-active
'
:
''
}
'>
${
avatarHtml
}
${
groupnameHtml
}
</a></li>`
;
}
roleRowHtml
(
role
,
isActive
)
{
return
`<li><a href='#' class='
${
isActive
?
'
is-active
'
:
''
}
item-
${
role
.
type
}
'>
${
role
.
text
}
</a></li>`
;
}
};
})(
window
);
app/assets/javascripts/protected_branches/ee/protected_branch_create.js
0 → 100644
View file @
ccac30cd
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, guard-for-in, no-restricted-syntax, max-len */
/* global ProtectedBranchDropdown */
/* global Flash */
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
const
ACCESS_LEVELS
=
{
MERGE
:
'
merge_access_levels
'
,
PUSH
:
'
push_access_levels
'
,
};
const
LEVEL_TYPES
=
{
ROLE
:
'
role
'
,
USER
:
'
user
'
,
GROUP
:
'
group
'
};
gl
.
ProtectedBranchCreate
=
class
{
constructor
()
{
this
.
$wrap
=
this
.
$form
=
$
(
'
#new_protected_branch
'
);
this
.
buildDropdowns
();
this
.
$branchInput
=
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
);
this
.
bindEvents
();
}
bindEvents
()
{
this
.
$form
.
on
(
'
submit
'
,
this
.
onFormSubmit
.
bind
(
this
));
}
buildDropdowns
()
{
const
$allowedToMergeDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-merge
'
);
const
$allowedToPushDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-push
'
);
// Cache callback
this
.
onSelectCallback
=
this
.
onSelect
.
bind
(
this
);
// Allowed to Merge dropdown
this
[
`
${
ACCESS_LEVELS
.
MERGE
}
_dropdown`
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
$dropdown
:
$allowedToMergeDropdown
,
accessLevelsData
:
gon
.
merge_access_levels
,
onSelect
:
this
.
onSelectCallback
,
accessLevel
:
ACCESS_LEVELS
.
MERGE
});
// Allowed to Push dropdown
this
[
`
${
ACCESS_LEVELS
.
PUSH
}
_dropdown`
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
$dropdown
:
$allowedToPushDropdown
,
accessLevelsData
:
gon
.
push_access_levels
,
onSelect
:
this
.
onSelectCallback
,
accessLevel
:
ACCESS_LEVELS
.
PUSH
});
// Protected branch dropdown
new
window
.
ProtectedBranchDropdown
({
$dropdown
:
this
.
$wrap
.
find
(
'
.js-protected-branch-select
'
),
onSelect
:
this
.
onSelectCallback
});
}
// Enable submit button after selecting an option
onSelect
()
{
const
$allowedToMerge
=
this
[
`
${
ACCESS_LEVELS
.
MERGE
}
_dropdown`
].
getSelectedItems
();
const
$allowedToPush
=
this
[
`
${
ACCESS_LEVELS
.
PUSH
}
_dropdown`
].
getSelectedItems
();
const
toggle
=
!
(
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
).
val
()
&&
$allowedToMerge
.
length
&&
$allowedToPush
.
length
);
this
.
$form
.
find
(
'
input[type="submit"]
'
).
attr
(
'
disabled
'
,
toggle
);
}
getFormData
()
{
const
formData
=
{
authenticity_token
:
this
.
$form
.
find
(
'
input[name="authenticity_token"]
'
).
val
(),
protected_branch
:
{
name
:
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
).
val
(),
}
};
for
(
const
ACCESS_LEVEL
in
ACCESS_LEVELS
)
{
const
selectedItems
=
this
[
`
${
ACCESS_LEVELS
[
ACCESS_LEVEL
]}
_dropdown`
].
getSelectedItems
();
const
levelAttributes
=
[];
for
(
let
i
=
0
;
i
<
selectedItems
.
length
;
i
+=
1
)
{
const
current
=
selectedItems
[
i
];
if
(
current
.
type
===
LEVEL_TYPES
.
USER
)
{
levelAttributes
.
push
({
user_id
:
selectedItems
[
i
].
user_id
});
}
else
if
(
current
.
type
===
LEVEL_TYPES
.
ROLE
)
{
levelAttributes
.
push
({
access_level
:
selectedItems
[
i
].
access_level
});
}
else
if
(
current
.
type
===
LEVEL_TYPES
.
GROUP
)
{
levelAttributes
.
push
({
group_id
:
selectedItems
[
i
].
group_id
});
}
}
formData
.
protected_branch
[
`
${
ACCESS_LEVELS
[
ACCESS_LEVEL
]}
_attributes`
]
=
levelAttributes
;
}
return
formData
;
}
onFormSubmit
(
e
)
{
e
.
preventDefault
();
$
.
ajax
({
url
:
this
.
$form
.
attr
(
'
action
'
),
method
:
this
.
$form
.
attr
(
'
method
'
),
data
:
this
.
getFormData
()
})
.
success
(()
=>
{
location
.
reload
();
})
.
fail
(()
=>
{
new
Flash
(
'
Failed to protect the branch
'
);
});
}
};
})(
window
);
app/assets/javascripts/protected_branches/ee/protected_branch_dropdown.js
0 → 100644
View file @
ccac30cd
/* eslint-disable comma-dangle, no-unused-vars */
class
ProtectedBranchDropdown
{
constructor
(
options
)
{
this
.
onSelect
=
options
.
onSelect
;
this
.
$dropdown
=
options
.
$dropdown
;
this
.
$dropdownContainer
=
this
.
$dropdown
.
parent
();
this
.
$dropdownFooter
=
this
.
$dropdownContainer
.
find
(
'
.dropdown-footer
'
);
this
.
$protectedBranch
=
this
.
$dropdownContainer
.
find
(
'
.js-create-new-protected-branch
'
);
this
.
buildDropdown
();
this
.
bindEvents
();
// Hide footer
this
.
$dropdownFooter
.
addClass
(
'
hidden
'
);
}
buildDropdown
()
{
this
.
$dropdown
.
glDropdown
({
data
:
this
.
getProtectedBranches
.
bind
(
this
),
filterable
:
true
,
remote
:
false
,
search
:
{
fields
:
[
'
title
'
]
},
selectable
:
true
,
toggleLabel
(
selected
)
{
return
(
selected
&&
'
id
'
in
selected
)
?
selected
.
title
:
'
Protected Branch
'
;
},
fieldName
:
'
protected_branch[name]
'
,
text
(
protectedBranch
)
{
return
_
.
escape
(
protectedBranch
.
title
);
},
id
(
protectedBranch
)
{
return
_
.
escape
(
protectedBranch
.
id
);
},
onFilter
:
this
.
toggleCreateNewButton
.
bind
(
this
),
clicked
:
(
options
)
=>
{
const
{
$el
,
e
}
=
options
;
e
.
preventDefault
();
this
.
onSelect
();
}
});
}
bindEvents
()
{
this
.
$protectedBranch
.
on
(
'
click
'
,
this
.
onClickCreateWildcard
.
bind
(
this
));
}
onClickCreateWildcard
(
e
)
{
e
.
preventDefault
();
// Refresh the dropdown's data, which ends up calling `getProtectedBranches`
this
.
$dropdown
.
data
(
'
glDropdown
'
).
remote
.
execute
();
this
.
$dropdown
.
data
(
'
glDropdown
'
).
selectRowAtIndex
();
}
getProtectedBranches
(
term
,
callback
)
{
if
(
this
.
selectedBranch
)
{
callback
(
gon
.
open_branches
.
concat
(
this
.
selectedBranch
));
}
else
{
callback
(
gon
.
open_branches
);
}
}
toggleCreateNewButton
(
branchName
)
{
this
.
selectedBranch
=
{
title
:
branchName
,
id
:
branchName
,
text
:
branchName
};
if
(
branchName
)
{
this
.
$dropdownContainer
.
find
(
'
.js-create-new-protected-branch code
'
)
.
text
(
branchName
);
}
this
.
$dropdownFooter
.
toggleClass
(
'
hidden
'
,
!
branchName
);
}
}
window
.
ProtectedBranchDropdown
=
ProtectedBranchDropdown
;
app/assets/javascripts/protected_branches/ee/protected_branch_edit.js
0 → 100644
View file @
ccac30cd
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, dot-notation, no-unused-vars, no-restricted-syntax, guard-for-in, max-len */
/* global Flash */
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
const
ACCESS_LEVELS
=
{
MERGE
:
'
merge_access_levels
'
,
PUSH
:
'
push_access_levels
'
,
};
const
LEVEL_TYPES
=
{
ROLE
:
'
role
'
,
USER
:
'
user
'
,
GROUP
:
'
group
'
};
gl
.
ProtectedBranchEdit
=
class
{
constructor
(
options
)
{
this
.
$wraps
=
{};
this
.
hasChanges
=
false
;
this
.
$wrap
=
options
.
$wrap
;
this
.
$allowedToMergeDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-merge
'
);
this
.
$allowedToPushDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-push
'
);
this
.
$wraps
[
ACCESS_LEVELS
.
MERGE
]
=
this
.
$allowedToMergeDropdown
.
closest
(
`.
${
ACCESS_LEVELS
.
MERGE
}
-container`
);
this
.
$wraps
[
ACCESS_LEVELS
.
PUSH
]
=
this
.
$allowedToPushDropdown
.
closest
(
`.
${
ACCESS_LEVELS
.
PUSH
}
-container`
);
this
.
buildDropdowns
();
}
buildDropdowns
()
{
// Allowed to merge dropdown
this
[
'
merge_access_levels_dropdown
'
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
accessLevel
:
ACCESS_LEVELS
.
MERGE
,
accessLevelsData
:
gon
.
merge_access_levels
,
$dropdown
:
this
.
$allowedToMergeDropdown
,
onSelect
:
this
.
onSelectOption
.
bind
(
this
),
onHide
:
this
.
onDropdownHide
.
bind
(
this
)
});
// Allowed to push dropdown
this
[
'
push_access_levels_dropdown
'
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
accessLevel
:
ACCESS_LEVELS
.
PUSH
,
accessLevelsData
:
gon
.
push_access_levels
,
$dropdown
:
this
.
$allowedToPushDropdown
,
onSelect
:
this
.
onSelectOption
.
bind
(
this
),
onHide
:
this
.
onDropdownHide
.
bind
(
this
)
});
}
onSelectOption
(
item
,
$el
,
dropdownInstance
)
{
this
.
hasChanges
=
true
;
}
onDropdownHide
()
{
if
(
!
this
.
hasChanges
)
return
;
this
.
hasChanges
=
true
;
this
.
updatePermissions
();
}
updatePermissions
()
{
const
formData
=
{};
for
(
const
ACCESS_LEVEL
in
ACCESS_LEVELS
)
{
const
accessLevelName
=
ACCESS_LEVELS
[
ACCESS_LEVEL
];
formData
[
`
${
accessLevelName
}
_attributes`
]
=
this
[
`
${
accessLevelName
}
_dropdown`
].
getInputData
(
accessLevelName
);
}
return
$
.
ajax
({
type
:
'
POST
'
,
url
:
this
.
$wrap
.
data
(
'
url
'
),
dataType
:
'
json
'
,
data
:
{
_method
:
'
PATCH
'
,
protected_branch
:
formData
},
success
:
(
response
)
=>
{
this
.
hasChanges
=
false
;
for
(
const
ACCESS_LEVEL
in
ACCESS_LEVELS
)
{
const
accessLevelName
=
ACCESS_LEVELS
[
ACCESS_LEVEL
];
// The data coming from server will be the new persisted *state* for each dropdown
this
.
setSelectedItemsToDropdown
(
response
[
accessLevelName
],
`
${
accessLevelName
}
_dropdown`
);
}
},
error
()
{
$
.
scrollTo
(
0
);
new
Flash
(
'
Failed to update branch!
'
);
}
}).
always
(()
=>
{
this
.
$allowedToMergeDropdown
.
enable
();
this
.
$allowedToPushDropdown
.
enable
();
});
}
setSelectedItemsToDropdown
(
items
=
[],
dropdownName
)
{
const
itemsToAdd
=
[];
for
(
let
i
=
0
;
i
<
items
.
length
;
i
+=
1
)
{
let
itemToAdd
;
const
currentItem
=
items
[
i
];
if
(
currentItem
.
user_id
)
{
// Do this only for users for now
// get the current data for selected items
const
selectedItems
=
this
[
dropdownName
].
getSelectedItems
();
const
currentSelectedItem
=
_
.
findWhere
(
selectedItems
,
{
user_id
:
currentItem
.
user_id
});
itemToAdd
=
{
id
:
currentItem
.
id
,
user_id
:
currentItem
.
user_id
,
type
:
LEVEL_TYPES
.
USER
,
persisted
:
true
,
name
:
currentSelectedItem
.
name
,
username
:
currentSelectedItem
.
username
,
avatar_url
:
currentSelectedItem
.
avatar_url
};
}
else
if
(
currentItem
.
group_id
)
{
itemToAdd
=
{
id
:
currentItem
.
id
,
group_id
:
currentItem
.
group_id
,
type
:
LEVEL_TYPES
.
GROUP
,
persisted
:
true
};
}
else
{
itemToAdd
=
{
id
:
currentItem
.
id
,
access_level
:
currentItem
.
access_level
,
type
:
LEVEL_TYPES
.
ROLE
,
persisted
:
true
};
}
itemsToAdd
.
push
(
itemToAdd
);
}
this
[
dropdownName
].
setSelectedItems
(
itemsToAdd
);
}
};
})(
window
);
app/assets/javascripts/protected_branches/ee/protected_branch_edit_list.js
0 → 100644
View file @
ccac30cd
/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
gl
.
ProtectedBranchEditList
=
class
{
constructor
()
{
this
.
$wrap
=
$
(
'
.protected-branches-list
'
);
// Build edit forms
this
.
$wrap
.
find
(
'
.js-protected-branch-edit-form
'
).
each
((
i
,
el
)
=>
{
new
gl
.
ProtectedBranchEdit
({
$wrap
:
$
(
el
)
});
});
}
};
})(
window
);
app/assets/javascripts/protected_branches/ee/protected_branches_bundle.js
0 → 100644
View file @
ccac30cd
import
'
./protected_branch_access_dropdown
'
;
import
'
./protected_branch_create
'
;
import
'
./protected_branch_dropdown
'
;
import
'
./protected_branch_edit
'
;
import
'
./protected_branch_edit_list
'
;
app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
View file @
ccac30cd
/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, no-underscore-dangle, no-continue, no-restricted-syntax, guard-for-in, no-new, class-methods-use-this, consistent-return, max-len */
/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, max-len */
/* global Flash */
(
global
=>
{
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
global
.
gl
=
global
.
gl
||
{};
const
PUSH_ACCESS_LEVEL
=
'
push_access_levels
'
;
const
LEVEL_TYPES
=
{
ROLE
:
'
role
'
,
USER
:
'
user
'
,
GROUP
:
'
group
'
};
gl
.
ProtectedBranchAccessDropdown
=
class
{
gl
.
ProtectedBranchAccessDropdown
=
class
{
constructor
(
options
)
{
constructor
(
options
)
{
const
self
=
this
;
const
{
$dropdown
,
data
,
onSelect
}
=
options
;
const
{
$dropdown
,
onSelect
,
onHide
,
accessLevel
,
accessLevelsData
}
=
options
;
this
.
isAllowedToPushDropdown
=
false
;
this
.
groups
=
[];
this
.
accessLevel
=
accessLevel
;
this
.
accessLevelsData
=
accessLevelsData
.
roles
;
this
.
$dropdown
=
$dropdown
;
this
.
$wrap
=
this
.
$dropdown
.
closest
(
`.
${
this
.
accessLevel
}
-container`
);
this
.
usersPath
=
'
/autocomplete/users.json
'
;
this
.
groupsPath
=
'
/autocomplete/project_groups.json
'
;
this
.
defaultLabel
=
this
.
$dropdown
.
data
(
'
defaultLabel
'
);
this
.
setSelectedItems
([]);
this
.
persistPreselectedItems
();
if
(
PUSH_ACCESS_LEVEL
===
this
.
accessLevel
)
{
this
.
isAllowedToPushDropdown
=
true
;
this
.
noOneObj
=
this
.
accessLevelsData
[
2
];
}
$dropdown
.
glDropdown
({
$dropdown
.
glDropdown
({
data
:
data
,
selectable
:
true
,
selectable
:
true
,
filterable
:
true
,
inputId
:
$dropdown
.
data
(
'
input-id
'
),
filterRemote
:
true
,
fieldName
:
$dropdown
.
data
(
'
field-name
'
),
data
:
this
.
getData
.
bind
(
this
),
toggleLabel
(
item
,
el
)
{
multiSelect
:
$dropdown
.
hasClass
(
'
js-multiselect
'
),
if
(
el
.
is
(
'
.is-active
'
))
{
renderRow
:
this
.
renderRow
.
bind
(
this
),
return
item
.
text
;
toggleLabel
:
this
.
toggleLabel
.
bind
(
this
),
}
else
{
hidden
()
{
return
'
Select
'
;
if
(
onHide
)
{
onHide
();
}
}
},
},
clicked
(
opts
)
{
clicked
(
opts
)
{
const
{
$el
,
e
}
=
opts
;
const
{
e
}
=
opts
;
const
item
=
opts
.
selectedObj
;
e
.
preventDefault
();
e
.
preventDefault
();
onSelect
();
if
(
$el
.
is
(
'
.is-active
'
))
{
if
(
self
.
isAllowedToPushDropdown
)
{
if
(
item
.
id
===
self
.
noOneObj
.
id
)
{
// remove all others selected items
self
.
accessLevelsData
.
forEach
((
level
)
=>
{
if
(
level
.
id
!==
item
.
id
)
{
self
.
removeSelectedItem
(
level
);
}
});
// remove selected item visually
self
.
$wrap
.
find
(
`.item-
${
item
.
type
}
`
).
removeClass
(
'
is-active
'
);
}
else
{
const
$noOne
=
self
.
$wrap
.
find
(
`.is-active.item-
${
item
.
type
}
:contains('No one')`
);
if
(
$noOne
.
length
)
{
$noOne
.
removeClass
(
'
is-active
'
);
self
.
removeSelectedItem
(
self
.
noOneObj
);
}
}
// make element active right away
$el
.
addClass
(
`is-active item-
${
item
.
type
}
`
);
}
// Add "No one"
self
.
addSelectedItem
(
item
);
}
else
{
self
.
removeSelectedItem
(
item
);
}
if
(
onSelect
)
{
onSelect
(
item
,
$el
,
self
);
}
}
});
}
persistPreselectedItems
()
{
const
itemsToPreselect
=
this
.
$dropdown
.
data
(
'
preselectedItems
'
);
if
(
typeof
itemsToPreselect
===
'
undefined
'
||
!
itemsToPreselect
.
length
)
{
return
;
}
itemsToPreselect
.
forEach
((
item
)
=>
{
item
.
persisted
=
true
;
});
this
.
setSelectedItems
(
itemsToPreselect
);
}
setSelectedItems
(
items
)
{
this
.
items
=
items
.
length
?
items
:
[];
}
getSelectedItems
()
{
return
this
.
items
.
filter
((
item
)
=>
!
item
.
_destroy
);
}
getAllSelectedItems
()
{
return
this
.
items
;
}
// Return dropdown as input data ready to submit
getInputData
()
{
const
accessLevels
=
[];
const
selectedItems
=
this
.
getAllSelectedItems
();
selectedItems
.
forEach
((
item
)
=>
{
const
obj
=
{};
if
(
typeof
item
.
id
!==
'
undefined
'
)
{
obj
.
id
=
item
.
id
;
}
if
(
typeof
item
.
_destroy
!==
'
undefined
'
)
{
obj
.
_destroy
=
item
.
_destroy
;
}
if
(
item
.
type
===
LEVEL_TYPES
.
ROLE
)
{
obj
.
access_level
=
item
.
access_level
;
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
USER
)
{
obj
.
user_id
=
item
.
user_id
;
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
GROUP
)
{
obj
.
group_id
=
item
.
group_id
;
}
accessLevels
.
push
(
obj
);
});
return
accessLevels
;
}
addSelectedItem
(
selectedItem
)
{
let
itemToAdd
=
{};
// If the item already exists, just use it
let
index
=
-
1
;
const
selectedItems
=
this
.
getAllSelectedItems
();
for
(
let
i
=
0
;
i
<
selectedItems
.
length
;
i
+=
1
)
{
if
(
selectedItem
.
id
===
selectedItems
[
i
].
access_level
)
{
index
=
i
;
continue
;
}
}
if
(
index
!==
-
1
&&
selectedItems
[
index
].
_destroy
)
{
delete
selectedItems
[
index
].
_destroy
;
return
;
}
itemToAdd
.
type
=
selectedItem
.
type
;
if
(
selectedItem
.
type
===
LEVEL_TYPES
.
USER
)
{
itemToAdd
=
{
user_id
:
selectedItem
.
id
,
name
:
selectedItem
.
name
||
'
_name1
'
,
username
:
selectedItem
.
username
||
'
_username1
'
,
avatar_url
:
selectedItem
.
avatar_url
||
'
_avatar_url1
'
,
type
:
LEVEL_TYPES
.
USER
};
}
else
if
(
selectedItem
.
type
===
LEVEL_TYPES
.
ROLE
)
{
itemToAdd
=
{
access_level
:
selectedItem
.
id
,
type
:
LEVEL_TYPES
.
ROLE
};
}
else
if
(
selectedItem
.
type
===
LEVEL_TYPES
.
GROUP
)
{
itemToAdd
=
{
group_id
:
selectedItem
.
id
,
type
:
LEVEL_TYPES
.
GROUP
};
}
this
.
items
.
push
(
itemToAdd
);
}
removeSelectedItem
(
itemToDelete
)
{
let
index
=
-
1
;
const
selectedItems
=
this
.
getAllSelectedItems
();
// To find itemToDelete on selectedItems, first we need the index
for
(
let
i
=
0
;
i
<
selectedItems
.
length
;
i
+=
1
)
{
const
currentItem
=
selectedItems
[
i
];
if
(
currentItem
.
type
!==
itemToDelete
.
type
)
{
continue
;
}
if
(
currentItem
.
type
===
LEVEL_TYPES
.
USER
&&
currentItem
.
user_id
===
itemToDelete
.
id
)
{
index
=
i
;
}
else
if
(
currentItem
.
type
===
LEVEL_TYPES
.
ROLE
&&
currentItem
.
access_level
===
itemToDelete
.
id
)
{
index
=
i
;
}
else
if
(
currentItem
.
type
===
LEVEL_TYPES
.
GROUP
&&
currentItem
.
group_id
===
itemToDelete
.
id
)
{
index
=
i
;
}
if
(
index
>
-
1
)
{
break
;
}
}
}
// if ItemToDelete is not really selected do nothing
if
(
index
===
-
1
)
{
return
;
}
if
(
selectedItems
[
index
].
persisted
)
{
// If we toggle an item that has been already marked with _destroy
if
(
selectedItems
[
index
].
_destroy
)
{
delete
selectedItems
[
index
].
_destroy
;
}
else
{
selectedItems
[
index
].
_destroy
=
'
1
'
;
}
}
else
{
selectedItems
.
splice
(
index
,
1
);
}
}
toggleLabel
()
{
const
currentItems
=
this
.
getSelectedItems
();
const
types
=
_
.
groupBy
(
currentItems
,
(
item
)
=>
item
.
type
);
const
label
=
[];
if
(
currentItems
.
length
)
{
for
(
const
LEVEL_TYPE
in
LEVEL_TYPES
)
{
const
typeName
=
LEVEL_TYPES
[
LEVEL_TYPE
];
const
numberOfTypes
=
types
[
typeName
]
?
types
[
typeName
].
length
:
0
;
const
text
=
numberOfTypes
===
1
?
typeName
:
`
${
typeName
}
s`
;
label
.
push
(
`
${
numberOfTypes
}
${
text
}
`
);
}
}
else
{
label
.
push
(
this
.
defaultLabel
);
}
this
.
$dropdown
.
find
(
'
.dropdown-toggle-text
'
).
toggleClass
(
'
is-default
'
,
!
currentItems
.
length
);
return
label
.
join
(
'
,
'
);
}
getData
(
query
,
callback
)
{
this
.
getUsers
(
query
).
done
((
usersResponse
)
=>
{
if
(
this
.
groups
.
length
)
{
callback
(
this
.
consolidateData
(
usersResponse
,
this
.
groups
));
}
else
{
this
.
getGroups
(
query
).
done
((
groupsResponse
)
=>
{
// Cache groups to avoid multiple requests
this
.
groups
=
groupsResponse
;
callback
(
this
.
consolidateData
(
usersResponse
,
groupsResponse
));
});
}
}).
error
(()
=>
{
new
Flash
(
'
Failed to load users.
'
);
});
}
consolidateData
(
usersResponse
,
groupsResponse
)
{
let
consolidatedData
=
[];
const
map
=
[];
let
roles
=
[];
const
users
=
[];
let
groups
=
[];
const
selectedItems
=
this
.
getSelectedItems
();
// ID property is handled differently locally from the server
//
// For Groups
// In dropdown: `id`
// For submit: `group_id`
//
// For Roles
// In dropdown: `id`
// For submit: `access_level`
//
// For Users
// In dropdown: `id`
// For submit: `user_id`
/*
* Build groups
*/
groups
=
groupsResponse
.
map
((
group
)
=>
{
group
.
type
=
LEVEL_TYPES
.
GROUP
;
return
group
;
});
/*
* Build roles
*/
roles
=
this
.
accessLevelsData
.
map
((
level
)
=>
{
level
.
type
=
LEVEL_TYPES
.
ROLE
;
return
level
;
});
/*
* Build users
*/
for
(
let
x
=
0
;
x
<
selectedItems
.
length
;
x
+=
1
)
{
const
current
=
selectedItems
[
x
];
if
(
current
.
type
!==
LEVEL_TYPES
.
USER
)
{
continue
;
}
// Collect selected users
users
.
push
({
id
:
current
.
user_id
,
name
:
current
.
name
,
username
:
current
.
username
,
avatar_url
:
current
.
avatar_url
,
type
:
LEVEL_TYPES
.
USER
,
});
});
// Save identifiers for easy-checking more later
map
.
push
(
LEVEL_TYPES
.
USER
+
current
.
user_id
);
}
// Has to be checked against server response
// because the selected item can be in filter results
for
(
let
i
=
0
;
i
<
usersResponse
.
length
;
i
+=
1
)
{
const
u
=
usersResponse
[
i
];
// Add is it has not been added
if
(
map
.
indexOf
(
LEVEL_TYPES
.
USER
+
u
.
id
)
===
-
1
)
{
u
.
type
=
LEVEL_TYPES
.
USER
;
users
.
push
(
u
);
}
}
if
(
roles
.
length
)
{
consolidatedData
=
consolidatedData
.
concat
([{
header
:
'
Roles
'
,
}],
roles
);
}
if
(
groups
.
length
)
{
if
(
roles
.
length
)
{
consolidatedData
=
consolidatedData
.
concat
([
'
divider
'
]);
}
consolidatedData
=
consolidatedData
.
concat
([{
header
:
'
Groups
'
,
}],
groups
);
}
if
(
users
.
length
)
{
consolidatedData
=
consolidatedData
.
concat
([
'
divider
'
],
[{
header
:
'
Users
'
,
}],
users
);
}
return
consolidatedData
;
}
getUsers
(
query
)
{
return
$
.
ajax
({
dataType
:
'
json
'
,
url
:
this
.
buildUrl
(
this
.
usersPath
),
data
:
{
search
:
query
,
per_page
:
20
,
active
:
true
,
project_id
:
gon
.
current_project_id
,
push_code
:
true
,
}
});
}
getGroups
()
{
return
$
.
ajax
({
dataType
:
'
json
'
,
url
:
this
.
buildUrl
(
this
.
groupsPath
),
data
:
{
project_id
:
gon
.
current_project_id
}
});
}
buildUrl
(
url
)
{
if
(
gon
.
relative_url_root
!=
null
)
{
url
=
gon
.
relative_url_root
.
replace
(
/
\/
$/
,
''
)
+
url
;
}
return
url
;
}
renderRow
(
item
)
{
let
criteria
=
{};
// Dectect if the current item is already saved so we can add
// the `is-active` class so the item looks as marked
if
(
item
.
type
===
LEVEL_TYPES
.
USER
)
{
criteria
=
{
user_id
:
item
.
id
};
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
ROLE
)
{
criteria
=
{
access_level
:
item
.
id
};
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
GROUP
)
{
criteria
=
{
group_id
:
item
.
id
};
}
const
isActive
=
_
.
findWhere
(
this
.
getSelectedItems
(),
criteria
)
?
'
is-active
'
:
''
;
if
(
item
.
type
===
LEVEL_TYPES
.
USER
)
{
return
this
.
userRowHtml
(
item
,
isActive
);
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
ROLE
)
{
return
this
.
roleRowHtml
(
item
,
isActive
);
}
else
if
(
item
.
type
===
LEVEL_TYPES
.
GROUP
)
{
return
this
.
groupRowHtml
(
item
,
isActive
);
}
}
userRowHtml
(
user
,
isActive
)
{
const
avatarHtml
=
`<img src='
${
user
.
avatar_url
}
' class='avatar avatar-inline' width='30'>`
;
const
nameHtml
=
`<strong class='dropdown-menu-user-full-name'>
${
user
.
name
}
</strong>`
;
const
usernameHtml
=
`<span class='dropdown-menu-user-username'>
${
user
.
username
}
</span>`
;
return
`<li><a href='#' class='
${
isActive
?
'
is-active
'
:
''
}
'>
${
avatarHtml
}
${
nameHtml
}
${
usernameHtml
}
</a></li>`
;
}
groupRowHtml
(
group
,
isActive
)
{
const
avatarHtml
=
group
.
avatar_url
?
`<img src='
${
group
.
avatar_url
}
' class='avatar avatar-inline' width='30'>`
:
''
;
const
groupnameHtml
=
`<span class='dropdown-menu-group-groupname'>
${
group
.
name
}
</span>`
;
return
`<li><a href='#' class='
${
isActive
?
'
is-active
'
:
''
}
'>
${
avatarHtml
}
${
groupnameHtml
}
</a></li>`
;
}
roleRowHtml
(
role
,
isActive
)
{
return
`<li><a href='#' class='
${
isActive
?
'
is-active
'
:
''
}
item-
${
role
.
type
}
'>
${
role
.
text
}
</a></li>`
;
}
}
};
};
})(
window
);
})(
window
);
app/assets/javascripts/protected_branches/protected_branch_create.js
View file @
ccac30cd
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle,
guard-for-in, no-restricted-syntax,
max-len */
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
/* global ProtectedBranchDropdown */
/* global ProtectedBranchDropdown */
/* global Flash */
(
global
=>
{
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
global
.
gl
=
global
.
gl
||
{};
const
ACCESS_LEVELS
=
{
MERGE
:
'
merge_access_levels
'
,
PUSH
:
'
push_access_levels
'
,
};
const
LEVEL_TYPES
=
{
ROLE
:
'
role
'
,
USER
:
'
user
'
,
GROUP
:
'
group
'
};
gl
.
ProtectedBranchCreate
=
class
{
gl
.
ProtectedBranchCreate
=
class
{
constructor
()
{
constructor
()
{
this
.
$wrap
=
this
.
$form
=
$
(
'
#new_protected_branch
'
);
this
.
$wrap
=
this
.
$form
=
$
(
'
#new_protected_branch
'
);
this
.
buildDropdowns
();
this
.
buildDropdowns
();
this
.
$branchInput
=
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
);
this
.
bindEvents
();
}
bindEvents
()
{
this
.
$form
.
on
(
'
submit
'
,
this
.
onFormSubmit
.
bind
(
this
));
}
}
buildDropdowns
()
{
buildDropdowns
()
{
...
@@ -36,87 +18,38 @@
...
@@ -36,87 +18,38 @@
this
.
onSelectCallback
=
this
.
onSelect
.
bind
(
this
);
this
.
onSelectCallback
=
this
.
onSelect
.
bind
(
this
);
// Allowed to Merge dropdown
// Allowed to Merge dropdown
this
[
`
${
ACCESS_LEVELS
.
MERGE
}
_dropdown`
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
new
gl
.
ProtectedBranchAccessDropdown
({
$dropdown
:
$allowedToMergeDropdown
,
$dropdown
:
$allowedToMergeDropdown
,
accessLevelsData
:
gon
.
merge_access_levels
,
data
:
gon
.
merge_access_levels
,
onSelect
:
this
.
onSelectCallback
,
onSelect
:
this
.
onSelectCallback
accessLevel
:
ACCESS_LEVELS
.
MERGE
});
});
// Allowed to Push dropdown
// Allowed to Push dropdown
this
[
`
${
ACCESS_LEVELS
.
PUSH
}
_dropdown`
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
new
gl
.
ProtectedBranchAccessDropdown
({
$dropdown
:
$allowedToPushDropdown
,
$dropdown
:
$allowedToPushDropdown
,
accessLevelsData
:
gon
.
push_access_levels
,
data
:
gon
.
push_access_levels
,
onSelect
:
this
.
onSelectCallback
,
onSelect
:
this
.
onSelectCallback
accessLevel
:
ACCESS_LEVELS
.
PUSH
});
});
// Select default
$allowedToPushDropdown
.
data
(
'
glDropdown
'
).
selectRowAtIndex
(
0
);
$allowedToMergeDropdown
.
data
(
'
glDropdown
'
).
selectRowAtIndex
(
0
);
// Protected branch dropdown
// Protected branch dropdown
new
window
.
ProtectedBranchDropdown
({
new
ProtectedBranchDropdown
({
$dropdown
:
this
.
$wrap
.
find
(
'
.js-protected-branch-select
'
),
$dropdown
:
this
.
$wrap
.
find
(
'
.js-protected-branch-select
'
),
onSelect
:
this
.
onSelectCallback
onSelect
:
this
.
onSelectCallback
});
});
}
}
//
Enable submit button after selecting an option
//
This will run after clicked callback
onSelect
()
{
onSelect
()
{
const
$allowedToMerge
=
this
[
`
${
ACCESS_LEVELS
.
MERGE
}
_dropdown`
].
getSelectedItems
();
// Enable submit button
const
$allowedToPush
=
this
[
`
${
ACCESS_LEVELS
.
PUSH
}
_dropdown`
].
getSelectedItems
();
const
$branchInput
=
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
);
const
toggle
=
!
(
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
).
val
()
&&
$allowedToMerge
.
length
&&
$allowedToPush
.
length
);
const
$allowedToMergeInput
=
this
.
$wrap
.
find
(
'
input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]
'
);
const
$allowedToPushInput
=
this
.
$wrap
.
find
(
'
input[name="protected_branch[push_access_levels_attributes][0][access_level]"]
'
);
this
.
$form
.
find
(
'
input[type="submit"]
'
).
attr
(
'
disabled
'
,
toggle
);
}
getFormData
()
{
const
formData
=
{
authenticity_token
:
this
.
$form
.
find
(
'
input[name="authenticity_token"]
'
).
val
(),
protected_branch
:
{
name
:
this
.
$wrap
.
find
(
'
input[name="protected_branch[name]"]
'
).
val
(),
}
};
for
(
const
ACCESS_LEVEL
in
ACCESS_LEVELS
)
{
this
.
$form
.
find
(
'
input[type="submit"]
'
).
attr
(
'
disabled
'
,
!
(
$branchInput
.
val
()
&&
$allowedToMergeInput
.
length
&&
$allowedToPushInput
.
length
));
const
selectedItems
=
this
[
`
${
ACCESS_LEVELS
[
ACCESS_LEVEL
]}
_dropdown`
].
getSelectedItems
();
const
levelAttributes
=
[];
for
(
let
i
=
0
;
i
<
selectedItems
.
length
;
i
+=
1
)
{
const
current
=
selectedItems
[
i
];
if
(
current
.
type
===
LEVEL_TYPES
.
USER
)
{
levelAttributes
.
push
({
user_id
:
selectedItems
[
i
].
user_id
});
}
else
if
(
current
.
type
===
LEVEL_TYPES
.
ROLE
)
{
levelAttributes
.
push
({
access_level
:
selectedItems
[
i
].
access_level
});
}
else
if
(
current
.
type
===
LEVEL_TYPES
.
GROUP
)
{
levelAttributes
.
push
({
group_id
:
selectedItems
[
i
].
group_id
});
}
}
formData
.
protected_branch
[
`
${
ACCESS_LEVELS
[
ACCESS_LEVEL
]}
_attributes`
]
=
levelAttributes
;
}
return
formData
;
}
onFormSubmit
(
e
)
{
e
.
preventDefault
();
$
.
ajax
({
url
:
this
.
$form
.
attr
(
'
action
'
),
method
:
this
.
$form
.
attr
(
'
method
'
),
data
:
this
.
getFormData
()
})
.
success
(()
=>
{
location
.
reload
();
})
.
fail
(()
=>
{
new
Flash
(
'
Failed to protect the branch
'
);
});
}
}
};
};
})(
window
);
})(
window
);
app/assets/javascripts/protected_branches/protected_branch_edit.js
View file @
ccac30cd
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle,
dot-notation, no-unused-vars, no-restricted-syntax, guard-for-in,
max-len */
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
/* global Flash */
/* global Flash */
(
global
=>
{
(
global
=>
{
global
.
gl
=
global
.
gl
||
{};
global
.
gl
=
global
.
gl
||
{};
const
ACCESS_LEVELS
=
{
MERGE
:
'
merge_access_levels
'
,
PUSH
:
'
push_access_levels
'
,
};
const
LEVEL_TYPES
=
{
ROLE
:
'
role
'
,
USER
:
'
user
'
,
GROUP
:
'
group
'
};
gl
.
ProtectedBranchEdit
=
class
{
gl
.
ProtectedBranchEdit
=
class
{
constructor
(
options
)
{
constructor
(
options
)
{
this
.
$wraps
=
{};
this
.
hasChanges
=
false
;
this
.
$wrap
=
options
.
$wrap
;
this
.
$wrap
=
options
.
$wrap
;
this
.
$allowedToMergeDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-merge
'
);
this
.
$allowedToMergeDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-merge
'
);
this
.
$allowedToPushDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-push
'
);
this
.
$allowedToPushDropdown
=
this
.
$wrap
.
find
(
'
.js-allowed-to-push
'
);
this
.
$wraps
[
ACCESS_LEVELS
.
MERGE
]
=
this
.
$allowedToMergeDropdown
.
closest
(
`.
${
ACCESS_LEVELS
.
MERGE
}
-container`
);
this
.
$wraps
[
ACCESS_LEVELS
.
PUSH
]
=
this
.
$allowedToPushDropdown
.
closest
(
`.
${
ACCESS_LEVELS
.
PUSH
}
-container`
);
this
.
buildDropdowns
();
this
.
buildDropdowns
();
}
}
buildDropdowns
()
{
buildDropdowns
()
{
// Allowed to merge dropdown
// Allowed to merge dropdown
this
[
'
merge_access_levels_dropdown
'
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
new
gl
.
ProtectedBranchAccessDropdown
({
accessLevel
:
ACCESS_LEVELS
.
MERGE
,
accessLevelsData
:
gon
.
merge_access_levels
,
$dropdown
:
this
.
$allowedToMergeDropdown
,
$dropdown
:
this
.
$allowedToMergeDropdown
,
onSelect
:
this
.
onSelectOption
.
bind
(
this
)
,
data
:
gon
.
merge_access_levels
,
on
Hide
:
this
.
onDropdownHide
.
bind
(
this
)
on
Select
:
this
.
onSelect
.
bind
(
this
)
});
});
// Allowed to push dropdown
// Allowed to push dropdown
this
[
'
push_access_levels_dropdown
'
]
=
new
gl
.
ProtectedBranchAccessDropdown
({
new
gl
.
ProtectedBranchAccessDropdown
({
accessLevel
:
ACCESS_LEVELS
.
PUSH
,
accessLevelsData
:
gon
.
push_access_levels
,
$dropdown
:
this
.
$allowedToPushDropdown
,
$dropdown
:
this
.
$allowedToPushDropdown
,
onSelect
:
this
.
onSelectOption
.
bind
(
this
)
,
data
:
gon
.
push_access_levels
,
on
Hide
:
this
.
onDropdownHide
.
bind
(
this
)
on
Select
:
this
.
onSelect
.
bind
(
this
)
});
});
}
}
onSelectOption
(
item
,
$el
,
dropdownInstance
)
{
onSelect
()
{
this
.
hasChanges
=
true
;
const
$allowedToMergeInput
=
this
.
$wrap
.
find
(
`input[name="
${
this
.
$allowedToMergeDropdown
.
data
(
'
fieldName
'
)}
"]`
);
}
const
$allowedToPushInput
=
this
.
$wrap
.
find
(
`input[name="
${
this
.
$allowedToPushDropdown
.
data
(
'
fieldName
'
)}
"]`
);
onDropdownHide
()
{
if
(
!
this
.
hasChanges
)
return
;
this
.
hasChanges
=
true
;
// Do not update if one dropdown has not selected any option
if
(
!
(
$allowedToMergeInput
.
length
&&
$allowedToPushInput
.
length
))
return
;
this
.
updatePermissions
();
this
.
$allowedToMergeDropdown
.
disable
();
}
this
.
$allowedToPushDropdown
.
disable
();
updatePermissions
()
{
const
formData
=
{};
for
(
const
ACCESS_LEVEL
in
ACCESS_LEVELS
)
{
$
.
ajax
({
const
accessLevelName
=
ACCESS_LEVELS
[
ACCESS_LEVEL
];
formData
[
`
${
accessLevelName
}
_attributes`
]
=
this
[
`
${
accessLevelName
}
_dropdown`
].
getInputData
(
accessLevelName
);
}
return
$
.
ajax
({
type
:
'
POST
'
,
type
:
'
POST
'
,
url
:
this
.
$wrap
.
data
(
'
url
'
),
url
:
this
.
$wrap
.
data
(
'
url
'
),
dataType
:
'
json
'
,
dataType
:
'
json
'
,
data
:
{
data
:
{
_method
:
'
PATCH
'
,
_method
:
'
PATCH
'
,
protected_branch
:
formData
protected_branch
:
{
},
merge_access_levels_attributes
:
[{
success
:
(
response
)
=>
{
id
:
this
.
$allowedToMergeDropdown
.
data
(
'
access-level-id
'
),
this
.
hasChanges
=
false
;
access_level
:
$allowedToMergeInput
.
val
()
}],
for
(
const
ACCESS_LEVEL
in
ACCESS_LEVELS
)
{
push_access_levels_attributes
:
[{
const
accessLevelName
=
ACCESS_LEVELS
[
ACCESS_LEVEL
];
id
:
this
.
$allowedToPushDropdown
.
data
(
'
access-level-id
'
),
access_level
:
$allowedToPushInput
.
val
()
// The data coming from server will be the new persisted *state* for each dropdown
}]
this
.
setSelectedItemsToDropdown
(
response
[
accessLevelName
],
`
${
accessLevelName
}
_dropdown`
);
}
}
},
},
error
()
{
error
()
{
...
@@ -97,49 +65,5 @@
...
@@ -97,49 +65,5 @@
this
.
$allowedToPushDropdown
.
enable
();
this
.
$allowedToPushDropdown
.
enable
();
});
});
}
}
setSelectedItemsToDropdown
(
items
=
[],
dropdownName
)
{
const
itemsToAdd
=
[];
for
(
let
i
=
0
;
i
<
items
.
length
;
i
+=
1
)
{
let
itemToAdd
;
const
currentItem
=
items
[
i
];
if
(
currentItem
.
user_id
)
{
// Do this only for users for now
// get the current data for selected items
const
selectedItems
=
this
[
dropdownName
].
getSelectedItems
();
const
currentSelectedItem
=
_
.
findWhere
(
selectedItems
,
{
user_id
:
currentItem
.
user_id
});
itemToAdd
=
{
id
:
currentItem
.
id
,
user_id
:
currentItem
.
user_id
,
type
:
LEVEL_TYPES
.
USER
,
persisted
:
true
,
name
:
currentSelectedItem
.
name
,
username
:
currentSelectedItem
.
username
,
avatar_url
:
currentSelectedItem
.
avatar_url
};
}
else
if
(
currentItem
.
group_id
)
{
itemToAdd
=
{
id
:
currentItem
.
id
,
group_id
:
currentItem
.
group_id
,
type
:
LEVEL_TYPES
.
GROUP
,
persisted
:
true
};
}
else
{
itemToAdd
=
{
id
:
currentItem
.
id
,
access_level
:
currentItem
.
access_level
,
type
:
LEVEL_TYPES
.
ROLE
,
persisted
:
true
};
}
itemsToAdd
.
push
(
itemToAdd
);
}
this
[
dropdownName
].
setSelectedItems
(
itemsToAdd
);
}
};
};
})(
window
);
})(
window
);
app/views/projects/protected_branches/_index.html.haml
View file @
ccac30cd
-
expanded
=
Rails
.
env
.
test?
-
expanded
=
Rails
.
env
.
test?
-
content_for
:page_specific_javascripts
do
-
content_for
:page_specific_javascripts
do
=
page_specific_javascript_bundle_tag
(
'protected_branches'
)
-
if
@project
.
feature_available?
(
:ref_permissions_for_users
,
current_user
)
=
webpack_bundle_tag
(
'ee_protected_branches'
)
-
else
=
webpack_bundle_tag
(
'protected_branches'
)
%section
.settings
%section
.settings
.settings-header
.settings-header
...
...
config/webpack.config.js
View file @
ccac30cd
...
@@ -60,6 +60,7 @@ var config = {
...
@@ -60,6 +60,7 @@ var config = {
profile
:
'
./profile/profile_bundle.js
'
,
profile
:
'
./profile/profile_bundle.js
'
,
prometheus_metrics
:
'
./prometheus_metrics
'
,
prometheus_metrics
:
'
./prometheus_metrics
'
,
protected_branches
:
'
./protected_branches/protected_branches_bundle.js
'
,
protected_branches
:
'
./protected_branches/protected_branches_bundle.js
'
,
ee_protected_branches
:
'
./protected_branches/ee/protected_branches_bundle.js
'
,
protected_tags
:
'
./protected_tags
'
,
protected_tags
:
'
./protected_tags
'
,
service_desk
:
'
./projects/settings_service_desk/service_desk_bundle.js
'
,
service_desk
:
'
./projects/settings_service_desk/service_desk_bundle.js
'
,
sidebar
:
'
./sidebar/sidebar_bundle.js
'
,
sidebar
:
'
./sidebar/sidebar_bundle.js
'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment