Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
d3a9c6ae9f70
Commit
d3a9c6ae
authored
Mar 05, 2018
by
Francisco Javier López
Browse files
Projects and groups badges API
parent
06067a06b420
Changes
43
Hide whitespace changes
Inline
Side-by-side
app/models/badge.rb
0 → 100644
View file @
d3a9c6ae
class
Badge
<
ActiveRecord
::
Base
# This structure sets the placeholders that the urls
# can have. This hash also sets which action to ask when
# the placeholder is found.
PLACEHOLDERS
=
{
'project_path'
=>
:full_path
,
'project_id'
=>
:id
,
'default_branch'
=>
:default_branch
,
'commit_sha'
=>
->
(
project
)
{
project
.
commit
&
.
sha
}
}.
freeze
# This regex is built dynamically using the keys from the PLACEHOLDER struct.
# So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
# This regex will build the new PLACEHOLDER_REGEX with the new information
PLACEHOLDERS_REGEX
=
/(
#{
PLACEHOLDERS
.
keys
.
join
(
'|'
)
}
)/
.
freeze
default_scope
{
order_created_at_asc
}
scope
:order_created_at_asc
,
->
{
reorder
(
created_at: :asc
)
}
validates
:link_url
,
:image_url
,
url_placeholder:
{
protocols:
%w(http https)
,
placeholder_regex:
PLACEHOLDERS_REGEX
}
validates
:type
,
presence:
true
def
rendered_link_url
(
project
=
nil
)
build_rendered_url
(
link_url
,
project
)
end
def
rendered_image_url
(
project
=
nil
)
build_rendered_url
(
image_url
,
project
)
end
private
def
build_rendered_url
(
url
,
project
=
nil
)
return
url
unless
valid?
&&
project
Gitlab
::
StringPlaceholderReplacer
.
replace_string_placeholders
(
url
,
PLACEHOLDERS_REGEX
)
do
|
arg
|
replace_placeholder_action
(
PLACEHOLDERS
[
arg
],
project
)
end
end
# The action param represents the :symbol or Proc to call in order
# to retrieve the return value from the project.
# This method checks if it is a Proc and use the call method, and if it is
# a symbol just send the action
def
replace_placeholder_action
(
action
,
project
)
return
unless
project
action
.
is_a?
(
Proc
)
?
action
.
call
(
project
)
:
project
.
public_send
(
action
)
# rubocop:disable GitlabSecurity/PublicSend
end
end
app/models/badges/group_badge.rb
0 → 100644
View file @
d3a9c6ae
class
GroupBadge
<
Badge
belongs_to
:group
validates
:group
,
presence:
true
end
app/models/badges/project_badge.rb
0 → 100644
View file @
d3a9c6ae
class
ProjectBadge
<
Badge
belongs_to
:project
validates
:project
,
presence:
true
def
rendered_link_url
(
project
=
nil
)
project
||=
self
.
project
super
end
def
rendered_image_url
(
project
=
nil
)
project
||=
self
.
project
super
end
end
app/models/group.rb
View file @
d3a9c6ae
...
...
@@ -31,6 +31,8 @@
has_many
:uploads
,
as: :model
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:badges
,
class_name:
'GroupBadge'
accepts_nested_attributes_for
:variables
,
allow_destroy:
true
validate
:visibility_level_allowed_by_projects
...
...
app/models/project.rb
View file @
d3a9c6ae
...
...
@@ -221,6 +221,8 @@
has_one
:auto_devops
,
class_name:
'ProjectAutoDevops'
has_many
:custom_attributes
,
class_name:
'ProjectCustomAttribute'
has_many
:project_badges
,
class_name:
'ProjectBadge'
accepts_nested_attributes_for
:variables
,
allow_destroy:
true
accepts_nested_attributes_for
:project_feature
,
update_only:
true
accepts_nested_attributes_for
:import_data
...
...
@@ -1766,6 +1768,17 @@
.
set
(
import_jid
,
StuckImportJobsWorker
::
IMPORT_JOBS_EXPIRATION
)
end
def
badges
return
project_badges
unless
group
group_badges_rel
=
GroupBadge
.
where
(
group:
group
.
self_and_ancestors
)
union
=
Gitlab
::
SQL
::
Union
.
new
([
project_badges
.
select
(
:id
),
group_badges_rel
.
select
(
:id
)])
Badge
.
where
(
"id IN (
#{
union
.
to_sql
}
)"
)
# rubocop:disable GitlabSecurity/SqlInjection
end
private
def
storage
...
...
app/services/badges/base_service.rb
0 → 100644
View file @
d3a9c6ae
module
Badges
class
BaseService
protected
attr_accessor
:params
def
initialize
(
params
=
{})
@params
=
params
.
dup
end
end
end
app/services/badges/build_service.rb
0 → 100644
View file @
d3a9c6ae
module
Badges
class
BuildService
<
Badges
::
BaseService
# returns the created badge
def
execute
(
source
)
if
source
.
is_a?
(
Group
)
GroupBadge
.
new
(
params
.
merge
(
group:
source
))
else
ProjectBadge
.
new
(
params
.
merge
(
project:
source
))
end
end
end
end
app/services/badges/create_service.rb
0 → 100644
View file @
d3a9c6ae
module
Badges
class
CreateService
<
Badges
::
BaseService
# returns the created badge
def
execute
(
source
)
badge
=
Badges
::
BuildService
.
new
(
params
).
execute
(
source
)
badge
.
tap
{
|
b
|
b
.
save
}
end
end
end
app/services/badges/update_service.rb
0 → 100644
View file @
d3a9c6ae
module
Badges
class
UpdateService
<
Badges
::
BaseService
# returns the updated badge
def
execute
(
badge
)
if
params
.
present?
badge
.
update_attributes
(
params
)
end
badge
end
end
end
app/validators/url_placeholder_validator.rb
0 → 100644
View file @
d3a9c6ae
# UrlValidator
#
# Custom validator for URLs.
#
# By default, only URLs for the HTTP(S) protocols will be considered valid.
# Provide a `:protocols` option to configure accepted protocols.
#
# Also, this validator can help you validate urls with placeholders inside.
# Usually, if you have a url like 'http://www.example.com/%{project_path}' the
# URI parser will reject that URL format. Provide a `:placeholder_regex` option
# to configure accepted placeholders.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :personal_url, url: true
#
# validates :ftp_url, url: { protocols: %w(ftp) }
#
# validates :git_url, url: { protocols: %w(http https ssh git) }
#
# validates :placeholder_url, url: { placeholder_regex: /(project_path|project_id|default_branch)/ }
# end
#
class
UrlPlaceholderValidator
<
UrlValidator
def
validate_each
(
record
,
attribute
,
value
)
placeholder_regex
=
self
.
options
[
:placeholder_regex
]
value
=
value
.
gsub
(
/%{
#{
placeholder_regex
}
}/
,
'foo'
)
if
placeholder_regex
&&
value
super
(
record
,
attribute
,
value
)
end
end
app/views/projects/_home_panel.html.haml
View file @
d3a9c6ae
...
...
@@ -23,6 +23,12 @@
-
deleted_message
=
s_
(
'ForkedFromProjectPath|Forked from %{project_name} (deleted)'
)
=
deleted_message
%
{
project_name:
fork_source_name
(
@project
)
}
.project-badges
-
@project
.
badges
.
each
do
|
badge
|
-
badge_link_url
=
badge
.
rendered_link_url
(
@project
)
%a
{
href:
badge_link_url
,
target:
'_blank'
,
rel:
'noopener noreferrer'
}
%img
{
src:
badge
.
rendered_image_url
(
@project
),
alt:
badge_link_url
}
.project-repo-buttons
.count-buttons
=
render
'projects/buttons/star'
...
...
changelogs/unreleased/fj-41174-projects-groups-badges-api.yml
0 → 100644
View file @
d3a9c6ae
---
title
:
Implemented badge API endpoints
merge_request
:
17082
author
:
type
:
added
config/application.rb
View file @
d3a9c6ae
...
...
@@ -26,6 +26,7 @@
# This is a nice reference article on autoloading/eager loading:
# http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
config
.
eager_load_paths
.
push
(
*
%W[
#{
config
.
root
}
/lib
#{
config
.
root
}
/app/models/badges
#{
config
.
root
}
/app/models/hooks
#{
config
.
root
}
/app/models/members
#{
config
.
root
}
/app/models/project_services
...
...
db/migrate/20180214093516_create_badges.rb
0 → 100644
View file @
d3a9c6ae
class
CreateBadges
<
ActiveRecord
::
Migration
DOWNTIME
=
false
def
change
create_table
:badges
do
|
t
|
t
.
string
:link_url
,
null:
false
t
.
string
:image_url
,
null:
false
t
.
references
:project
,
index:
true
,
foreign_key:
{
on_delete: :cascade
},
null:
true
t
.
integer
:group_id
,
index:
true
,
null:
true
t
.
string
:type
,
null:
false
t
.
timestamps_with_timezone
null:
false
end
add_foreign_key
:badges
,
:namespaces
,
column: :group_id
,
on_delete: :cascade
end
end
db/schema.rb
View file @
d3a9c6ae
...
...
@@ -183,6 +183,19 @@
add_index
"award_emoji"
,
[
"awardable_type"
,
"awardable_id"
],
name:
"index_award_emoji_on_awardable_type_and_awardable_id"
,
using: :btree
add_index
"award_emoji"
,
[
"user_id"
,
"name"
],
name:
"index_award_emoji_on_user_id_and_name"
,
using: :btree
create_table
"badges"
,
force: :cascade
do
|
t
|
t
.
string
"link_url"
,
null:
false
t
.
string
"image_url"
,
null:
false
t
.
integer
"project_id"
t
.
integer
"group_id"
t
.
string
"type"
,
null:
false
t
.
datetime_with_timezone
"created_at"
,
null:
false
t
.
datetime_with_timezone
"updated_at"
,
null:
false
end
add_index
"badges"
,
[
"group_id"
],
name:
"index_badges_on_group_id"
,
using: :btree
add_index
"badges"
,
[
"project_id"
],
name:
"index_badges_on_project_id"
,
using: :btree
create_table
"boards"
,
force: :cascade
do
|
t
|
t
.
integer
"project_id"
,
null:
false
t
.
datetime
"created_at"
,
null:
false
...
...
@@ -1969,6 +1982,8 @@
add_index
"web_hooks"
,
[
"project_id"
],
name:
"index_web_hooks_on_project_id"
,
using: :btree
add_index
"web_hooks"
,
[
"type"
],
name:
"index_web_hooks_on_type"
,
using: :btree
add_foreign_key
"badges"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"badges"
,
"projects"
,
on_delete: :cascade
add_foreign_key
"boards"
,
"projects"
,
name:
"fk_f15266b5f9"
,
on_delete: :cascade
add_foreign_key
"chat_teams"
,
"namespaces"
,
on_delete: :cascade
add_foreign_key
"ci_build_trace_section_names"
,
"projects"
,
on_delete: :cascade
...
...
doc/api/README.md
View file @
d3a9c6ae
...
...
@@ -24,6 +24,7 @@
-
[
GitLab CI Config templates
](
templates/gitlab_ci_ymls.md
)
-
[
Groups
](
groups.md
)
-
[
Group Access Requests
](
access_requests.md
)
-
[
Group Badges
](
group_badges.md
)
-
[
Group Members
](
members.md
)
-
[
Issues
](
issues.md
)
-
[
Issue Boards
](
boards.md
)
...
...
@@ -43,6 +44,7 @@
-
[
Pipeline Schedules
](
pipeline_schedules.md
)
-
[
Projects
](
projects.md
)
including setting Webhooks
-
[
Project Access Requests
](
access_requests.md
)
-
[
Project Badges
](
project_badges.md
)
-
[
Project import/export
](
project_import_export.md
)
-
[
Project Members
](
members.md
)
-
[
Project Snippets
](
project_snippets.md
)
...
...
doc/api/group_badges.md
0 → 100644
View file @
d3a9c6ae
# Group badges API
## Placeholder tokens
Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
-
**%{project_path}**
: will be replaced by the project path.
-
**%{project_id}**
: will be replaced by the project id.
-
**%{default_branch}**
: will be replaced by the project default branch.
-
**%{commit_sha}**
: will be replaced by the last project's commit sha.
Because these enpoints aren't inside a project's context, the information used to replace the placeholders will be
from the first group's project by creation date. If the group hasn't got any project the original URL with the placeholders will be returned.
## List all badges of a group
Gets a list of a group's badges.
```
GET /groups/:id/badges
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the group
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
```
bash
curl
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/:id/badges
```
Example response:
```
json
[
{
"id"
:
1
,
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"group"
},
{
"id"
:
2
,
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"group"
},
]
```
## Get a badge of a group
Gets a badge of a group.
```
GET /groups/:id/badges/:badge_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the group
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`badge_id`
| integer | yes | The badge ID |
```
bash
curl
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
```
Example response:
```
json
{
"id"
:
1
,
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"group"
}
```
## Add a badge to a group
Adds a badge to a group.
```
POST /groups/:id/badges
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the group
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`link_url`
| string | yes | URL of the badge link |
|
`image_url`
| string | yes | URL of the badge image |
```
bash
curl
--request
POST
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
--data
"link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0"
https://gitlab.example.com/api/v4/groups/:id/badges
```
Example response:
```
json
{
"id"
:
1
,
"link_url"
:
"https://gitlab.com/gitlab-org/gitlab-ce/commits/master"
,
"image_url"
:
"https://shields.io/my/badge1"
,
"rendered_link_url"
:
"https://gitlab.com/gitlab-org/gitlab-ce/commits/master"
,
"rendered_image_url"
:
"https://shields.io/my/badge1"
,
"kind"
:
"group"
}
```
## Edit a badge of a group
Updates a badge of a group.
```
PUT /groups/:id/badges/:badge_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the group
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`badge_id`
| integer | yes | The badge ID |
|
`link_url`
| string | no | URL of the badge link |
|
`image_url`
| string | no | URL of the badge image |
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
```
Example response:
```
json
{
"id"
:
1
,
"link_url"
:
"https://gitlab.com/gitlab-org/gitlab-ce/commits/master"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"https://gitlab.com/gitlab-org/gitlab-ce/commits/master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"group"
}
```
## Remove a badge from a group
Removes a badge from a group.
```
DELETE /groups/:id/badges/:badge_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the group
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`badge_id`
| integer | yes | The badge ID |
```
bash
curl
--request
DELETE
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
```
## Preview a badge from a group
Returns how the
`link_url`
and
`image_url`
final URLs would be after resolving the placeholder interpolation.
```
GET /groups/:id/badges/render
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the group
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`link_url`
| string | yes | URL of the badge link|
|
`image_url`
| string | yes | URL of the badge image |
```
bash
curl
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/:id/badges/render?link_url
=
http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url
=
https%3A%2F%2Fshields.io%2Fmy%2Fbadge
```
Example response:
```
json
{
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
}
```
doc/api/groups.md
View file @
d3a9c6ae
...
...
@@ -525,3 +525,7 @@
```
[
ce-15142
]:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15142
## Group badges
Read more in the
[
Group Badges
](
group_badges.md
)
documentation.
doc/api/project_badges.md
0 → 100644
View file @
d3a9c6ae
# Project badges API
## Placeholder tokens
Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are:
-
**%{project_path}**
: will be replaced by the project path.
-
**%{project_id}**
: will be replaced by the project id.
-
**%{default_branch}**
: will be replaced by the project default branch.
-
**%{commit_sha}**
: will be replaced by the last project's commit sha.
## List all badges of a project
Gets a list of a project's badges and its group badges.
```
GET /projects/:id/badges
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
```
bash
curl
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/:id/badges
```
Example response:
```
json
[
{
"id"
:
1
,
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"project"
},
{
"id"
:
2
,
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"group"
},
]
```
## Get a badge of a project
Gets a badge of a project.
```
GET /projects/:id/badges/:badge_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`badge_id`
| integer | yes | The badge ID |
```
bash
curl
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
```
Example response:
```
json
{
"id"
:
1
,
"link_url"
:
"http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}"
,
"image_url"
:
"https://shields.io/my/badge"
,
"rendered_link_url"
:
"http://example.com/ci_status.svg?project=example-org/example-project&ref=master"
,
"rendered_image_url"
:
"https://shields.io/my/badge"
,
"kind"
:
"project"
}
```
## Add a badge to a project
Adds a badge to a project.
```
POST /projects/:id/badges
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`link_url`
| string | yes | URL of the badge link |
|
`image_url`
| string | yes | URL of the badge image |
```
bash
curl
--request
POST
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
--data
"link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0"
https://gitlab.example.com/api/v4/projects/:id/badges
```
Example response:
```
json
{
"id"
:
1
,
"link_url"
:
"https://gitlab.com/gitlab-org/gitlab-ce/commits/master"
,
"image_url"
:
"https://shields.io/my/badge1"
,
"rendered_link_url"
:
"https://gitlab.com/gitlab-org/gitlab-ce/commits/master"
,
"rendered_image_url"
:
"https://shields.io/my/badge1"
,
"kind"
:
"project"
}
```
## Edit a badge of a project
Updates a badge of a project.
```
PUT /projects/:id/badges/:badge_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`badge_id`
| integer | yes | The badge ID |
|
`link_url`
| string | no | URL of the badge link |
|
`image_url`
| string | no | URL of the badge image |
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id