This instance will be upgraded to Heptapod 0.26.0rc1 on 2021-10-26 at 14:30 UTC+2 (a few minutes of down time)

Unverified Commit 6902848a authored by Markus Koller's avatar Markus Koller Committed by Markus Koller
Browse files

Support custom attributes on projects

parent 04d0134a4718
......@@ -18,6 +18,8 @@
# non_archived: boolean
#
class ProjectsFinder < UnionFinder
include CustomAttributesFilter
attr_accessor :params
attr_reader :current_user, :project_ids_relation
......@@ -44,6 +46,7 @@ def execute
collection = by_tags(collection)
collection = by_search(collection)
collection = by_archived(collection)
collection = by_custom_attributes(collection)
sort(collection)
end
......
......@@ -213,6 +213,7 @@ class Project < ActiveRecord::Base
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_one :auto_devops, class_name: 'ProjectAutoDevops'
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
......
class ProjectCustomAttribute < ActiveRecord::Base
belongs_to :project
validates :project, :key, :value, presence: true
validates :key, uniqueness: { scope: [:project_id] }
end
class CreateProjectCustomAttributes < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :project_custom_attributes do |t|
t.timestamps_with_timezone null: false
t.references :project, null: false, foreign_key: { on_delete: :cascade }
t.string :key, null: false
t.string :value, null: false
t.index [:project_id, :key], unique: true
t.index [:key, :value]
end
end
end
......@@ -1214,6 +1214,17 @@
add_index "project_auto_devops", ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree
create_table "project_custom_attributes", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "project_id", null: false
t.string "key", null: false
t.string "value", null: false
end
add_index "project_custom_attributes", ["key", "value"], name: "index_project_custom_attributes_on_key_and_value", using: :btree
add_index "project_custom_attributes", ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
create_table "project_features", force: :cascade do |t|
t.integer "project_id"
t.integer "merge_requests_access_level"
......@@ -1859,6 +1870,7 @@
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
......
# Custom Attributes API
Every API call to custom attributes must be authenticated as administrator.
Custom attributes are currently available on users and projects, which will
be referred to as "resource" in this documentation.
## List custom attributes
Get all custom attributes on a user.
Get all custom attributes on a resource.
```
GET /users/:id/custom_attributes
GET /projects/:id/custom_attributes
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `id` | integer | yes | The ID of a resource |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes
......@@ -35,15 +38,16 @@ Example response:
## Single custom attribute
Get a single custom attribute on a user.
Get a single custom attribute on a resource.
```
GET /users/:id/custom_attributes/:key
GET /projects/:id/custom_attributes/:key
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `id` | integer | yes | The ID of a resource |
| `key` | string | yes | The key of the custom attribute |
```bash
......@@ -61,16 +65,17 @@ Example response:
## Set custom attribute
Set a custom attribute on a user. The attribute will be updated if it already exists,
Set a custom attribute on a resource. The attribute will be updated if it already exists,
or newly created otherwise.
```
PUT /users/:id/custom_attributes/:key
PUT /projects/:id/custom_attributes/:key
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `id` | integer | yes | The ID of a resource |
| `key` | string | yes | The key of the custom attribute |
| `value` | string | yes | The value of the custom attribute |
......@@ -89,15 +94,16 @@ Example response:
## Delete custom attribute
Delete a custom attribute on a user.
Delete a custom attribute on a resource.
```
DELETE /users/:id/custom_attributes/:key
DELETE /projects/:id/custom_attributes/:key
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a user |
| `id` | integer | yes | The ID of a resource |
| `key` | string | yes | The key of the custom attribute |
```bash
......
......@@ -192,6 +192,12 @@ GET /projects
]
```
You can filter by [custom attributes](custom_attributes.md) with:
```
GET /projects?custom_attributes[key]=value&custom_attributes[other_key]=other_value
```
## List user projects
Get a list of visible projects for the given user. When accessed without
......
......@@ -328,6 +328,7 @@ def project_finder_params
finder_params[:archived] = params[:archived]
finder_params[:search] = params[:search] if params[:search]
finder_params[:user] = params.delete(:user) if params[:user]
finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
finder_params
end
......
......@@ -119,6 +119,8 @@ def present_projects(projects, options = {})
end
resource :projects do
include CustomAttributesEndpoints
desc 'Get a list of visible projects for authenticated user' do
success Entities::BasicProjectDetails
end
......
......@@ -63,6 +63,7 @@ project_tree:
- protected_tags:
- :create_access_levels
- :project_feature
- :custom_attributes
# Only include the following attributes for the models specified.
included_attributes:
......
......@@ -17,7 +17,8 @@ class RelationFactory
labels: :project_labels,
priorities: :label_priorities,
auto_devops: :project_auto_devops,
label: :project_label }.freeze
label: :project_label,
custom_attributes: 'ProjectCustomAttribute' }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id].freeze
......
FactoryGirl.define do
factory :project_custom_attribute do
project
sequence(:key) { |n| "key#{n}" }
sequence(:value) { |n| "value#{n}" }
end
end
......@@ -276,6 +276,7 @@ project:
- root_of_fork_network
- fork_network_member
- fork_network
- custom_attributes
award_emoji:
- awardable
- user
......
......@@ -7408,5 +7408,23 @@
"snippets_access_level": 20,
"updated_at": "2016-09-23T11:58:28.000Z",
"wiki_access_level": 20
}
},
"custom_attributes": [
{
"id": 1,
"created_at": "2017-10-19T15:36:23.466Z",
"updated_at": "2017-10-19T15:36:23.466Z",
"project_id": 5,
"key": "foo",
"value": "foo"
},
{
"id": 2,
"created_at": "2017-10-19T15:37:21.904Z",
"updated_at": "2017-10-19T15:37:21.904Z",
"project_id": 5,
"key": "bar",
"value": "bar"
}
]
}
......@@ -133,6 +133,10 @@
expect(@project.project_feature).not_to be_nil
end
it 'has custom attributes' do
expect(@project.custom_attributes.count).to eq(2)
end
it 'restores the correct service' do
expect(CustomIssueTrackerService.first).not_to be_nil
end
......
......@@ -168,6 +168,10 @@
expect(project_feature["builds_access_level"]).to eq(ProjectFeature::PRIVATE)
end
it 'has custom attributes' do
expect(saved_project_json['custom_attributes'].count).to eq(2)
end
it 'does not complain about non UTF-8 characters in MR diffs' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
......@@ -279,6 +283,9 @@ def setup_project
create(:event, :created, target: milestone, project: project, author: user)
create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker')
create(:project_custom_attribute, project: project)
create(:project_custom_attribute, project: project)
project
end
......
......@@ -508,4 +508,11 @@ ProjectAutoDevops:
- updated_at
IssueAssignee:
- user_id
- issue_id
\ No newline at end of file
- issue_id
ProjectCustomAttribute:
- id
- created_at
- updated_at
- project_id
- key
- value
require 'spec_helper'
describe ProjectCustomAttribute do
describe 'assocations' do
it { is_expected.to belong_to(:project) }
end
describe 'validations' do
subject { build :project_custom_attribute }
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:key) }
it { is_expected.to validate_presence_of(:value) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id) }
end
end
......@@ -79,6 +79,7 @@
it { is_expected.to have_many(:pipeline_schedules) }
it { is_expected.to have_many(:members_and_requesters) }
it { is_expected.to have_one(:cluster) }
it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
context 'after initialized' do
it "has a project_feature" do
......
......@@ -1856,4 +1856,9 @@
end
end
end
it_behaves_like 'custom attributes endpoints', 'projects' do
let(:attributable) { project }
let(:other_attributable) { project2 }
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment