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
0
Merge Requests
0
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
Jérome Perrin
gitlab-ce
Commits
31dd86b6
Commit
31dd86b6
authored
6 years ago
by
Francisco Javier López
Committed by
Douwe Maan
6 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Projects and groups badges settings UI
parent
dd552d06
Changes
46
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
46 changed files
with
3021 additions
and
13 deletions
+3021
-13
app/assets/javascripts/badges/components/badge.vue
app/assets/javascripts/badges/components/badge.vue
+121
-0
app/assets/javascripts/badges/components/badge_form.vue
app/assets/javascripts/badges/components/badge_form.vue
+219
-0
app/assets/javascripts/badges/components/badge_list.vue
app/assets/javascripts/badges/components/badge_list.vue
+57
-0
app/assets/javascripts/badges/components/badge_list_row.vue
app/assets/javascripts/badges/components/badge_list_row.vue
+89
-0
app/assets/javascripts/badges/components/badge_settings.vue
app/assets/javascripts/badges/components/badge_settings.vue
+70
-0
app/assets/javascripts/badges/constants.js
app/assets/javascripts/badges/constants.js
+2
-0
app/assets/javascripts/badges/empty_badge.js
app/assets/javascripts/badges/empty_badge.js
+7
-0
app/assets/javascripts/badges/store/actions.js
app/assets/javascripts/badges/store/actions.js
+167
-0
app/assets/javascripts/badges/store/index.js
app/assets/javascripts/badges/store/index.js
+13
-0
app/assets/javascripts/badges/store/mutation_types.js
app/assets/javascripts/badges/store/mutation_types.js
+21
-0
app/assets/javascripts/badges/store/mutations.js
app/assets/javascripts/badges/store/mutations.js
+158
-0
app/assets/javascripts/badges/store/state.js
app/assets/javascripts/badges/store/state.js
+13
-0
app/assets/javascripts/pages/groups/settings/badges/index.js
app/assets/javascripts/pages/groups/settings/badges/index.js
+10
-0
app/assets/javascripts/pages/projects/settings/badges/index/index.js
...javascripts/pages/projects/settings/badges/index/index.js
+10
-0
app/assets/javascripts/pages/shared/mount_badge_settings.js
app/assets/javascripts/pages/shared/mount_badge_settings.js
+24
-0
app/assets/stylesheets/framework/responsive_tables.scss
app/assets/stylesheets/framework/responsive_tables.scss
+1
-1
app/assets/stylesheets/pages/projects.scss
app/assets/stylesheets/pages/projects.scss
+8
-0
app/controllers/groups/settings/badges_controller.rb
app/controllers/groups/settings/badges_controller.rb
+13
-0
app/controllers/projects/settings/badges_controller.rb
app/controllers/projects/settings/badges_controller.rb
+13
-0
app/helpers/groups_helper.rb
app/helpers/groups_helper.rb
+1
-1
app/views/groups/settings/badges/index.html.haml
app/views/groups/settings/badges/index.html.haml
+4
-0
app/views/layouts/nav/sidebar/_group.html.haml
app/views/layouts/nav/sidebar/_group.html.haml
+7
-1
app/views/layouts/nav/sidebar/_project.html.haml
app/views/layouts/nav/sidebar/_project.html.haml
+7
-2
app/views/projects/_home_panel.html.haml
app/views/projects/_home_panel.html.haml
+7
-4
app/views/projects/settings/badges/index.html.haml
app/views/projects/settings/badges/index.html.haml
+4
-0
app/views/shared/badges/_badge_settings.html.haml
app/views/shared/badges/_badge_settings.html.haml
+4
-0
changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml
...elogs/unreleased/winh-41174-projects-groups-badges-ui.yml
+5
-0
config/karma.config.js
config/karma.config.js
+1
-1
config/routes/group.rb
config/routes/group.rb
+1
-0
config/routes/project.rb
config/routes/project.rb
+1
-0
lib/api/badges.rb
lib/api/badges.rb
+1
-0
spec/features/groups/settings/group_badges_spec.rb
spec/features/groups/settings/group_badges_spec.rb
+124
-0
spec/features/projects/settings/project_badges_spec.rb
spec/features/projects/settings/project_badges_spec.rb
+125
-0
spec/javascripts/badges/components/badge_form_spec.js
spec/javascripts/badges/components/badge_form_spec.js
+171
-0
spec/javascripts/badges/components/badge_list_row_spec.js
spec/javascripts/badges/components/badge_list_row_spec.js
+97
-0
spec/javascripts/badges/components/badge_list_spec.js
spec/javascripts/badges/components/badge_list_spec.js
+88
-0
spec/javascripts/badges/components/badge_settings_spec.js
spec/javascripts/badges/components/badge_settings_spec.js
+109
-0
spec/javascripts/badges/components/badge_spec.js
spec/javascripts/badges/components/badge_spec.js
+147
-0
spec/javascripts/badges/dummy_badge.js
spec/javascripts/badges/dummy_badge.js
+23
-0
spec/javascripts/badges/store/actions_spec.js
spec/javascripts/badges/store/actions_spec.js
+607
-0
spec/javascripts/badges/store/mutations_spec.js
spec/javascripts/badges/store/mutations_spec.js
+418
-0
spec/javascripts/fixtures/one_white_pixel.png
spec/javascripts/fixtures/one_white_pixel.png
+0
-0
spec/javascripts/helpers/vue_mount_component_helper.js
spec/javascripts/helpers/vue_mount_component_helper.js
+6
-0
spec/javascripts/matchers.js
spec/javascripts/matchers.js
+35
-0
spec/javascripts/test_bundle.js
spec/javascripts/test_bundle.js
+8
-3
spec/javascripts/test_constants.js
spec/javascripts/test_constants.js
+4
-0
No files found.
app/assets/javascripts/badges/components/badge.vue
0 → 100644
View file @
31dd86b6
<
script
>
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
Tooltip
from
'
~/vue_shared/directives/tooltip
'
;
export
default
{
name
:
'
Badge
'
,
components
:
{
Icon
,
LoadingIcon
,
Tooltip
,
},
directives
:
{
Tooltip
,
},
props
:
{
imageUrl
:
{
type
:
String
,
required
:
true
,
},
linkUrl
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
hasError
:
false
,
isLoading
:
true
,
numRetries
:
0
,
};
},
computed
:
{
imageUrlWithRetries
()
{
if
(
this
.
numRetries
===
0
)
{
return
this
.
imageUrl
;
}
return
`
${
this
.
imageUrl
}
#retries=
${
this
.
numRetries
}
`
;
},
},
watch
:
{
imageUrl
()
{
this
.
hasError
=
false
;
this
.
isLoading
=
true
;
this
.
numRetries
=
0
;
},
},
methods
:
{
onError
()
{
this
.
isLoading
=
false
;
this
.
hasError
=
true
;
},
onLoad
()
{
this
.
isLoading
=
false
;
},
reloadImage
()
{
this
.
hasError
=
false
;
this
.
isLoading
=
true
;
this
.
numRetries
+=
1
;
},
},
};
</
script
>
<
template
>
<div>
<a
v-show=
"!isLoading && !hasError"
:href=
"linkUrl"
target=
"_blank"
rel=
"noopener noreferrer"
>
<img
class=
"project-badge"
:src=
"imageUrlWithRetries"
@
load=
"onLoad"
@
error=
"onError"
aria-hidden=
"true"
/>
</a>
<loading-icon
v-show=
"isLoading"
:inline=
"true"
/>
<div
v-show=
"hasError"
class=
"btn-group"
>
<div
class=
"btn btn-default btn-xs disabled"
>
<icon
class=
"prepend-left-8 append-right-8"
name=
"doc_image"
:size=
"16"
aria-hidden=
"true"
/>
</div>
<div
class=
"btn btn-default btn-xs disabled"
>
<span
class=
"prepend-left-8 append-right-8"
>
{{
s__
(
'
Badges|No badge image
'
)
}}
</span>
</div>
</div>
<button
v-show=
"hasError"
class=
"btn btn-transparent btn-xs text-primary"
type=
"button"
v-tooltip
:title=
"s__('Badges|Reload badge image')"
@
click=
"reloadImage"
>
<icon
name=
"retry"
:size=
"16"
/>
</button>
</div>
</
template
>
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/components/badge_form.vue
0 → 100644
View file @
31dd86b6
<
script
>
import
_
from
'
underscore
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
createFlash
from
'
~/flash
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
import
LoadingButton
from
'
~/vue_shared/components/loading_button.vue
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
createEmptyBadge
from
'
../empty_badge
'
;
import
Badge
from
'
./badge.vue
'
;
const
badgePreviewDelayInMilliseconds
=
1500
;
export
default
{
name
:
'
BadgeForm
'
,
components
:
{
Badge
,
LoadingButton
,
LoadingIcon
,
},
props
:
{
isEditing
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
...
mapState
([
'
badgeInAddForm
'
,
'
badgeInEditForm
'
,
'
docsUrl
'
,
'
isRendering
'
,
'
isSaving
'
,
'
renderedBadge
'
,
]),
badge
()
{
if
(
this
.
isEditing
)
{
return
this
.
badgeInEditForm
;
}
return
this
.
badgeInAddForm
;
},
canSubmit
()
{
return
(
this
.
badge
!==
null
&&
this
.
badge
.
imageUrl
&&
this
.
badge
.
imageUrl
.
trim
()
!==
''
&&
this
.
badge
.
linkUrl
&&
this
.
badge
.
linkUrl
.
trim
()
!==
''
&&
!
this
.
isSaving
);
},
helpText
()
{
const
placeholders
=
[
'
project_path
'
,
'
project_id
'
,
'
default_branch
'
,
'
commit_sha
'
]
.
map
(
placeholder
=>
`<code>%{
${
placeholder
}
}</code>`
)
.
join
(
'
,
'
);
return
sprintf
(
s__
(
'
Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}
'
),
{
docsLinkEnd
:
'
</a>
'
,
docsLinkStart
:
`<a href="
${
_
.
escape
(
this
.
docsUrl
)}
">`
,
placeholders
,
},
false
,
);
},
renderedImageUrl
()
{
return
this
.
renderedBadge
?
this
.
renderedBadge
.
renderedImageUrl
:
''
;
},
renderedLinkUrl
()
{
return
this
.
renderedBadge
?
this
.
renderedBadge
.
renderedLinkUrl
:
''
;
},
imageUrl
:
{
get
()
{
return
this
.
badge
?
this
.
badge
.
imageUrl
:
''
;
},
set
(
imageUrl
)
{
const
badge
=
this
.
badge
||
createEmptyBadge
();
this
.
updateBadgeInForm
({
...
badge
,
imageUrl
,
});
},
},
linkUrl
:
{
get
()
{
return
this
.
badge
?
this
.
badge
.
linkUrl
:
''
;
},
set
(
linkUrl
)
{
const
badge
=
this
.
badge
||
createEmptyBadge
();
this
.
updateBadgeInForm
({
...
badge
,
linkUrl
,
});
},
},
submitButtonLabel
()
{
if
(
this
.
isEditing
)
{
return
s__
(
'
Badges|Save changes
'
);
}
return
s__
(
'
Badges|Add badge
'
);
},
},
methods
:
{
...
mapActions
([
'
addBadge
'
,
'
renderBadge
'
,
'
saveBadge
'
,
'
stopEditing
'
,
'
updateBadgeInForm
'
]),
debouncedPreview
:
_
.
debounce
(
function
preview
()
{
this
.
renderBadge
();
},
badgePreviewDelayInMilliseconds
),
onCancel
()
{
this
.
stopEditing
();
},
onSubmit
()
{
if
(
!
this
.
canSubmit
)
{
return
Promise
.
resolve
();
}
if
(
this
.
isEditing
)
{
return
this
.
saveBadge
()
.
then
(()
=>
{
createFlash
(
s__
(
'
Badges|The badge was saved.
'
),
'
notice
'
);
})
.
catch
(
error
=>
{
createFlash
(
s__
(
'
Badges|Saving the badge failed, please check the entered URLs and try again.
'
),
);
throw
error
;
});
}
return
this
.
addBadge
()
.
then
(()
=>
{
createFlash
(
s__
(
'
Badges|A new badge was added.
'
),
'
notice
'
);
})
.
catch
(
error
=>
{
createFlash
(
s__
(
'
Badges|Adding the badge failed, please check the entered URLs and try again.
'
),
);
throw
error
;
});
},
},
badgeImageUrlPlaceholder
:
'
https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg
'
,
badgeLinkUrlPlaceholder
:
'
https://example.gitlab.com/%{project_path}
'
,
};
</
script
>
<
template
>
<form
class=
"prepend-top-default append-bottom-default"
@
submit.prevent.stop=
"onSubmit"
>
<div
class=
"form-group"
>
<label
for=
"badge-link-url"
>
{{
s__
(
'
Badges|Link
'
)
}}
</label>
<input
id=
"badge-link-url"
type=
"text"
class=
"form-control"
v-model=
"linkUrl"
:placeholder=
"$options.badgeLinkUrlPlaceholder"
@
input=
"debouncedPreview"
/>
<span
class=
"help-block"
v-html=
"helpText"
></span>
</div>
<div
class=
"form-group"
>
<label
for=
"badge-image-url"
>
{{
s__
(
'
Badges|Badge image URL
'
)
}}
</label>
<input
id=
"badge-image-url"
type=
"text"
class=
"form-control"
v-model=
"imageUrl"
:placeholder=
"$options.badgeImageUrlPlaceholder"
@
input=
"debouncedPreview"
/>
<span
class=
"help-block"
v-html=
"helpText"
></span>
</div>
<div
class=
"form-group"
>
<label
for=
"badge-preview"
>
{{
s__
(
'
Badges|Badge image preview
'
)
}}
</label>
<badge
id=
"badge-preview"
v-show=
"renderedBadge && !isRendering"
:image-url=
"renderedImageUrl"
:link-url=
"renderedLinkUrl"
/>
<p
v-show=
"isRendering"
>
<loading-icon
:inline=
"true"
/>
</p>
<p
v-show=
"!renderedBadge && !isRendering"
class=
"disabled-content"
>
{{
s__
(
'
Badges|No image to preview
'
)
}}
</p>
</div>
<div
class=
"row-content-block"
>
<loading-button
type=
"submit"
container-class=
"btn btn-success"
:disabled=
"!canSubmit"
:loading=
"isSaving"
:label=
"submitButtonLabel"
/>
<button
class=
"btn btn-cancel"
type=
"button"
v-if=
"isEditing"
@
click=
"onCancel"
>
{{
__
(
'
Cancel
'
)
}}
</button>
</div>
</form>
</
template
>
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/components/badge_list.vue
0 → 100644
View file @
31dd86b6
<
script
>
import
{
mapState
}
from
'
vuex
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
BadgeListRow
from
'
./badge_list_row.vue
'
;
import
{
GROUP_BADGE
}
from
'
../constants
'
;
export
default
{
name
:
'
BadgeList
'
,
components
:
{
BadgeListRow
,
LoadingIcon
,
},
computed
:
{
...
mapState
([
'
badges
'
,
'
isLoading
'
,
'
kind
'
]),
hasNoBadges
()
{
return
!
this
.
isLoading
&&
(
!
this
.
badges
||
!
this
.
badges
.
length
);
},
isGroupBadge
()
{
return
this
.
kind
===
GROUP_BADGE
;
},
},
};
</
script
>
<
template
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
{{
s__
(
'
Badges|Your badges
'
)
}}
<span
v-show=
"!isLoading"
class=
"badge"
>
{{
badges
.
length
}}
</span>
</div>
<loading-icon
v-show=
"isLoading"
class=
"panel-body"
size=
"2"
/>
<div
v-if=
"hasNoBadges"
class=
"panel-body"
>
<span
v-if=
"isGroupBadge"
>
{{
s__
(
'
Badges|This group has no badges
'
)
}}
</span>
<span
v-else
>
{{
s__
(
'
Badges|This project has no badges
'
)
}}
</span>
</div>
<div
v-else
class=
"panel-body"
>
<badge-list-row
v-for=
"badge in badges"
:key=
"badge.id"
:badge=
"badge"
/>
</div>
</div>
</
template
>
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/components/badge_list_row.vue
0 → 100644
View file @
31dd86b6
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
{
PROJECT_BADGE
}
from
'
../constants
'
;
import
Badge
from
'
./badge.vue
'
;
export
default
{
name
:
'
BadgeListRow
'
,
components
:
{
Badge
,
Icon
,
LoadingIcon
,
},
props
:
{
badge
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
...
mapState
([
'
kind
'
]),
badgeKindText
()
{
if
(
this
.
badge
.
kind
===
PROJECT_BADGE
)
{
return
s__
(
'
Badges|Project Badge
'
);
}
return
s__
(
'
Badges|Group Badge
'
);
},
canEditBadge
()
{
return
this
.
badge
.
kind
===
this
.
kind
;
},
},
methods
:
{
...
mapActions
([
'
editBadge
'
,
'
updateBadgeInModal
'
]),
},
};
</
script
>
<
template
>
<div
class=
"gl-responsive-table-row-layout gl-responsive-table-row"
>
<badge
class=
"table-section section-30"
:image-url=
"badge.renderedImageUrl"
:link-url=
"badge.renderedLinkUrl"
/>
<span
class=
"table-section section-50 str-truncated"
>
{{
badge
.
linkUrl
}}
</span>
<div
class=
"table-section section-10"
>
<span
class=
"badge"
>
{{
badgeKindText
}}
</span>
</div>
<div
class=
"table-section section-10 table-button-footer"
>
<div
v-if=
"canEditBadge"
class=
"table-action-buttons"
>
<button
class=
"btn btn-default append-right-8"
type=
"button"
:disabled=
"badge.isDeleting"
@
click=
"editBadge(badge)"
>
<icon
name=
"pencil"
:size=
"16"
:aria-label=
"__('Edit')"
/>
</button>
<button
class=
"btn btn-danger"
type=
"button"
data-toggle=
"modal"
data-target=
"#delete-badge-modal"
:disabled=
"badge.isDeleting"
@
click=
"updateBadgeInModal(badge)"
>
<icon
name=
"remove"
:size=
"16"
:aria-label=
"__('Delete')"
/>
</button>
<loading-icon
v-show=
"badge.isDeleting"
:inline=
"true"
/>
</div>
</div>
</div>
</
template
>
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/components/badge_settings.vue
0 → 100644
View file @
31dd86b6
<
script
>
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
createFlash
from
'
~/flash
'
;
import
{
s__
}
from
'
~/locale
'
;
import
GlModal
from
'
~/vue_shared/components/gl_modal.vue
'
;
import
Badge
from
'
./badge.vue
'
;
import
BadgeForm
from
'
./badge_form.vue
'
;
import
BadgeList
from
'
./badge_list.vue
'
;
export
default
{
name
:
'
BadgeSettings
'
,
components
:
{
Badge
,
BadgeForm
,
BadgeList
,
GlModal
,
},
computed
:
{
...
mapState
([
'
badgeInModal
'
,
'
isEditing
'
]),
deleteModalText
()
{
return
s__
(
'
Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored.
'
,
);
},
},
methods
:
{
...
mapActions
([
'
deleteBadge
'
]),
onSubmitModal
()
{
this
.
deleteBadge
(
this
.
badgeInModal
)
.
then
(()
=>
{
createFlash
(
s__
(
'
Badges|The badge was deleted.
'
),
'
notice
'
);
})
.
catch
(
error
=>
{
createFlash
(
s__
(
'
Badges|Deleting the badge failed, please try again.
'
));
throw
error
;
});
},
},
};
</
script
>
<
template
>
<div
class=
"badge-settings"
>
<gl-modal
id=
"delete-badge-modal"
:header-title-text=
"s__('Badges|Delete badge?')"
footer-primary-button-variant=
"danger"
:footer-primary-button-text=
"s__('Badges|Delete badge')"
@
submit=
"onSubmitModal"
>
<div
class=
"well"
>
<badge
:image-url=
"badgeInModal ? badgeInModal.renderedImageUrl : ''"
:link-url=
"badgeInModal ? badgeInModal.renderedLinkUrl : ''"
/>
</div>
<p
v-html=
"deleteModalText"
></p>
</gl-modal>
<badge-form
v-show=
"isEditing"
:is-editing=
"true"
/>
<badge-form
v-show=
"!isEditing"
:is-editing=
"false"
/>
<badge-list
v-show=
"!isEditing"
/>
</div>
</
template
>
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/constants.js
0 → 100644
View file @
31dd86b6
export
const
GROUP_BADGE
=
'
group
'
;
export
const
PROJECT_BADGE
=
'
project
'
;
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/empty_badge.js
0 → 100644
View file @
31dd86b6
export
default
()
=>
({
imageUrl
:
''
,
isDeleting
:
false
,
linkUrl
:
''
,
renderedImageUrl
:
''
,
renderedLinkUrl
:
''
,
});
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/store/actions.js
0 → 100644
View file @
31dd86b6
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
types
from
'
./mutation_types
'
;
export
const
transformBackendBadge
=
badge
=>
({
id
:
badge
.
id
,
imageUrl
:
badge
.
image_url
,
kind
:
badge
.
kind
,
linkUrl
:
badge
.
link_url
,
renderedImageUrl
:
badge
.
rendered_image_url
,
renderedLinkUrl
:
badge
.
rendered_link_url
,
isDeleting
:
false
,
});
export
default
{
requestNewBadge
({
commit
})
{
commit
(
types
.
REQUEST_NEW_BADGE
);
},
receiveNewBadge
({
commit
},
newBadge
)
{
commit
(
types
.
RECEIVE_NEW_BADGE
,
newBadge
);
},
receiveNewBadgeError
({
commit
})
{
commit
(
types
.
RECEIVE_NEW_BADGE_ERROR
);
},
addBadge
({
dispatch
,
state
})
{
const
newBadge
=
state
.
badgeInAddForm
;
const
endpoint
=
state
.
apiEndpointUrl
;
dispatch
(
'
requestNewBadge
'
);
return
axios
.
post
(
endpoint
,
{
image_url
:
newBadge
.
imageUrl
,
link_url
:
newBadge
.
linkUrl
,
})
.
catch
(
error
=>
{
dispatch
(
'
receiveNewBadgeError
'
);
throw
error
;
})
.
then
(
res
=>
{
dispatch
(
'
receiveNewBadge
'
,
transformBackendBadge
(
res
.
data
));
});
},
requestDeleteBadge
({
commit
},
badgeId
)
{
commit
(
types
.
REQUEST_DELETE_BADGE
,
badgeId
);
},
receiveDeleteBadge
({
commit
},
badgeId
)
{
commit
(
types
.
RECEIVE_DELETE_BADGE
,
badgeId
);
},
receiveDeleteBadgeError
({
commit
},
badgeId
)
{
commit
(
types
.
RECEIVE_DELETE_BADGE_ERROR
,
badgeId
);
},
deleteBadge
({
dispatch
,
state
},
badge
)
{
const
badgeId
=
badge
.
id
;
dispatch
(
'
requestDeleteBadge
'
,
badgeId
);
const
endpoint
=
`
${
state
.
apiEndpointUrl
}
/
${
badgeId
}
`
;
return
axios
.
delete
(
endpoint
)
.
catch
(
error
=>
{
dispatch
(
'
receiveDeleteBadgeError
'
,
badgeId
);
throw
error
;
})
.
then
(()
=>
{
dispatch
(
'
receiveDeleteBadge
'
,
badgeId
);
});
},
editBadge
({
commit
},
badge
)
{
commit
(
types
.
START_EDITING
,
badge
);
},
requestLoadBadges
({
commit
},
data
)
{
commit
(
types
.
REQUEST_LOAD_BADGES
,
data
);
},
receiveLoadBadges
({
commit
},
badges
)
{
commit
(
types
.
RECEIVE_LOAD_BADGES
,
badges
);
},
receiveLoadBadgesError
({
commit
})
{
commit
(
types
.
RECEIVE_LOAD_BADGES_ERROR
);
},
loadBadges
({
dispatch
,
state
},
data
)
{
dispatch
(
'
requestLoadBadges
'
,
data
);
const
endpoint
=
state
.
apiEndpointUrl
;
return
axios
.
get
(
endpoint
)
.
catch
(
error
=>
{
dispatch
(
'
receiveLoadBadgesError
'
);
throw
error
;
})
.
then
(
res
=>
{
dispatch
(
'
receiveLoadBadges
'
,
res
.
data
.
map
(
transformBackendBadge
));
});
},
requestRenderedBadge
({
commit
})
{
commit
(
types
.
REQUEST_RENDERED_BADGE
);
},
receiveRenderedBadge
({
commit
},
renderedBadge
)
{
commit
(
types
.
RECEIVE_RENDERED_BADGE
,
renderedBadge
);
},
receiveRenderedBadgeError
({
commit
})
{
commit
(
types
.
RECEIVE_RENDERED_BADGE_ERROR
);
},
renderBadge
({
dispatch
,
state
})
{
const
badge
=
state
.
isEditing
?
state
.
badgeInEditForm
:
state
.
badgeInAddForm
;
const
{
linkUrl
,
imageUrl
}
=
badge
;
if
(
!
linkUrl
||
linkUrl
.
trim
()
===
''
||
!
imageUrl
||
imageUrl
.
trim
()
===
''
)
{
return
Promise
.
resolve
(
badge
);
}
dispatch
(
'
requestRenderedBadge
'
);
const
parameters
=
[
`link_url=
${
encodeURIComponent
(
linkUrl
)}
`
,
`image_url=
${
encodeURIComponent
(
imageUrl
)}
`
,
].
join
(
'
&
'
);
const
renderEndpoint
=
`
${
state
.
apiEndpointUrl
}
/render?
${
parameters
}
`
;
return
axios
.
get
(
renderEndpoint
)
.
catch
(
error
=>
{
dispatch
(
'
receiveRenderedBadgeError
'
);
throw
error
;
})
.
then
(
res
=>
{
dispatch
(
'
receiveRenderedBadge
'
,
transformBackendBadge
(
res
.
data
));
});
},
requestUpdatedBadge
({
commit
})
{
commit
(
types
.
REQUEST_UPDATED_BADGE
);
},
receiveUpdatedBadge
({
commit
},
updatedBadge
)
{
commit
(
types
.
RECEIVE_UPDATED_BADGE
,
updatedBadge
);
},
receiveUpdatedBadgeError
({
commit
})
{
commit
(
types
.
RECEIVE_UPDATED_BADGE_ERROR
);
},
saveBadge
({
dispatch
,
state
})
{
const
badge
=
state
.
badgeInEditForm
;
const
endpoint
=
`
${
state
.
apiEndpointUrl
}
/
${
badge
.
id
}
`
;
dispatch
(
'
requestUpdatedBadge
'
);
return
axios
.
put
(
endpoint
,
{
image_url
:
badge
.
imageUrl
,
link_url
:
badge
.
linkUrl
,
})
.
catch
(
error
=>
{
dispatch
(
'
receiveUpdatedBadgeError
'
);
throw
error
;
})
.
then
(
res
=>
{
dispatch
(
'
receiveUpdatedBadge
'
,
transformBackendBadge
(
res
.
data
));
});
},
stopEditing
({
commit
})
{
commit
(
types
.
STOP_EDITING
);
},
updateBadgeInForm
({
commit
},
badge
)
{
commit
(
types
.
UPDATE_BADGE_IN_FORM
,
badge
);
},
updateBadgeInModal
({
commit
},
badge
)
{
commit
(
types
.
UPDATE_BADGE_IN_MODAL
,
badge
);
},
};
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/store/index.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
createState
from
'
./state
'
;
import
actions
from
'
./actions
'
;
import
mutations
from
'
./mutations
'
;
Vue
.
use
(
Vuex
);
export
default
new
Vuex
.
Store
({
state
:
createState
(),
actions
,
mutations
,
});
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/store/mutation_types.js
0 → 100644
View file @
31dd86b6
export
default
{
RECEIVE_DELETE_BADGE
:
'
RECEIVE_DELETE_BADGE
'
,
RECEIVE_DELETE_BADGE_ERROR
:
'
RECEIVE_DELETE_BADGE_ERROR
'
,
RECEIVE_LOAD_BADGES
:
'
RECEIVE_LOAD_BADGES
'
,
RECEIVE_LOAD_BADGES_ERROR
:
'
RECEIVE_LOAD_BADGES_ERROR
'
,
RECEIVE_NEW_BADGE
:
'
RECEIVE_NEW_BADGE
'
,
RECEIVE_NEW_BADGE_ERROR
:
'
RECEIVE_NEW_BADGE_ERROR
'
,
RECEIVE_RENDERED_BADGE
:
'
RECEIVE_RENDERED_BADGE
'
,
RECEIVE_RENDERED_BADGE_ERROR
:
'
RECEIVE_RENDERED_BADGE_ERROR
'
,
RECEIVE_UPDATED_BADGE
:
'
RECEIVE_UPDATED_BADGE
'
,
RECEIVE_UPDATED_BADGE_ERROR
:
'
RECEIVE_UPDATED_BADGE_ERROR
'
,
REQUEST_DELETE_BADGE
:
'
REQUEST_DELETE_BADGE
'
,
REQUEST_LOAD_BADGES
:
'
REQUEST_LOAD_BADGES
'
,
REQUEST_NEW_BADGE
:
'
REQUEST_NEW_BADGE
'
,
REQUEST_RENDERED_BADGE
:
'
REQUEST_RENDERED_BADGE
'
,
REQUEST_UPDATED_BADGE
:
'
REQUEST_UPDATED_BADGE
'
,
START_EDITING
:
'
START_EDITING
'
,
STOP_EDITING
:
'
STOP_EDITING
'
,
UPDATE_BADGE_IN_FORM
:
'
UPDATE_BADGE_IN_FORM
'
,
UPDATE_BADGE_IN_MODAL
:
'
UPDATE_BADGE_IN_MODAL
'
,
};
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/store/mutations.js
0 → 100644
View file @
31dd86b6
import
types
from
'
./mutation_types
'
;
import
{
PROJECT_BADGE
}
from
'
../constants
'
;
const
reorderBadges
=
badges
=>
badges
.
sort
((
a
,
b
)
=>
{
if
(
a
.
kind
!==
b
.
kind
)
{
return
a
.
kind
===
PROJECT_BADGE
?
1
:
-
1
;
}
return
a
.
id
-
b
.
id
;
});
export
default
{
[
types
.
RECEIVE_NEW_BADGE
](
state
,
newBadge
)
{
Object
.
assign
(
state
,
{
badgeInAddForm
:
null
,
badges
:
reorderBadges
(
state
.
badges
.
concat
(
newBadge
)),
isSaving
:
false
,
renderedBadge
:
null
,
});
},
[
types
.
RECEIVE_NEW_BADGE_ERROR
](
state
)
{
Object
.
assign
(
state
,
{
isSaving
:
false
,
});
},
[
types
.
REQUEST_NEW_BADGE
](
state
)
{
Object
.
assign
(
state
,
{
isSaving
:
true
,
});
},
[
types
.
RECEIVE_UPDATED_BADGE
](
state
,
updatedBadge
)
{
const
badges
=
state
.
badges
.
map
(
badge
=>
{
if
(
badge
.
id
===
updatedBadge
.
id
)
{
return
updatedBadge
;
}
return
badge
;
});
Object
.
assign
(
state
,
{
badgeInEditForm
:
null
,
badges
,
isEditing
:
false
,
isSaving
:
false
,
renderedBadge
:
null
,
});
},
[
types
.
RECEIVE_UPDATED_BADGE_ERROR
](
state
)
{
Object
.
assign
(
state
,
{
isSaving
:
false
,
});
},
[
types
.
REQUEST_UPDATED_BADGE
](
state
)
{
Object
.
assign
(
state
,
{
isSaving
:
true
,
});
},
[
types
.
RECEIVE_LOAD_BADGES
](
state
,
badges
)
{
Object
.
assign
(
state
,
{
badges
:
reorderBadges
(
badges
),
isLoading
:
false
,
});
},
[
types
.
RECEIVE_LOAD_BADGES_ERROR
](
state
)
{
Object
.
assign
(
state
,
{
isLoading
:
false
,
});
},
[
types
.
REQUEST_LOAD_BADGES
](
state
,
data
)
{
Object
.
assign
(
state
,
{
kind
:
data
.
kind
,
// project or group
apiEndpointUrl
:
data
.
apiEndpointUrl
,
docsUrl
:
data
.
docsUrl
,
isLoading
:
true
,
});
},
[
types
.
RECEIVE_DELETE_BADGE
](
state
,
badgeId
)
{
const
badges
=
state
.
badges
.
filter
(
badge
=>
badge
.
id
!==
badgeId
);
Object
.
assign
(
state
,
{
badges
,
});
},
[
types
.
RECEIVE_DELETE_BADGE_ERROR
](
state
,
badgeId
)
{
const
badges
=
state
.
badges
.
map
(
badge
=>
{
if
(
badge
.
id
===
badgeId
)
{
return
{
...
badge
,
isDeleting
:
false
,
};
}
return
badge
;
});
Object
.
assign
(
state
,
{
badges
,
});
},
[
types
.
REQUEST_DELETE_BADGE
](
state
,
badgeId
)
{
const
badges
=
state
.
badges
.
map
(
badge
=>
{
if
(
badge
.
id
===
badgeId
)
{
return
{
...
badge
,
isDeleting
:
true
,
};
}
return
badge
;
});
Object
.
assign
(
state
,
{
badges
,
});
},
[
types
.
RECEIVE_RENDERED_BADGE
](
state
,
renderedBadge
)
{
Object
.
assign
(
state
,
{
isRendering
:
false
,
renderedBadge
});
},
[
types
.
RECEIVE_RENDERED_BADGE_ERROR
](
state
)
{
Object
.
assign
(
state
,
{
isRendering
:
false
});
},
[
types
.
REQUEST_RENDERED_BADGE
](
state
)
{
Object
.
assign
(
state
,
{
isRendering
:
true
});
},
[
types
.
START_EDITING
](
state
,
badge
)
{
Object
.
assign
(
state
,
{
badgeInEditForm
:
{
...
badge
},
isEditing
:
true
,
renderedBadge
:
{
...
badge
},
});
},
[
types
.
STOP_EDITING
](
state
)
{
Object
.
assign
(
state
,
{
badgeInEditForm
:
null
,
isEditing
:
false
,
renderedBadge
:
null
,
});
},
[
types
.
UPDATE_BADGE_IN_FORM
](
state
,
badge
)
{
if
(
state
.
isEditing
)
{
Object
.
assign
(
state
,
{
badgeInEditForm
:
badge
,
});
}
else
{
Object
.
assign
(
state
,
{
badgeInAddForm
:
badge
,
});
}
},
[
types
.
UPDATE_BADGE_IN_MODAL
](
state
,
badge
)
{
Object
.
assign
(
state
,
{
badgeInModal
:
badge
,
});
},
};
This diff is collapsed.
Click to expand it.
app/assets/javascripts/badges/store/state.js
0 → 100644
View file @
31dd86b6
export
default
()
=>
({
apiEndpointUrl
:
null
,
badgeInAddForm
:
null
,
badgeInEditForm
:
null
,
badgeInModal
:
null
,
badges
:
[],
docsUrl
:
null
,
renderedBadge
:
null
,
isEditing
:
false
,
isLoading
:
false
,
isRendering
:
false
,
isSaving
:
false
,
});
This diff is collapsed.
Click to expand it.
app/assets/javascripts/pages/groups/settings/badges/index.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
Translate
from
'
~/vue_shared/translate
'
;
import
{
GROUP_BADGE
}
from
'
~/badges/constants
'
;
import
mountBadgeSettings
from
'
~/pages/shared/mount_badge_settings
'
;
Vue
.
use
(
Translate
);
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
mountBadgeSettings
(
GROUP_BADGE
);
});
This diff is collapsed.
Click to expand it.
app/assets/javascripts/pages/projects/settings/badges/index/index.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
Translate
from
'
~/vue_shared/translate
'
;
import
{
PROJECT_BADGE
}
from
'
~/badges/constants
'
;
import
mountBadgeSettings
from
'
~/pages/shared/mount_badge_settings
'
;
Vue
.
use
(
Translate
);
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
mountBadgeSettings
(
PROJECT_BADGE
);
});
This diff is collapsed.
Click to expand it.
app/assets/javascripts/pages/shared/mount_badge_settings.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
BadgeSettings
from
'
~/badges/components/badge_settings.vue
'
;
import
store
from
'
~/badges/store
'
;
export
default
kind
=>
{
const
badgeSettingsElement
=
document
.
getElementById
(
'
badge-settings
'
);
store
.
dispatch
(
'
loadBadges
'
,
{
kind
,
apiEndpointUrl
:
badgeSettingsElement
.
dataset
.
apiEndpointUrl
,
docsUrl
:
badgeSettingsElement
.
dataset
.
docsUrl
,
});
return
new
Vue
({
el
:
badgeSettingsElement
,
store
,
components
:
{
BadgeSettings
,
},
render
(
createElement
)
{
return
createElement
(
BadgeSettings
);
},
});
};
This diff is collapsed.
Click to expand it.
app/assets/stylesheets/framework/responsive_tables.scss
View file @
31dd86b6
...
@@ -39,7 +39,7 @@
...
@@ -39,7 +39,7 @@
.table-section
{
.table-section
{
white-space
:
nowrap
;
white-space
:
nowrap
;
$section-widths
:
10
15
20
25
30
40
100
;
$section-widths
:
10
15
20
25
30
40
50
100
;
@each
$width
in
$section-widths
{
@each
$width
in
$section-widths
{
&
.section-
#{
$width
}
{
&
.section-
#{
$width
}
{
flex
:
0
0
#{
$width
+
'%'
}
;
flex
:
0
0
#{
$width
+
'%'
}
;
...
...
This diff is collapsed.
Click to expand it.
app/assets/stylesheets/pages/projects.scss
View file @
31dd86b6
...
@@ -1143,3 +1143,11 @@ pre.light-well {
...
@@ -1143,3 +1143,11 @@ pre.light-well {
white-space
:
pre-wrap
;
white-space
:
pre-wrap
;
}
}
}
}
.project-badge
{
opacity
:
0
.9
;
&
:hover
{
opacity
:
1
;
}
}
This diff is collapsed.
Click to expand it.
app/controllers/groups/settings/badges_controller.rb
0 → 100644
View file @
31dd86b6
module
Groups
module
Settings
class
BadgesController
<
Groups
::
ApplicationController
include
GrapeRouteHelpers
::
NamedRouteMatcher
before_action
:authorize_admin_group!
def
index
@badge_api_endpoint
=
api_v4_groups_badges_path
(
id:
@group
.
id
)
end
end
end
end
This diff is collapsed.
Click to expand it.
app/controllers/projects/settings/badges_controller.rb
0 → 100644
View file @
31dd86b6
module
Projects
module
Settings
class
BadgesController
<
Projects
::
ApplicationController
include
GrapeRouteHelpers
::
NamedRouteMatcher
before_action
:authorize_admin_project!
def
index
@badge_api_endpoint
=
api_v4_projects_badges_path
(
id:
@project
.
id
)
end
end
end
end
This diff is collapsed.
Click to expand it.
app/helpers/groups_helper.rb
View file @
31dd86b6
module
GroupsHelper
module
GroupsHelper
def
group_nav_link_paths
def
group_nav_link_paths
%w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
%w[groups#projects groups#edit
badges#index
ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
end
def
group_sidebar_links
def
group_sidebar_links
...
...
This diff is collapsed.
Click to expand it.
app/views/groups/settings/badges/index.html.haml
0 → 100644
View file @
31dd86b6
-
breadcrumb_title
_
(
'Project Badges'
)
-
page_title
_
(
'Project Badges'
)
=
render
'shared/badges/badge_settings'
This diff is collapsed.
Click to expand it.
app/views/layouts/nav/sidebar/_group.html.haml
View file @
31dd86b6
...
@@ -112,7 +112,7 @@
...
@@ -112,7 +112,7 @@
%span
.nav-item-name
%span
.nav-item-name
Settings
Settings
%ul
.sidebar-sub-level-items
%ul
.sidebar-sub-level-items
=
nav_link
(
path:
%w[groups#projects groups#edit ci_cd#show]
,
html_options:
{
class:
"fly-out-top-item"
}
)
do
=
nav_link
(
path:
%w[groups#projects groups#edit
badges#index
ci_cd#show]
,
html_options:
{
class:
"fly-out-top-item"
}
)
do
=
link_to
edit_group_path
(
@group
)
do
=
link_to
edit_group_path
(
@group
)
do
%strong
.fly-out-top-item-name
%strong
.fly-out-top-item-name
#{
_
(
'Settings'
)
}
#{
_
(
'Settings'
)
}
...
@@ -122,6 +122,12 @@
...
@@ -122,6 +122,12 @@
%span
%span
General
General
=
nav_link
(
controller: :badges
)
do
=
link_to
group_settings_badges_path
(
@group
),
title:
_
(
'Project Badges'
)
do
%span
=
_
(
'Project Badges'
)
=
nav_link
(
path:
'groups#projects'
)
do
=
nav_link
(
path:
'groups#projects'
)
do
=
link_to
projects_group_path
(
@group
),
title:
'Projects'
do
=
link_to
projects_group_path
(
@group
),
title:
'Projects'
do
%span
%span
...
...
This diff is collapsed.
Click to expand it.
app/views/layouts/nav/sidebar/_project.html.haml
View file @
31dd86b6
...
@@ -258,7 +258,7 @@
...
@@ -258,7 +258,7 @@
#{
_
(
'Snippets'
)
}
#{
_
(
'Snippets'
)
}
-
if
project_nav_tab?
:settings
-
if
project_nav_tab?
:settings
=
nav_link
(
path:
%w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]
)
do
=
nav_link
(
path:
%w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show
badges#index
pages#show]
)
do
=
link_to
edit_project_path
(
@project
),
class:
'shortcuts-tree'
do
=
link_to
edit_project_path
(
@project
),
class:
'shortcuts-tree'
do
.nav-icon-container
.nav-icon-container
=
sprite_icon
(
'settings'
)
=
sprite_icon
(
'settings'
)
...
@@ -268,7 +268,7 @@
...
@@ -268,7 +268,7 @@
%ul
.sidebar-sub-level-items
%ul
.sidebar-sub-level-items
-
can_edit
=
can?
(
current_user
,
:admin_project
,
@project
)
-
can_edit
=
can?
(
current_user
,
:admin_project
,
@project
)
-
if
can_edit
-
if
can_edit
=
nav_link
(
path:
%w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]
,
html_options:
{
class:
"fly-out-top-item"
}
)
do
=
nav_link
(
path:
%w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show
badges#index
pages#show]
,
html_options:
{
class:
"fly-out-top-item"
}
)
do
=
link_to
edit_project_path
(
@project
)
do
=
link_to
edit_project_path
(
@project
)
do
%strong
.fly-out-top-item-name
%strong
.fly-out-top-item-name
#{
_
(
'Settings'
)
}
#{
_
(
'Settings'
)
}
...
@@ -281,6 +281,11 @@
...
@@ -281,6 +281,11 @@
=
link_to
project_project_members_path
(
@project
),
title:
'Members'
do
=
link_to
project_project_members_path
(
@project
),
title:
'Members'
do
%span
%span
Members
Members
-
if
can_edit
=
nav_link
(
controller: :badges
)
do
=
link_to
project_settings_badges_path
(
@project
),
title:
_
(
'Badges'
)
do
%span
=
_
(
'Badges'
)
-
if
can_edit
-
if
can_edit
=
nav_link
(
controller:
[
:integrations
,
:services
,
:hooks
,
:hook_logs
])
do
=
nav_link
(
controller:
[
:integrations
,
:services
,
:hooks
,
:hook_logs
])
do
=
link_to
project_settings_integrations_path
(
@project
),
title:
'Integrations'
do
=
link_to
project_settings_integrations_path
(
@project
),
title:
'Integrations'
do
...
...
This diff is collapsed.
Click to expand it.
app/views/projects/_home_panel.html.haml
View file @
31dd86b6
...
@@ -23,11 +23,14 @@
...
@@ -23,11 +23,14 @@
-
deleted_message
=
s_
(
'ForkedFromProjectPath|Forked from %{project_name} (deleted)'
)
-
deleted_message
=
s_
(
'ForkedFromProjectPath|Forked from %{project_name} (deleted)'
)
=
deleted_message
%
{
project_name:
fork_source_name
(
@project
)
}
=
deleted_message
%
{
project_name:
fork_source_name
(
@project
)
}
.project-badges
.project-badges
.prepend-top-default.append-bottom-default
-
@project
.
badges
.
each
do
|
badge
|
-
@project
.
badges
.
each
do
|
badge
|
-
badge_link_url
=
badge
.
rendered_link_url
(
@project
)
%a
.append-right-8
{
href:
badge
.
rendered_link_url
(
@project
),
%a
{
href:
badge_link_url
,
target:
'_blank'
,
rel:
'noopener noreferrer'
}
target:
'_blank'
,
%img
{
src:
badge
.
rendered_image_url
(
@project
),
alt:
badge_link_url
}
rel:
'noopener noreferrer'
}
>
%img
.project-badge
{
src:
badge
.
rendered_image_url
(
@project
),
'aria-hidden'
:
true
,
alt:
''
}
>
.project-repo-buttons
.project-repo-buttons
.count-buttons
.count-buttons
...
...
This diff is collapsed.
Click to expand it.
app/views/projects/settings/badges/index.html.haml
0 → 100644
View file @
31dd86b6
-
breadcrumb_title
_
(
'Badges'
)
-
page_title
_
(
'Badges'
)
=
render
'shared/badges/badge_settings'
This diff is collapsed.
Click to expand it.
app/views/shared/badges/_badge_settings.html.haml
0 → 100644
View file @
31dd86b6
#badge-settings
{
data:
{
api_endpoint_url:
@badge_api_endpoint
,
docs_url:
help_page_path
(
'user/project/badges'
)}
}
.text-center.prepend-top-default
=
icon
(
'spinner spin 2x'
)
This diff is collapsed.
Click to expand it.
changelogs/unreleased/winh-41174-projects-groups-badges-ui.yml
0 → 100644
View file @
31dd86b6
---
title
:
Projects and groups badges settings UI
merge_request
:
17114
author
:
type
:
added
This diff is collapsed.
Click to expand it.
config/karma.config.js
View file @
31dd86b6
...
@@ -39,7 +39,7 @@ module.exports = function(config) {
...
@@ -39,7 +39,7 @@ module.exports = function(config) {
frameworks
:
[
'
jasmine
'
],
frameworks
:
[
'
jasmine
'
],
files
:
[
files
:
[
{
pattern
:
'
spec/javascripts/test_bundle.js
'
,
watched
:
false
},
{
pattern
:
'
spec/javascripts/test_bundle.js
'
,
watched
:
false
},
{
pattern
:
'
spec/javascripts/fixtures/**/*@(.json|.html|.html.raw)
'
,
included
:
false
},
{
pattern
:
'
spec/javascripts/fixtures/**/*@(.json|.html|.html.raw
|.png
)
'
,
included
:
false
},
],
],
preprocessors
:
{
preprocessors
:
{
'
spec/javascripts/**/*.js
'
:
[
'
webpack
'
,
'
sourcemap
'
],
'
spec/javascripts/**/*.js
'
:
[
'
webpack
'
,
'
sourcemap
'
],
...
...
This diff is collapsed.
Click to expand it.
config/routes/group.rb
View file @
31dd86b6
...
@@ -24,6 +24,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
...
@@ -24,6 +24,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
constraints:
{
group_id:
Gitlab
::
PathRegex
.
full_namespace_route_regex
})
do
constraints:
{
group_id:
Gitlab
::
PathRegex
.
full_namespace_route_regex
})
do
namespace
:settings
do
namespace
:settings
do
resource
:ci_cd
,
only:
[
:show
],
controller:
'ci_cd'
resource
:ci_cd
,
only:
[
:show
],
controller:
'ci_cd'
resources
:badges
,
only:
[
:index
]
end
end
resource
:variables
,
only:
[
:show
,
:update
]
resource
:variables
,
only:
[
:show
,
:update
]
...
...
This diff is collapsed.
Click to expand it.
config/routes/project.rb
View file @
31dd86b6
...
@@ -435,6 +435,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
...
@@ -435,6 +435,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource
:repository
,
only:
[
:show
],
controller: :repository
do
resource
:repository
,
only:
[
:show
],
controller: :repository
do
post
:create_deploy_token
,
path:
'deploy_token/create'
post
:create_deploy_token
,
path:
'deploy_token/create'
end
end
resources
:badges
,
only:
[
:index
]
end
end
# Since both wiki and repository routing contains wildcard characters
# Since both wiki and repository routing contains wildcard characters
...
...
This diff is collapsed.
Click to expand it.
lib/api/badges.rb
View file @
31dd86b6
...
@@ -127,6 +127,7 @@ module API
...
@@ -127,6 +127,7 @@ module API
end
end
destroy_conditionally!
(
badge
)
destroy_conditionally!
(
badge
)
body
false
end
end
end
end
end
end
...
...
This diff is collapsed.
Click to expand it.
spec/features/groups/settings/group_badges_spec.rb
0 → 100644
View file @
31dd86b6
require
'spec_helper'
feature
'Group Badges'
do
include
WaitForRequests
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:badge_link_url
)
{
'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'
}
let
(
:badge_image_url
)
{
'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'
}
let!
(
:badge_1
)
{
create
(
:group_badge
,
group:
group
)
}
let!
(
:badge_2
)
{
create
(
:group_badge
,
group:
group
)
}
before
do
group
.
add_owner
(
user
)
sign_in
(
user
)
visit
(
group_settings_badges_path
(
group
))
end
it
'shows a list of badges'
,
:js
do
page
.
within
'.badge-settings'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
expect
(
rows
[
0
]).
to
have_content
badge_1
.
link_url
expect
(
rows
[
1
]).
to
have_content
badge_2
.
link_url
end
end
context
'adding a badge'
,
:js
do
it
'user can preview a badge'
do
page
.
within
'.badge-settings form'
do
fill_in
'badge-link-url'
,
with:
badge_link_url
fill_in
'badge-image-url'
,
with:
badge_image_url
within
'#badge-preview'
do
expect
(
find
(
'a'
)[
:href
]).
to
eq
badge_link_url
expect
(
find
(
'a img'
)[
:src
]).
to
eq
badge_image_url
end
end
end
it
do
page
.
within
'.badge-settings'
do
fill_in
'badge-link-url'
,
with:
badge_link_url
fill_in
'badge-image-url'
,
with:
badge_image_url
click_button
'Add badge'
wait_for_requests
within
'.panel-body'
do
expect
(
find
(
'a'
)[
:href
]).
to
eq
badge_link_url
expect
(
find
(
'a img'
)[
:src
]).
to
eq
badge_image_url
end
end
end
end
context
'editing a badge'
,
:js
do
it
'form is shown when clicking edit button in list'
do
page
.
within
'.badge-settings'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
rows
[
1
].
find
(
'[aria-label="Edit"]'
).
click
within
'form'
do
expect
(
find
(
'#badge-link-url'
).
value
).
to
eq
badge_2
.
link_url
expect
(
find
(
'#badge-image-url'
).
value
).
to
eq
badge_2
.
image_url
end
end
end
it
'updates a badge when submitting the edit form'
do
page
.
within
'.badge-settings'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
rows
[
1
].
find
(
'[aria-label="Edit"]'
).
click
within
'form'
do
fill_in
'badge-link-url'
,
with:
badge_link_url
fill_in
'badge-image-url'
,
with:
badge_image_url
click_button
'Save changes'
wait_for_requests
end
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
expect
(
rows
[
1
]).
to
have_content
badge_link_url
end
end
end
context
'deleting a badge'
,
:js
do
def
click_delete_button
(
badge_row
)
badge_row
.
find
(
'[aria-label="Delete"]'
).
click
end
it
'shows a modal when deleting a badge'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
click_delete_button
(
rows
[
1
])
expect
(
find
(
'.modal .modal-title'
)).
to
have_content
'Delete badge?'
end
it
'deletes a badge when confirming the modal'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
click_delete_button
(
rows
[
1
])
find
(
'.modal .btn-danger'
).
click
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
1
expect
(
rows
[
0
]).
to
have_content
badge_1
.
link_url
end
end
end
This diff is collapsed.
Click to expand it.
spec/features/projects/settings/project_badges_spec.rb
0 → 100644
View file @
31dd86b6
require
'spec_helper'
feature
'Project Badges'
do
include
WaitForRequests
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:project
)
{
create
(
:project
,
namespace:
group
)
}
let
(
:badge_link_url
)
{
'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'
}
let
(
:badge_image_url
)
{
'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'
}
let!
(
:project_badge
)
{
create
(
:project_badge
,
project:
project
)
}
let!
(
:group_badge
)
{
create
(
:group_badge
,
group:
group
)
}
before
do
group
.
add_master
(
user
)
sign_in
(
user
)
visit
(
project_settings_badges_path
(
project
))
end
it
'shows a list of badges'
,
:js
do
page
.
within
'.badge-settings'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
expect
(
rows
[
0
]).
to
have_content
group_badge
.
link_url
expect
(
rows
[
1
]).
to
have_content
project_badge
.
link_url
end
end
context
'adding a badge'
,
:js
do
it
'user can preview a badge'
do
page
.
within
'.badge-settings form'
do
fill_in
'badge-link-url'
,
with:
badge_link_url
fill_in
'badge-image-url'
,
with:
badge_image_url
within
'#badge-preview'
do
expect
(
find
(
'a'
)[
:href
]).
to
eq
badge_link_url
expect
(
find
(
'a img'
)[
:src
]).
to
eq
badge_image_url
end
end
end
it
do
page
.
within
'.badge-settings'
do
fill_in
'badge-link-url'
,
with:
badge_link_url
fill_in
'badge-image-url'
,
with:
badge_image_url
click_button
'Add badge'
wait_for_requests
within
'.panel-body'
do
expect
(
find
(
'a'
)[
:href
]).
to
eq
badge_link_url
expect
(
find
(
'a img'
)[
:src
]).
to
eq
badge_image_url
end
end
end
end
context
'editing a badge'
,
:js
do
it
'form is shown when clicking edit button in list'
do
page
.
within
'.badge-settings'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
rows
[
1
].
find
(
'[aria-label="Edit"]'
).
click
within
'form'
do
expect
(
find
(
'#badge-link-url'
).
value
).
to
eq
project_badge
.
link_url
expect
(
find
(
'#badge-image-url'
).
value
).
to
eq
project_badge
.
image_url
end
end
end
it
'updates a badge when submitting the edit form'
do
page
.
within
'.badge-settings'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
rows
[
1
].
find
(
'[aria-label="Edit"]'
).
click
within
'form'
do
fill_in
'badge-link-url'
,
with:
badge_link_url
fill_in
'badge-image-url'
,
with:
badge_image_url
click_button
'Save changes'
wait_for_requests
end
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
expect
(
rows
[
1
]).
to
have_content
badge_link_url
end
end
end
context
'deleting a badge'
,
:js
do
def
click_delete_button
(
badge_row
)
badge_row
.
find
(
'[aria-label="Delete"]'
).
click
end
it
'shows a modal when deleting a badge'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
click_delete_button
(
rows
[
1
])
expect
(
find
(
'.modal .modal-title'
)).
to
have_content
'Delete badge?'
end
it
'deletes a badge when confirming the modal'
do
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
2
click_delete_button
(
rows
[
1
])
find
(
'.modal .btn-danger'
).
click
wait_for_requests
rows
=
all
(
'.panel-body > div'
)
expect
(
rows
.
length
).
to
eq
1
expect
(
rows
[
0
]).
to
have_content
group_badge
.
link_url
end
end
end
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/components/badge_form_spec.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
store
from
'
~/badges/store
'
;
import
BadgeForm
from
'
~/badges/components/badge_form.vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
createDummyBadge
}
from
'
../dummy_badge
'
;
describe
(
'
BadgeForm component
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
BadgeForm
);
let
vm
;
beforeEach
(()
=>
{
setFixtures
(
`
<div id="dummy-element"></div>
`
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponentWithStore
(
Component
,
{
el
:
'
#dummy-element
'
,
store
,
props
:
{
isEditing
:
false
,
},
});
});
describe
(
'
onCancel
'
,
()
=>
{
it
(
'
calls stopEditing
'
,
()
=>
{
spyOn
(
vm
,
'
stopEditing
'
);
vm
.
onCancel
();
expect
(
vm
.
stopEditing
).
toHaveBeenCalled
();
});
});
describe
(
'
onSubmit
'
,
()
=>
{
describe
(
'
if isEditing is true
'
,
()
=>
{
beforeEach
(()
=>
{
spyOn
(
vm
,
'
saveBadge
'
).
and
.
returnValue
(
Promise
.
resolve
());
store
.
replaceState
({
...
store
.
state
,
isSaving
:
false
,
badgeInEditForm
:
createDummyBadge
(),
});
vm
.
isEditing
=
true
;
});
it
(
'
returns immediately if imageUrl is empty
'
,
()
=>
{
store
.
state
.
badgeInEditForm
.
imageUrl
=
''
;
vm
.
onSubmit
();
expect
(
vm
.
saveBadge
).
not
.
toHaveBeenCalled
();
});
it
(
'
returns immediately if linkUrl is empty
'
,
()
=>
{
store
.
state
.
badgeInEditForm
.
linkUrl
=
''
;
vm
.
onSubmit
();
expect
(
vm
.
saveBadge
).
not
.
toHaveBeenCalled
();
});
it
(
'
returns immediately if isSaving is true
'
,
()
=>
{
store
.
state
.
isSaving
=
true
;
vm
.
onSubmit
();
expect
(
vm
.
saveBadge
).
not
.
toHaveBeenCalled
();
});
it
(
'
calls saveBadge
'
,
()
=>
{
vm
.
onSubmit
();
expect
(
vm
.
saveBadge
).
toHaveBeenCalled
();
});
});
describe
(
'
if isEditing is false
'
,
()
=>
{
beforeEach
(()
=>
{
spyOn
(
vm
,
'
addBadge
'
).
and
.
returnValue
(
Promise
.
resolve
());
store
.
replaceState
({
...
store
.
state
,
isSaving
:
false
,
badgeInAddForm
:
createDummyBadge
(),
});
vm
.
isEditing
=
false
;
});
it
(
'
returns immediately if imageUrl is empty
'
,
()
=>
{
store
.
state
.
badgeInAddForm
.
imageUrl
=
''
;
vm
.
onSubmit
();
expect
(
vm
.
addBadge
).
not
.
toHaveBeenCalled
();
});
it
(
'
returns immediately if linkUrl is empty
'
,
()
=>
{
store
.
state
.
badgeInAddForm
.
linkUrl
=
''
;
vm
.
onSubmit
();
expect
(
vm
.
addBadge
).
not
.
toHaveBeenCalled
();
});
it
(
'
returns immediately if isSaving is true
'
,
()
=>
{
store
.
state
.
isSaving
=
true
;
vm
.
onSubmit
();
expect
(
vm
.
addBadge
).
not
.
toHaveBeenCalled
();
});
it
(
'
calls addBadge
'
,
()
=>
{
vm
.
onSubmit
();
expect
(
vm
.
addBadge
).
toHaveBeenCalled
();
});
});
});
});
describe
(
'
if isEditing is false
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponentWithStore
(
Component
,
{
el
:
'
#dummy-element
'
,
store
,
props
:
{
isEditing
:
false
,
},
});
});
it
(
'
renders one button
'
,
()
=>
{
const
buttons
=
vm
.
$el
.
querySelectorAll
(
'
.row-content-block button
'
);
expect
(
buttons
.
length
).
toBe
(
1
);
const
buttonAddElement
=
buttons
[
0
];
expect
(
buttonAddElement
).
toBeVisible
();
expect
(
buttonAddElement
).
toHaveText
(
'
Add badge
'
);
});
});
describe
(
'
if isEditing is true
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponentWithStore
(
Component
,
{
el
:
'
#dummy-element
'
,
store
,
props
:
{
isEditing
:
true
,
},
});
});
it
(
'
renders two buttons
'
,
()
=>
{
const
buttons
=
vm
.
$el
.
querySelectorAll
(
'
.row-content-block button
'
);
expect
(
buttons
.
length
).
toBe
(
2
);
const
buttonSaveElement
=
buttons
[
0
];
expect
(
buttonSaveElement
).
toBeVisible
();
expect
(
buttonSaveElement
).
toHaveText
(
'
Save changes
'
);
const
buttonCancelElement
=
buttons
[
1
];
expect
(
buttonCancelElement
).
toBeVisible
();
expect
(
buttonCancelElement
).
toHaveText
(
'
Cancel
'
);
});
});
});
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/components/badge_list_row_spec.js
0 → 100644
View file @
31dd86b6
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
{
GROUP_BADGE
,
PROJECT_BADGE
}
from
'
~/badges/constants
'
;
import
store
from
'
~/badges/store
'
;
import
BadgeListRow
from
'
~/badges/components/badge_list_row.vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
createDummyBadge
}
from
'
../dummy_badge
'
;
describe
(
'
BadgeListRow component
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
BadgeListRow
);
let
badge
;
let
vm
;
beforeEach
(()
=>
{
setFixtures
(
`
<div id="delete-badge-modal" class="modal"></div>
<div id="dummy-element"></div>
`
);
store
.
replaceState
({
...
store
.
state
,
kind
:
PROJECT_BADGE
,
});
badge
=
createDummyBadge
();
vm
=
mountComponentWithStore
(
Component
,
{
el
:
'
#dummy-element
'
,
store
,
props
:
{
badge
},
});
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
renders the badge
'
,
()
=>
{
const
badgeElement
=
vm
.
$el
.
querySelector
(
'
.project-badge
'
);
expect
(
badgeElement
).
not
.
toBeNull
();
expect
(
badgeElement
.
getAttribute
(
'
src
'
)).
toBe
(
badge
.
renderedImageUrl
);
});
it
(
'
renders the badge link
'
,
()
=>
{
expect
(
vm
.
$el
).
toContainText
(
badge
.
linkUrl
);
});
it
(
'
renders the badge kind
'
,
()
=>
{
expect
(
vm
.
$el
).
toContainText
(
'
Project Badge
'
);
});
it
(
'
shows edit and delete buttons
'
,
()
=>
{
const
buttons
=
vm
.
$el
.
querySelectorAll
(
'
.table-button-footer button
'
);
expect
(
buttons
).
toHaveLength
(
2
);
const
buttonEditElement
=
buttons
[
0
];
expect
(
buttonEditElement
).
toBeVisible
();
expect
(
buttonEditElement
).
toHaveSpriteIcon
(
'
pencil
'
);
const
buttonDeleteElement
=
buttons
[
1
];
expect
(
buttonDeleteElement
).
toBeVisible
();
expect
(
buttonDeleteElement
).
toHaveSpriteIcon
(
'
remove
'
);
});
it
(
'
calls editBadge when clicking then edit button
'
,
()
=>
{
spyOn
(
vm
,
'
editBadge
'
);
const
editButton
=
vm
.
$el
.
querySelector
(
'
.table-button-footer button:first-of-type
'
);
editButton
.
click
();
expect
(
vm
.
editBadge
).
toHaveBeenCalled
();
});
it
(
'
calls updateBadgeInModal and shows modal when clicking then delete button
'
,
done
=>
{
spyOn
(
vm
,
'
updateBadgeInModal
'
);
$
(
'
#delete-badge-modal
'
).
on
(
'
shown.bs.modal
'
,
()
=>
done
());
const
deleteButton
=
vm
.
$el
.
querySelector
(
'
.table-button-footer button:last-of-type
'
);
deleteButton
.
click
();
expect
(
vm
.
updateBadgeInModal
).
toHaveBeenCalled
();
});
describe
(
'
for a group badge
'
,
()
=>
{
beforeEach
(
done
=>
{
badge
.
kind
=
GROUP_BADGE
;
Vue
.
nextTick
()
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
renders the badge kind
'
,
()
=>
{
expect
(
vm
.
$el
).
toContainText
(
'
Group Badge
'
);
});
it
(
'
hides edit and delete buttons
'
,
()
=>
{
const
buttons
=
vm
.
$el
.
querySelectorAll
(
'
.table-button-footer button
'
);
expect
(
buttons
).
toHaveLength
(
0
);
});
});
});
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/components/badge_list_spec.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
{
GROUP_BADGE
,
PROJECT_BADGE
}
from
'
~/badges/constants
'
;
import
store
from
'
~/badges/store
'
;
import
BadgeList
from
'
~/badges/components/badge_list.vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
createDummyBadge
}
from
'
../dummy_badge
'
;
describe
(
'
BadgeList component
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
BadgeList
);
const
numberOfDummyBadges
=
3
;
let
vm
;
beforeEach
(()
=>
{
setFixtures
(
'
<div id="dummy-element"></div>
'
);
const
badges
=
[];
for
(
let
id
=
0
;
id
<
numberOfDummyBadges
;
id
+=
1
)
{
badges
.
push
({
id
,
...
createDummyBadge
()
});
}
store
.
replaceState
({
...
store
.
state
,
badges
,
kind
:
PROJECT_BADGE
,
isLoading
:
false
,
});
vm
=
mountComponentWithStore
(
Component
,
{
el
:
'
#dummy-element
'
,
store
,
});
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
renders a header with the badge count
'
,
()
=>
{
const
header
=
vm
.
$el
.
querySelector
(
'
.panel-heading
'
);
expect
(
header
).
toHaveText
(
new
RegExp
(
`Your badges\\s+
${
numberOfDummyBadges
}
`
));
});
it
(
'
renders a row for each badge
'
,
()
=>
{
const
rows
=
vm
.
$el
.
querySelectorAll
(
'
.gl-responsive-table-row
'
);
expect
(
rows
).
toHaveLength
(
numberOfDummyBadges
);
});
it
(
'
renders a message if no badges exist
'
,
done
=>
{
store
.
state
.
badges
=
[];
Vue
.
nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
).
toContainText
(
'
This project has no badges
'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
shows a loading icon when loading
'
,
done
=>
{
store
.
state
.
isLoading
=
true
;
Vue
.
nextTick
()
.
then
(()
=>
{
const
loadingIcon
=
vm
.
$el
.
querySelector
(
'
.fa-spinner
'
);
expect
(
loadingIcon
).
toBeVisible
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
describe
(
'
for group badges
'
,
()
=>
{
beforeEach
(
done
=>
{
store
.
state
.
kind
=
GROUP_BADGE
;
Vue
.
nextTick
()
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
renders a message if no badges exist
'
,
done
=>
{
store
.
state
.
badges
=
[];
Vue
.
nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
).
toContainText
(
'
This group has no badges
'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/components/badge_settings_spec.js
0 → 100644
View file @
31dd86b6
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
store
from
'
~/badges/store
'
;
import
BadgeSettings
from
'
~/badges/components/badge_settings.vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
createDummyBadge
}
from
'
../dummy_badge
'
;
describe
(
'
BadgeSettings component
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
BadgeSettings
);
let
vm
;
beforeEach
(()
=>
{
setFixtures
(
`
<div id="dummy-element"></div>
<button
id="dummy-modal-button"
type="button"
data-toggle="modal"
data-target="#delete-badge-modal"
>Show modal</button>
`
);
vm
=
mountComponentWithStore
(
Component
,
{
el
:
'
#dummy-element
'
,
store
,
});
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
displays modal if button is clicked
'
,
done
=>
{
const
badge
=
createDummyBadge
();
store
.
state
.
badgeInModal
=
badge
;
const
modal
=
vm
.
$el
.
querySelector
(
'
#delete-badge-modal
'
);
const
button
=
document
.
getElementById
(
'
dummy-modal-button
'
);
$
(
modal
).
on
(
'
shown.bs.modal
'
,
()
=>
{
expect
(
modal
).
toContainText
(
'
Delete badge?
'
);
const
badgeElement
=
modal
.
querySelector
(
'
img.project-badge
'
);
expect
(
badgeElement
).
not
.
toBe
(
null
);
expect
(
badgeElement
.
getAttribute
(
'
src
'
)).
toBe
(
badge
.
renderedImageUrl
);
done
();
});
Vue
.
nextTick
()
.
then
(()
=>
{
button
.
click
();
})
.
catch
(
done
.
fail
);
});
it
(
'
displays a form to add a badge
'
,
()
=>
{
const
form
=
vm
.
$el
.
querySelector
(
'
form:nth-of-type(2)
'
);
expect
(
form
).
not
.
toBe
(
null
);
const
button
=
form
.
querySelector
(
'
.btn-success
'
);
expect
(
button
).
not
.
toBe
(
null
);
expect
(
button
).
toHaveText
(
/Add badge/
);
});
it
(
'
displays badge list
'
,
()
=>
{
const
badgeListElement
=
vm
.
$el
.
querySelector
(
'
.panel
'
);
expect
(
badgeListElement
).
not
.
toBe
(
null
);
expect
(
badgeListElement
).
toBeVisible
();
expect
(
badgeListElement
).
toContainText
(
'
Your badges
'
);
});
describe
(
'
when editing
'
,
()
=>
{
beforeEach
(
done
=>
{
store
.
state
.
isEditing
=
true
;
Vue
.
nextTick
()
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
displays a form to edit a badge
'
,
()
=>
{
const
form
=
vm
.
$el
.
querySelector
(
'
form:nth-of-type(1)
'
);
expect
(
form
).
not
.
toBe
(
null
);
const
submitButton
=
form
.
querySelector
(
'
.btn-success
'
);
expect
(
submitButton
).
not
.
toBe
(
null
);
expect
(
submitButton
).
toHaveText
(
/Save changes/
);
const
cancelButton
=
form
.
querySelector
(
'
.btn-cancel
'
);
expect
(
cancelButton
).
not
.
toBe
(
null
);
expect
(
cancelButton
).
toHaveText
(
/Cancel/
);
});
it
(
'
displays no badge list
'
,
()
=>
{
const
badgeListElement
=
vm
.
$el
.
querySelector
(
'
.panel
'
);
expect
(
badgeListElement
).
toBeHidden
();
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
onSubmitModal
'
,
()
=>
{
it
(
'
triggers
'
,
()
=>
{
spyOn
(
vm
,
'
deleteBadge
'
).
and
.
callFake
(()
=>
Promise
.
resolve
());
const
modal
=
vm
.
$el
.
querySelector
(
'
#delete-badge-modal
'
);
const
deleteButton
=
modal
.
querySelector
(
'
.btn-danger
'
);
deleteButton
.
click
();
const
badge
=
store
.
state
.
badgeInModal
;
expect
(
vm
.
deleteBadge
).
toHaveBeenCalledWith
(
badge
);
});
});
});
});
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/components/badge_spec.js
0 → 100644
View file @
31dd86b6
import
Vue
from
'
vue
'
;
import
Badge
from
'
~/badges/components/badge.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
DUMMY_IMAGE_URL
,
TEST_HOST
}
from
'
spec/test_constants
'
;
describe
(
'
Badge component
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
Badge
);
const
dummyProps
=
{
imageUrl
:
DUMMY_IMAGE_URL
,
linkUrl
:
`
${
TEST_HOST
}
/badge/link/url`
,
};
let
vm
;
const
findElements
=
()
=>
{
const
buttons
=
vm
.
$el
.
querySelectorAll
(
'
button
'
);
return
{
badgeImage
:
vm
.
$el
.
querySelector
(
'
img.project-badge
'
),
loadingIcon
:
vm
.
$el
.
querySelector
(
'
.fa-spinner
'
),
reloadButton
:
buttons
[
buttons
.
length
-
1
],
};
};
const
createComponent
=
(
props
,
el
=
null
)
=>
{
vm
=
mountComponent
(
Component
,
props
,
el
);
const
{
badgeImage
}
=
findElements
();
return
new
Promise
(
resolve
=>
badgeImage
.
addEventListener
(
'
load
'
,
resolve
)).
then
(()
=>
Vue
.
nextTick
(),
);
};
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
watchers
'
,
()
=>
{
describe
(
'
imageUrl
'
,
()
=>
{
it
(
'
sets isLoading and resets numRetries and hasError
'
,
done
=>
{
const
props
=
{
...
dummyProps
};
createComponent
(
props
)
.
then
(()
=>
{
expect
(
vm
.
isLoading
).
toBe
(
false
);
vm
.
hasError
=
true
;
vm
.
numRetries
=
42
;
vm
.
imageUrl
=
`
${
props
.
imageUrl
}
#something/else`
;
return
Vue
.
nextTick
();
})
.
then
(()
=>
{
expect
(
vm
.
isLoading
).
toBe
(
true
);
expect
(
vm
.
numRetries
).
toBe
(
0
);
expect
(
vm
.
hasError
).
toBe
(
false
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(
done
=>
{
createComponent
({
...
dummyProps
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
onError resets isLoading and sets hasError
'
,
()
=>
{
vm
.
hasError
=
false
;
vm
.
isLoading
=
true
;
vm
.
onError
();
expect
(
vm
.
hasError
).
toBe
(
true
);
expect
(
vm
.
isLoading
).
toBe
(
false
);
});
it
(
'
onLoad sets isLoading
'
,
()
=>
{
vm
.
isLoading
=
true
;
vm
.
onLoad
();
expect
(
vm
.
isLoading
).
toBe
(
false
);
});
it
(
'
reloadImage resets isLoading and hasError and increases numRetries
'
,
()
=>
{
vm
.
hasError
=
true
;
vm
.
isLoading
=
false
;
vm
.
numRetries
=
0
;
vm
.
reloadImage
();
expect
(
vm
.
hasError
).
toBe
(
false
);
expect
(
vm
.
isLoading
).
toBe
(
true
);
expect
(
vm
.
numRetries
).
toBe
(
1
);
});
});
describe
(
'
behavior
'
,
()
=>
{
beforeEach
(
done
=>
{
setFixtures
(
'
<div id="dummy-element"></div>
'
);
createComponent
({
...
dummyProps
},
'
#dummy-element
'
)
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
shows a badge image after loading
'
,
()
=>
{
expect
(
vm
.
isLoading
).
toBe
(
false
);
expect
(
vm
.
hasError
).
toBe
(
false
);
const
{
badgeImage
,
loadingIcon
,
reloadButton
}
=
findElements
();
expect
(
badgeImage
).
toBeVisible
();
expect
(
loadingIcon
).
toBeHidden
();
expect
(
reloadButton
).
toBeHidden
();
expect
(
vm
.
$el
.
innerText
).
toBe
(
''
);
});
it
(
'
shows a loading icon when loading
'
,
done
=>
{
vm
.
isLoading
=
true
;
Vue
.
nextTick
()
.
then
(()
=>
{
const
{
badgeImage
,
loadingIcon
,
reloadButton
}
=
findElements
();
expect
(
badgeImage
).
toBeHidden
();
expect
(
loadingIcon
).
toBeVisible
();
expect
(
reloadButton
).
toBeHidden
();
expect
(
vm
.
$el
.
innerText
).
toBe
(
''
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
shows an error and reload button if loading failed
'
,
done
=>
{
vm
.
hasError
=
true
;
Vue
.
nextTick
()
.
then
(()
=>
{
const
{
badgeImage
,
loadingIcon
,
reloadButton
}
=
findElements
();
expect
(
badgeImage
).
toBeHidden
();
expect
(
loadingIcon
).
toBeHidden
();
expect
(
reloadButton
).
toBeVisible
();
expect
(
reloadButton
).
toHaveSpriteIcon
(
'
retry
'
);
expect
(
vm
.
$el
.
innerText
.
trim
()).
toBe
(
'
No badge image
'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/dummy_badge.js
0 → 100644
View file @
31dd86b6
import
{
PROJECT_BADGE
}
from
'
~/badges/constants
'
;
import
{
DUMMY_IMAGE_URL
,
TEST_HOST
}
from
'
spec/test_constants
'
;
export
const
createDummyBadge
=
()
=>
{
const
id
=
Math
.
floor
(
1000
*
Math
.
random
());
return
{
id
,
imageUrl
:
`
${
TEST_HOST
}
/badges/
${
id
}
/image/url`
,
isDeleting
:
false
,
linkUrl
:
`
${
TEST_HOST
}
/badges/
${
id
}
/link/url`
,
kind
:
PROJECT_BADGE
,
renderedImageUrl
:
`
${
DUMMY_IMAGE_URL
}
?id=
${
id
}
`
,
renderedLinkUrl
:
`
${
TEST_HOST
}
/badges/
${
id
}
/rendered/link/url`
,
};
};
export
const
createDummyBadgeResponse
=
()
=>
({
image_url
:
`
${
TEST_HOST
}
/badge/image/url`
,
link_url
:
`
${
TEST_HOST
}
/badge/link/url`
,
kind
:
PROJECT_BADGE
,
rendered_image_url
:
DUMMY_IMAGE_URL
,
rendered_link_url
:
`
${
TEST_HOST
}
/rendered/badge/link/url`
,
});
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/store/actions_spec.js
0 → 100644
View file @
31dd86b6
This diff is collapsed.
Click to expand it.
spec/javascripts/badges/store/mutations_spec.js
0 → 100644
View file @
31dd86b6
This diff is collapsed.
Click to expand it.
spec/javascripts/fixtures/one_white_pixel.png
0 → 100644
View file @
31dd86b6
68 Bytes
This diff is collapsed.
Click to expand it.
spec/javascripts/helpers/vue_mount_component_helper.js
View file @
31dd86b6
...
@@ -3,6 +3,12 @@ export const createComponentWithStore = (Component, store, propsData = {}) => ne
...
@@ -3,6 +3,12 @@ export const createComponentWithStore = (Component, store, propsData = {}) => ne
propsData
,
propsData
,
});
});
export
const
mountComponentWithStore
=
(
Component
,
{
el
,
props
,
store
})
=>
new
Component
({
store
,
propsData
:
props
||
{
},
}).
$mount
(
el
);
export
default
(
Component
,
props
=
{},
el
=
null
)
=>
new
Component
({
export
default
(
Component
,
props
=
{},
el
=
null
)
=>
new
Component
({
propsData
:
props
,
propsData
:
props
,
}).
$mount
(
el
);
}).
$mount
(
el
);
This diff is collapsed.
Click to expand it.
spec/javascripts/matchers.js
0 → 100644
View file @
31dd86b6
export
default
{
toHaveSpriteIcon
:
()
=>
({
compare
(
element
,
iconName
)
{
if
(
!
iconName
)
{
throw
new
Error
(
'
toHaveSpriteIcon is missing iconName argument!
'
);
}
if
(
!
(
element
instanceof
HTMLElement
))
{
throw
new
Error
(
`
${
element
}
is not a DOM element!`
);
}
const
iconReferences
=
[].
slice
.
apply
(
element
.
querySelectorAll
(
'
svg use
'
));
const
matchingIcon
=
iconReferences
.
find
(
reference
=>
reference
.
getAttribute
(
'
xlink:href
'
).
endsWith
(
`#
${
iconName
}
`
));
const
result
=
{
pass
:
!!
matchingIcon
,
};
if
(
result
.
pass
)
{
result
.
message
=
`
${
element
.
outerHTML
}
contains the sprite icon "
${
iconName
}
"!`
;
}
else
{
result
.
message
=
`
${
element
.
outerHTML
}
does not contain the sprite icon "
${
iconName
}
"!`
;
const
existingIcons
=
iconReferences
.
map
((
reference
)
=>
{
const
iconUrl
=
reference
.
getAttribute
(
'
xlink:href
'
);
return
`"
${
iconUrl
.
replace
(
/^.+#/
,
''
)}
"`
;
});
if
(
existingIcons
.
length
>
0
)
{
result
.
message
+=
` (only found
${
existingIcons
.
join
(
'
,
'
)}
)`
;
}
}
return
result
;
},
}),
};
This diff is collapsed.
Click to expand it.
spec/javascripts/test_bundle.js
View file @
31dd86b6
...
@@ -7,6 +7,9 @@ import Vue from 'vue';
...
@@ -7,6 +7,9 @@ import Vue from 'vue';
import
VueResource
from
'
vue-resource
'
;
import
VueResource
from
'
vue-resource
'
;
import
{
getDefaultAdapter
}
from
'
~/lib/utils/axios_utils
'
;
import
{
getDefaultAdapter
}
from
'
~/lib/utils/axios_utils
'
;
import
{
FIXTURES_PATH
,
TEST_HOST
}
from
'
./test_constants
'
;
import
customMatchers
from
'
./matchers
'
;
const
isHeadlessChrome
=
/
\b
HeadlessChrome
\/
/
.
test
(
navigator
.
userAgent
);
const
isHeadlessChrome
=
/
\b
HeadlessChrome
\/
/
.
test
(
navigator
.
userAgent
);
Vue
.
config
.
devtools
=
!
isHeadlessChrome
;
Vue
.
config
.
devtools
=
!
isHeadlessChrome
;
...
@@ -27,15 +30,17 @@ Vue.config.errorHandler = function (err) {
...
@@ -27,15 +30,17 @@ Vue.config.errorHandler = function (err) {
Vue
.
use
(
VueResource
);
Vue
.
use
(
VueResource
);
// enable test fixtures
// enable test fixtures
jasmine
.
getFixtures
().
fixturesPath
=
'
/base/spec/javascripts/fixtures
'
;
jasmine
.
getFixtures
().
fixturesPath
=
FIXTURES_PATH
;
jasmine
.
getJSONFixtures
().
fixturesPath
=
'
/base/spec/javascripts/fixtures
'
;
jasmine
.
getJSONFixtures
().
fixturesPath
=
FIXTURES_PATH
;
beforeAll
(()
=>
jasmine
.
addMatchers
(
customMatchers
));
// globalize common libraries
// globalize common libraries
window
.
$
=
window
.
jQuery
=
$
;
window
.
$
=
window
.
jQuery
=
$
;
// stub expected globals
// stub expected globals
window
.
gl
=
window
.
gl
||
{};
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
TEST_HOST
=
'
http://test.host
'
;
window
.
gl
.
TEST_HOST
=
TEST_HOST
;
window
.
gon
=
window
.
gon
||
{};
window
.
gon
=
window
.
gon
||
{};
window
.
gon
.
test_env
=
true
;
window
.
gon
.
test_env
=
true
;
...
...
This diff is collapsed.
Click to expand it.
spec/javascripts/test_constants.js
0 → 100644
View file @
31dd86b6
export
const
FIXTURES_PATH
=
'
/base/spec/javascripts/fixtures
'
;
export
const
TEST_HOST
=
'
http://test.host
'
;
export
const
DUMMY_IMAGE_URL
=
`
${
FIXTURES_PATH
}
/one_white_pixel.png`
;
This diff is collapsed.
Click to expand it.
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