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
2b043b03
Commit
2b043b03
authored
Jul 30, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
23be6b41
3399b4c5
Changes
28
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
259 additions
and
180 deletions
+259
-180
app/assets/javascripts/admin/users/components/actions/delete.vue
...ets/javascripts/admin/users/components/actions/delete.vue
+1
-0
app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue
...in/users/components/actions/delete_with_contributions.vue
+1
-0
app/assets/javascripts/admin/users/components/actions/shared/shared_delete_action.vue
.../users/components/actions/shared/shared_delete_action.vue
+5
-1
app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
...ripts/boards/graphql/group_board_milestones.query.graphql
+0
-0
app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
...pts/boards/graphql/project_board_milestones.query.graphql
+0
-0
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+49
-0
app/assets/javascripts/boards/stores/mutation_types.js
app/assets/javascripts/boards/stores/mutation_types.js
+3
-0
app/assets/javascripts/boards/stores/mutations.js
app/assets/javascripts/boards/stores/mutations.js
+15
-1
app/assets/javascripts/boards/stores/state.js
app/assets/javascripts/boards/stores/state.js
+2
-0
app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
...sets/javascripts/diffs/components/diff_gutter_avatars.vue
+1
-0
app/assets/javascripts/notes/components/noteable_note.vue
app/assets/javascripts/notes/components/noteable_note.vue
+1
-0
app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
...ts/vue_shared/components/user_avatar/user_avatar_link.vue
+6
-0
app/helpers/admin/user_actions_helper.rb
app/helpers/admin/user_actions_helper.rb
+2
-2
app/helpers/users_helper.rb
app/helpers/users_helper.rb
+1
-1
doc/raketasks/backup_restore.md
doc/raketasks/backup_restore.md
+1
-1
doc/topics/autodevops/upgrading_postgresql.md
doc/topics/autodevops/upgrading_postgresql.md
+2
-2
ee/app/assets/javascripts/boards/stores/actions.js
ee/app/assets/javascripts/boards/stores/actions.js
+0
-46
ee/app/assets/javascripts/boards/stores/mutation_types.js
ee/app/assets/javascripts/boards/stores/mutation_types.js
+0
-3
ee/app/assets/javascripts/boards/stores/mutations.js
ee/app/assets/javascripts/boards/stores/mutations.js
+0
-14
ee/app/assets/javascripts/boards/stores/state.js
ee/app/assets/javascripts/boards/stores/state.js
+0
-2
ee/spec/frontend/boards/stores/actions_spec.js
ee/spec/frontend/boards/stores/actions_spec.js
+0
-81
spec/features/admin/users/user_spec.rb
spec/features/admin/users/user_spec.rb
+33
-0
spec/frontend/admin/users/components/actions/actions_spec.js
spec/frontend/admin/users/components/actions/actions_spec.js
+25
-24
spec/frontend/admin/users/mock_data.js
spec/frontend/admin/users/mock_data.js
+1
-1
spec/frontend/boards/mock_data.js
spec/frontend/boards/mock_data.js
+11
-0
spec/frontend/boards/stores/actions_spec.js
spec/frontend/boards/stores/actions_spec.js
+87
-0
spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
...ue_shared/components/user_avatar/user_avatar_link_spec.js
+11
-0
spec/helpers/admin/user_actions_helper_spec.rb
spec/helpers/admin/user_actions_helper_spec.rb
+1
-1
No files found.
app/assets/javascripts/admin/users/components/actions/delete.vue
View file @
2b043b03
...
...
@@ -28,6 +28,7 @@ export default {
modal-type=
"delete"
:username=
"username"
:paths=
"paths"
:delete-path=
"paths.delete"
:oncall-schedules=
"oncallSchedules"
>
<slot></slot>
...
...
app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue
View file @
2b043b03
...
...
@@ -28,6 +28,7 @@ export default {
modal-type=
"delete-with-contributions"
:username=
"username"
:paths=
"paths"
:delete-path=
"paths.deleteWithContributions"
:oncall-schedules=
"oncallSchedules"
>
<slot></slot>
...
...
app/assets/javascripts/admin/users/components/actions/shared/shared_delete_action.vue
View file @
2b043b03
...
...
@@ -14,6 +14,10 @@ export default {
type
:
Object
,
required
:
true
,
},
deletePath
:
{
type
:
String
,
required
:
true
,
},
modalType
:
{
type
:
String
,
required
:
true
,
...
...
@@ -27,7 +31,7 @@ export default {
modalAttributes
()
{
return
{
'
data-block-user-url
'
:
this
.
paths
.
block
,
'
data-delete-user-url
'
:
this
.
paths
.
delete
,
'
data-delete-user-url
'
:
this
.
deletePath
,
'
data-gl-modal-action
'
:
this
.
modalType
,
'
data-username
'
:
this
.
username
,
'
data-oncall-schedules
'
:
JSON
.
stringify
(
this
.
oncallSchedules
),
...
...
ee/
app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
→
app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
View file @
2b043b03
File moved
ee/
app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
→
app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
View file @
2b043b03
File moved
app/assets/javascripts/boards/stores/actions.js
View file @
2b043b03
...
...
@@ -36,10 +36,13 @@ import {
filterVariables
,
}
from
'
../boards_util
'
;
import
boardLabelsQuery
from
'
../graphql/board_labels.query.graphql
'
;
import
groupBoardMilestonesQuery
from
'
../graphql/group_board_milestones.query.graphql
'
;
import
groupProjectsQuery
from
'
../graphql/group_projects.query.graphql
'
;
import
issueCreateMutation
from
'
../graphql/issue_create.mutation.graphql
'
;
import
issueSetLabelsMutation
from
'
../graphql/issue_set_labels.mutation.graphql
'
;
import
listsIssuesQuery
from
'
../graphql/lists_issues.query.graphql
'
;
import
projectBoardMilestonesQuery
from
'
../graphql/project_board_milestones.query.graphql
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
const
gqlClient
=
createGqClient
(
...
...
@@ -216,6 +219,52 @@ export default {
});
},
fetchMilestones
({
state
,
commit
},
searchTerm
)
{
commit
(
types
.
RECEIVE_MILESTONES_REQUEST
);
const
{
fullPath
,
boardType
}
=
state
;
const
variables
=
{
fullPath
,
searchTerm
,
};
let
query
;
if
(
boardType
===
BoardType
.
project
)
{
query
=
projectBoardMilestonesQuery
;
}
if
(
boardType
===
BoardType
.
group
)
{
query
=
groupBoardMilestonesQuery
;
}
if
(
!
query
)
{
// eslint-disable-next-line @gitlab/require-i18n-strings
throw
new
Error
(
'
Unknown board type
'
);
}
return
gqlClient
.
query
({
query
,
variables
,
})
.
then
(({
data
})
=>
{
const
errors
=
data
[
boardType
]?.
errors
;
const
milestones
=
data
[
boardType
]?.
milestones
.
nodes
;
if
(
errors
?.[
0
])
{
throw
new
Error
(
errors
[
0
]);
}
commit
(
types
.
RECEIVE_MILESTONES_SUCCESS
,
milestones
);
return
milestones
;
})
.
catch
((
e
)
=>
{
commit
(
types
.
RECEIVE_MILESTONES_FAILURE
);
throw
e
;
});
},
moveList
:
(
{
state
:
{
boardLists
},
commit
,
dispatch
},
{
...
...
app/assets/javascripts/boards/stores/mutation_types.js
View file @
2b043b03
...
...
@@ -18,6 +18,9 @@ export const RESET_ITEMS_FOR_LIST = 'RESET_ITEMS_FOR_LIST';
export
const
REQUEST_ITEMS_FOR_LIST
=
'
REQUEST_ITEMS_FOR_LIST
'
;
export
const
RECEIVE_ITEMS_FOR_LIST_FAILURE
=
'
RECEIVE_ITEMS_FOR_LIST_FAILURE
'
;
export
const
RECEIVE_ITEMS_FOR_LIST_SUCCESS
=
'
RECEIVE_ITEMS_FOR_LIST_SUCCESS
'
;
export
const
RECEIVE_MILESTONES_REQUEST
=
'
RECEIVE_MILESTONES_REQUEST
'
;
export
const
RECEIVE_MILESTONES_SUCCESS
=
'
RECEIVE_MILESTONES_SUCCESS
'
;
export
const
RECEIVE_MILESTONES_FAILURE
=
'
RECEIVE_MILESTONES_FAILURE
'
;
export
const
UPDATE_BOARD_ITEM
=
'
UPDATE_BOARD_ITEM
'
;
export
const
REMOVE_BOARD_ITEM
=
'
REMOVE_BOARD_ITEM
'
;
export
const
MUTATE_ISSUE_SUCCESS
=
'
MUTATE_ISSUE_SUCCESS
'
;
...
...
app/assets/javascripts/boards/stores/mutations.js
View file @
2b043b03
import
{
cloneDeep
,
pull
,
union
}
from
'
lodash
'
;
import
Vue
from
'
vue
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
formatIssue
}
from
'
../boards_util
'
;
import
{
issuableTypes
}
from
'
../constants
'
;
import
*
as
mutationTypes
from
'
./mutation_types
'
;
...
...
@@ -133,6 +133,20 @@ export default {
Vue
.
set
(
state
.
listsFlags
,
listId
,
{
[
fetchNext
?
'
isLoadingMore
'
:
'
isLoading
'
]:
true
});
},
[
mutationTypes
.
RECEIVE_MILESTONES_SUCCESS
](
state
,
milestones
)
{
state
.
milestones
=
milestones
;
state
.
milestonesLoading
=
false
;
},
[
mutationTypes
.
RECEIVE_MILESTONES_REQUEST
](
state
)
{
state
.
milestonesLoading
=
true
;
},
[
mutationTypes
.
RECEIVE_MILESTONES_FAILURE
](
state
)
{
state
.
milestonesLoading
=
false
;
state
.
error
=
__
(
'
Failed to load milestones.
'
);
},
[
mutationTypes
.
RECEIVE_ITEMS_FOR_LIST_SUCCESS
]:
(
state
,
{
listItems
,
listPageInfo
,
listId
})
=>
{
const
{
listData
,
boardItems
}
=
listItems
;
Vue
.
set
(
state
,
'
boardItems
'
,
{
...
state
.
boardItems
,
...
boardItems
});
...
...
app/assets/javascripts/boards/stores/state.js
View file @
2b043b03
...
...
@@ -19,6 +19,8 @@ export default () => ({
boardConfig
:
{},
labelsLoading
:
false
,
labels
:
[],
milestones
:
[],
milestonesLoading
:
false
,
highlightedLists
:
[],
selectedBoardItems
:
[],
groupProjects
:
[],
...
...
app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
View file @
2b043b03
...
...
@@ -75,6 +75,7 @@ export default {
:key=
"note.id"
:img-src=
"note.author.avatar_url"
:tooltip-text=
"getTooltipText(note)"
lazy
class=
"diff-comment-avatar js-diff-comment-avatar"
@
click.native=
"$emit('toggleLineDiscussions')"
/>
...
...
app/assets/javascripts/notes/components/noteable_note.vue
View file @
2b043b03
...
...
@@ -392,6 +392,7 @@ export default {
:img-src=
"author.avatar_url"
:img-alt=
"author.name"
:img-size=
"40"
lazy
>
<
template
#avatar-badge
>
<slot
name=
"avatar-badge"
></slot>
...
...
app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
View file @
2b043b03
...
...
@@ -30,6 +30,11 @@ export default {
GlTooltip
:
GlTooltipDirective
,
},
props
:
{
lazy
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
linkHref
:
{
type
:
String
,
required
:
false
,
...
...
@@ -91,6 +96,7 @@ export default {
:size=
"imgSize"
:tooltip-text=
"avatarTooltipText"
:tooltip-placement=
"tooltipPlacement"
:lazy=
"lazy"
>
<slot></slot>
</user-avatar-image
><span
...
...
app/helpers/admin/user_actions_helper.rb
View file @
2b043b03
...
...
@@ -48,9 +48,9 @@ module Admin
end
def
delete_actions
return
unless
can?
(
current_user
,
:destroy_user
,
@user
)
&&
!
@user
.
blocked_pending_approval?
&&
@user
.
can_be_removed?
return
unless
can?
(
current_user
,
:destroy_user
,
@user
)
&&
!
@user
.
blocked_pending_approval?
@actions
<<
'delete'
@actions
<<
'delete'
if
@user
.
can_be_removed?
@actions
<<
'delete_with_contributions'
end
...
...
app/helpers/users_helper.rb
View file @
2b043b03
...
...
@@ -184,7 +184,7 @@ module UsersHelper
activate:
activate_admin_user_path
(
:id
),
unlock:
unlock_admin_user_path
(
:id
),
delete:
admin_user_path
(
:id
),
delete_with_contributions:
admin_user_path
(
:id
),
delete_with_contributions:
admin_user_path
(
:id
,
hard_delete:
true
),
admin_user:
admin_user_path
(
:id
),
ban:
ban_admin_user_path
(
:id
),
unban:
unban_admin_user_path
(
:id
)
...
...
doc/raketasks/backup_restore.md
View file @
2b043b03
...
...
@@ -119,7 +119,7 @@ script on the GitLab task runner pod. For more details, see
[
backing up a GitLab installation
](
https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation
)
.
```
shell
kubectl
exec
-it
<gitlab task-runner pod> backup-utility
kubectl
exec
-it
<gitlab task-runner pod>
--
backup-utility
```
Similar to the Kubernetes case, if you have scaled out your GitLab cluster to
...
...
doc/topics/autodevops/upgrading_postgresql.md
View file @
2b043b03
...
...
@@ -103,7 +103,7 @@ being modified after the database dump is created.
1.
Connect to the pod with:
```shell
kubectl exec -it production-postgres-5db86568d7-qxlxv --namespace "$APP_NAMESPACE" bash
kubectl exec -it production-postgres-5db86568d7-qxlxv --namespace "$APP_NAMESPACE"
--
bash
```
1.
Once, connected, create a dump file with the following command.
...
...
@@ -221,7 +221,7 @@ higher*. This is the
1.
Connect to the pod:
```
shell
kubectl
exec
-it
production-postgresql-0
--namespace
"
$APP_NAMESPACE
"
bash
kubectl
exec
-it
production-postgresql-0
--namespace
"
$APP_NAMESPACE
"
--
bash
```
1.
Once connected to the pod, run the following command to restore the database.
...
...
ee/app/assets/javascripts/boards/stores/actions.js
View file @
2b043b03
...
...
@@ -34,11 +34,9 @@ import epicCreateMutation from '../graphql/epic_create.mutation.graphql';
import
epicMoveListMutation
from
'
../graphql/epic_move_list.mutation.graphql
'
;
import
epicsSwimlanesQuery
from
'
../graphql/epics_swimlanes.query.graphql
'
;
import
groupBoardIterationsQuery
from
'
../graphql/group_board_iterations.query.graphql
'
;
import
groupBoardMilestonesQuery
from
'
../graphql/group_board_milestones.query.graphql
'
;
import
listUpdateLimitMetricsMutation
from
'
../graphql/list_update_limit_metrics.mutation.graphql
'
;
import
listsEpicsQuery
from
'
../graphql/lists_epics.query.graphql
'
;
import
projectBoardIterationsQuery
from
'
../graphql/project_board_iterations.query.graphql
'
;
import
projectBoardMilestonesQuery
from
'
../graphql/project_board_milestones.query.graphql
'
;
import
updateBoardEpicUserPreferencesMutation
from
'
../graphql/update_board_epic_user_preferences.mutation.graphql
'
;
import
updateEpicLabelsMutation
from
'
../graphql/update_epic_labels.mutation.graphql
'
;
...
...
@@ -424,50 +422,6 @@ export default {
);
},
fetchMilestones
({
state
,
commit
},
searchTerm
)
{
commit
(
types
.
RECEIVE_MILESTONES_REQUEST
);
const
{
fullPath
,
boardType
}
=
state
;
const
variables
=
{
fullPath
,
searchTerm
,
};
let
query
;
if
(
boardType
===
BoardType
.
project
)
{
query
=
projectBoardMilestonesQuery
;
}
if
(
boardType
===
BoardType
.
group
)
{
query
=
groupBoardMilestonesQuery
;
}
if
(
!
query
)
{
// eslint-disable-next-line @gitlab/require-i18n-strings
throw
new
Error
(
'
Unknown board type
'
);
}
return
gqlClient
.
query
({
query
,
variables
,
})
.
then
(({
data
})
=>
{
const
errors
=
data
[
boardType
]?.
errors
;
const
milestones
=
data
[
boardType
]?.
milestones
.
nodes
;
if
(
errors
?.[
0
])
{
throw
new
Error
(
errors
[
0
]);
}
commit
(
types
.
RECEIVE_MILESTONES_SUCCESS
,
milestones
);
})
.
catch
((
e
)
=>
{
commit
(
types
.
RECEIVE_MILESTONES_FAILURE
);
throw
e
;
});
},
fetchIterations
({
state
,
commit
},
title
)
{
commit
(
types
.
RECEIVE_ITERATIONS_REQUEST
);
...
...
ee/app/assets/javascripts/boards/stores/mutation_types.js
View file @
2b043b03
...
...
@@ -15,9 +15,6 @@ export const SET_SHOW_LABELS = 'SET_SHOW_LABELS';
export
const
MOVE_EPIC
=
'
MOVE_EPIC
'
;
export
const
MOVE_EPIC_FAILURE
=
'
MOVE_EPIC_FAILURE
'
;
export
const
SET_BOARD_EPIC_USER_PREFERENCES
=
'
SET_BOARD_EPIC_USER_PREFERENCES
'
;
export
const
RECEIVE_MILESTONES_REQUEST
=
'
RECEIVE_MILESTONES_REQUEST
'
;
export
const
RECEIVE_MILESTONES_SUCCESS
=
'
RECEIVE_MILESTONES_SUCCESS
'
;
export
const
RECEIVE_MILESTONES_FAILURE
=
'
RECEIVE_MILESTONES_FAILURE
'
;
export
const
RECEIVE_ITERATIONS_REQUEST
=
'
RECEIVE_ITERATIONS_REQUEST
'
;
export
const
RECEIVE_ITERATIONS_SUCCESS
=
'
RECEIVE_ITERATIONS_SUCCESS
'
;
export
const
RECEIVE_ITERATIONS_FAILURE
=
'
RECEIVE_ITERATIONS_FAILURE
'
;
...
...
ee/app/assets/javascripts/boards/stores/mutations.js
View file @
2b043b03
...
...
@@ -171,20 +171,6 @@ export default {
}
},
[
mutationTypes
.
RECEIVE_MILESTONES_REQUEST
](
state
)
{
state
.
milestonesLoading
=
true
;
},
[
mutationTypes
.
RECEIVE_MILESTONES_SUCCESS
](
state
,
milestones
)
{
state
.
milestones
=
milestones
;
state
.
milestonesLoading
=
false
;
},
[
mutationTypes
.
RECEIVE_MILESTONES_FAILURE
](
state
)
{
state
.
milestonesLoading
=
false
;
state
.
error
=
__
(
'
Failed to load milestones.
'
);
},
[
mutationTypes
.
RECEIVE_ITERATIONS_REQUEST
](
state
)
{
state
.
iterationsLoading
=
true
;
},
...
...
ee/app/assets/javascripts/boards/stores/state.js
View file @
2b043b03
...
...
@@ -15,8 +15,6 @@ export default () => ({
epicsEndCursor
:
null
,
epics
:
[],
epicsFlags
:
{},
milestones
:
[],
milestonesLoading
:
false
,
iterations
:
[],
iterationsLoading
:
false
,
assignees
:
[],
...
...
ee/spec/frontend/boards/stores/actions_spec.js
View file @
2b043b03
...
...
@@ -1120,87 +1120,6 @@ describe('addListNewEpic', () => {
});
});
describe
(
'
fetchMilestones
'
,
()
=>
{
const
queryResponse
=
{
data
:
{
project
:
{
milestones
:
{
nodes
:
mockMilestones
,
},
},
},
};
const
queryErrors
=
{
data
:
{
project
:
{
errors
:
[
'
You cannot view these milestones
'
],
milestones
:
{},
},
},
};
function
createStore
({
state
=
{
boardType
:
'
project
'
,
fullPath
:
'
gitlab-org/gitlab
'
,
milestones
:
[],
milestonesLoading
:
false
,
},
}
=
{})
{
return
new
Vuex
.
Store
({
state
,
mutations
,
});
}
it
(
'
throws error if state.boardType is not group or project
'
,
()
=>
{
const
store
=
createStore
({
state
:
{
boardType
:
'
invalid
'
,
},
});
expect
(()
=>
actions
.
fetchMilestones
(
store
)).
toThrow
(
new
Error
(
'
Unknown board type
'
));
});
it
(
'
sets milestonesLoading to true
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
const
store
=
createStore
();
actions
.
fetchMilestones
(
store
);
expect
(
store
.
state
.
milestonesLoading
).
toBe
(
true
);
});
describe
(
'
success
'
,
()
=>
{
it
(
'
sets state.milestones from query result
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
const
store
=
createStore
();
await
actions
.
fetchMilestones
(
store
);
expect
(
store
.
state
.
milestonesLoading
).
toBe
(
false
);
expect
(
store
.
state
.
milestones
).
toBe
(
mockMilestones
);
});
});
describe
(
'
failure
'
,
()
=>
{
it
(
'
sets state.milestones from query result
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryErrors
);
const
store
=
createStore
();
await
expect
(
actions
.
fetchMilestones
(
store
)).
rejects
.
toThrow
();
expect
(
store
.
state
.
milestonesLoading
).
toBe
(
false
);
expect
(
store
.
state
.
error
).
toBe
(
'
Failed to load milestones.
'
);
});
});
});
describe
(
'
fetchIterations
'
,
()
=>
{
const
queryResponse
=
{
data
:
{
...
...
spec/features/admin/users/user_spec.rb
View file @
2b043b03
...
...
@@ -90,6 +90,39 @@ RSpec.describe 'Admin::Users::User' do
end
end
context
'when user is the sole owner of a group'
do
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:user_sole_owner_of_group
)
{
create
(
:user
)
}
before
do
group
.
add_owner
(
user_sole_owner_of_group
)
end
it
'shows `Delete user and contributions` action but not `Delete user` action'
,
:js
do
visit
admin_user_path
(
user_sole_owner_of_group
)
click_user_dropdown_toggle
(
user_sole_owner_of_group
.
id
)
expect
(
page
).
to
have_button
(
'Delete user and contributions'
)
expect
(
page
).
not_to
have_button
(
'Delete user'
,
exact:
true
)
end
it
'allows user to be deleted by using the `Delete user and contributions` action'
,
:js
do
visit
admin_user_path
(
user_sole_owner_of_group
)
click_action_in_user_dropdown
(
user_sole_owner_of_group
.
id
,
'Delete user and contributions'
)
page
.
within
(
'[role="dialog"]'
)
do
fill_in
(
'username'
,
with:
user_sole_owner_of_group
.
name
)
click_button
(
'Delete user and contributions'
)
end
wait_for_requests
expect
(
page
).
to
have_content
(
'The user is being deleted.'
)
end
end
describe
'Impersonation'
do
let_it_be
(
:another_user
)
{
create
(
:user
)
}
...
...
spec/frontend/admin/users/components/actions/actions_spec.js
View file @
2b043b03
...
...
@@ -5,8 +5,8 @@ import { nextTick } from 'vue';
import
Actions
from
'
~/admin/users/components/actions
'
;
import
SharedDeleteAction
from
'
~/admin/users/components/actions/shared/shared_delete_action.vue
'
;
import
{
capitalizeFirstCharacter
}
from
'
~/lib/utils/text_utility
'
;
import
{
CONFIRMATION_ACTIONS
,
DELETE_ACTIONS
}
from
'
../../constants
'
;
import
{
paths
}
from
'
../../mock_data
'
;
describe
(
'
Action components
'
,
()
=>
{
let
wrapper
;
...
...
@@ -47,32 +47,33 @@ describe('Action components', () => {
describe
(
'
DELETE_ACTION_COMPONENTS
'
,
()
=>
{
const
oncallSchedules
=
[{
name
:
'
schedule1
'
},
{
name
:
'
schedule2
'
}];
it
.
each
(
DELETE_ACTIONS
)(
'
renders a dropdown item for "%s"
'
,
async
(
action
)
=>
{
initComponent
({
component
:
Actions
[
capitalizeFirstCharacter
(
action
)],
props
:
{
username
:
'
John Doe
'
,
paths
:
{
delete
:
'
/delete
'
,
block
:
'
/block
'
,
it
.
each
(
DELETE_ACTIONS
.
map
((
action
)
=>
[
action
,
paths
[
action
]]))(
'
renders a dropdown item for "%s"
'
,
async
(
action
,
expectedPath
)
=>
{
initComponent
({
component
:
Actions
[
capitalizeFirstCharacter
(
action
)],
props
:
{
username
:
'
John Doe
'
,
paths
,
oncallSchedules
,
},
oncallSchedules
,
},
stubs
:
{
SharedDeleteAction
},
});
stubs
:
{
SharedDeleteAction
},
});
await
nextTick
();
await
nextTick
();
const
sharedAction
=
wrapper
.
find
(
SharedDeleteAction
);
const
sharedAction
=
wrapper
.
find
(
SharedDeleteAction
);
expect
(
sharedAction
.
attributes
(
'
data-block-user-url
'
)).
toBe
(
'
/block
'
);
expect
(
sharedAction
.
attributes
(
'
data-delete-user-url
'
)).
toBe
(
'
/delete
'
);
expect
(
sharedAction
.
attributes
(
'
data-gl-modal-action
'
)).
toBe
(
kebabCase
(
action
));
expect
(
sharedAction
.
attributes
(
'
data-username
'
)).
toBe
(
'
John Doe
'
);
expect
(
sharedAction
.
attributes
(
'
data-oncall-schedules
'
)).
toBe
(
JSON
.
stringify
(
oncallSchedules
),
);
expect
(
findDropdownItem
().
exists
()).
toBe
(
true
);
});
expect
(
sharedAction
.
attributes
(
'
data-block-user-url
'
)).
toBe
(
paths
.
block
);
expect
(
sharedAction
.
attributes
(
'
data-delete-user-url
'
)).
toBe
(
expectedPath
);
expect
(
sharedAction
.
attributes
(
'
data-gl-modal-action
'
)).
toBe
(
kebabCase
(
action
));
expect
(
sharedAction
.
attributes
(
'
data-username
'
)).
toBe
(
'
John Doe
'
);
expect
(
sharedAction
.
attributes
(
'
data-oncall-schedules
'
)).
toBe
(
JSON
.
stringify
(
oncallSchedules
),
);
expect
(
findDropdownItem
().
exists
()).
toBe
(
true
);
},
);
});
});
spec/frontend/admin/users/mock_data.js
View file @
2b043b03
...
...
@@ -30,7 +30,7 @@ export const paths = {
activate
:
'
/admin/users/id/activate
'
,
unlock
:
'
/admin/users/id/unlock
'
,
delete
:
'
/admin/users/id
'
,
deleteWithContributions
:
'
/admin/users/id
'
,
deleteWithContributions
:
'
/admin/users/id
?hard_delete=true
'
,
adminUser
:
'
/admin/users/id
'
,
ban
:
'
/admin/users/id/ban
'
,
unban
:
'
/admin/users/id/unban
'
,
...
...
spec/frontend/boards/mock_data.js
View file @
2b043b03
...
...
@@ -101,6 +101,17 @@ export const mockMilestone = {
due_date
:
'
2019-12-31
'
,
};
export
const
mockMilestones
=
[
{
id
:
'
gid://gitlab/Milestone/1
'
,
title
:
'
Milestone 1
'
,
},
{
id
:
'
gid://gitlab/Milestone/2
'
,
title
:
'
Milestone 2
'
,
},
];
export
const
assignees
=
[
{
id
:
'
gid://gitlab/User/2
'
,
...
...
spec/frontend/boards/stores/actions_spec.js
View file @
2b043b03
import
*
as
Sentry
from
'
@sentry/browser
'
;
import
{
cloneDeep
}
from
'
lodash
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
{
inactiveId
,
ISSUABLE
,
...
...
@@ -22,6 +24,7 @@ import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutati
import
issueCreateMutation
from
'
~/boards/graphql/issue_create.mutation.graphql
'
;
import
actions
,
{
gqlClient
}
from
'
~/boards/stores/actions
'
;
import
*
as
types
from
'
~/boards/stores/mutation_types
'
;
import
mutations
from
'
~/boards/stores/mutations
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
...
...
@@ -38,6 +41,7 @@ import {
mockMoveState
,
mockMoveData
,
mockList
,
mockMilestones
,
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
...
...
@@ -46,6 +50,8 @@ jest.mock('~/flash');
// subgroups when the movIssue action is called.
const
getProjectPath
=
(
path
)
=>
path
.
split
(
'
#
'
)[
0
];
Vue
.
use
(
Vuex
);
beforeEach
(()
=>
{
window
.
gon
=
{
features
:
{}
};
});
...
...
@@ -261,6 +267,87 @@ describe('fetchLists', () => {
);
});
describe
(
'
fetchMilestones
'
,
()
=>
{
const
queryResponse
=
{
data
:
{
project
:
{
milestones
:
{
nodes
:
mockMilestones
,
},
},
},
};
const
queryErrors
=
{
data
:
{
project
:
{
errors
:
[
'
You cannot view these milestones
'
],
milestones
:
{},
},
},
};
function
createStore
({
state
=
{
boardType
:
'
project
'
,
fullPath
:
'
gitlab-org/gitlab
'
,
milestones
:
[],
milestonesLoading
:
false
,
},
}
=
{})
{
return
new
Vuex
.
Store
({
state
,
mutations
,
});
}
it
(
'
throws error if state.boardType is not group or project
'
,
()
=>
{
const
store
=
createStore
({
state
:
{
boardType
:
'
invalid
'
,
},
});
expect
(()
=>
actions
.
fetchMilestones
(
store
)).
toThrow
(
new
Error
(
'
Unknown board type
'
));
});
it
(
'
sets milestonesLoading to true
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
const
store
=
createStore
();
actions
.
fetchMilestones
(
store
);
expect
(
store
.
state
.
milestonesLoading
).
toBe
(
true
);
});
describe
(
'
success
'
,
()
=>
{
it
(
'
sets state.milestones from query result
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
const
store
=
createStore
();
await
actions
.
fetchMilestones
(
store
);
expect
(
store
.
state
.
milestonesLoading
).
toBe
(
false
);
expect
(
store
.
state
.
milestones
).
toBe
(
mockMilestones
);
});
});
describe
(
'
failure
'
,
()
=>
{
it
(
'
sets state.milestones from query result
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryErrors
);
const
store
=
createStore
();
await
expect
(
actions
.
fetchMilestones
(
store
)).
rejects
.
toThrow
();
expect
(
store
.
state
.
milestonesLoading
).
toBe
(
false
);
expect
(
store
.
state
.
error
).
toBe
(
'
Failed to load milestones.
'
);
});
});
});
describe
(
'
createList
'
,
()
=>
{
it
(
'
should dispatch createIssueList action
'
,
()
=>
{
testAction
({
...
...
spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
View file @
2b043b03
...
...
@@ -104,4 +104,15 @@ describe('User Avatar Link Component', () => {
);
});
});
describe
(
'
lazy
'
,
()
=>
{
it
(
'
passes lazy prop to avatar image
'
,
()
=>
{
createWrapper
({
username
:
''
,
lazy
:
true
,
});
expect
(
wrapper
.
find
(
UserAvatarImage
).
props
(
'
lazy
'
)).
toBe
(
true
);
});
});
});
spec/helpers/admin/user_actions_helper_spec.rb
View file @
2b043b03
...
...
@@ -106,7 +106,7 @@ RSpec.describe Admin::UserActionsHelper do
group
.
add_owner
(
user
)
end
it
{
is_expected
.
to
contain_exactly
(
"edit"
,
"block"
,
"ban"
,
"deactivate"
)
}
it
{
is_expected
.
to
contain_exactly
(
"edit"
,
"block"
,
"ban"
,
"deactivate"
,
"delete_with_contributions"
)
}
end
context
'the user is a bot'
do
...
...
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