Support custom attributes on groups
This commit is contained in:
parent
6902848a9c
commit
1f773a8ef5
|
@ -15,6 +15,8 @@
|
||||||
# Anonymous users will never return any `owned` groups. They will return all
|
# Anonymous users will never return any `owned` groups. They will return all
|
||||||
# public groups instead, even if `all_available` is set to false.
|
# public groups instead, even if `all_available` is set to false.
|
||||||
class GroupsFinder < UnionFinder
|
class GroupsFinder < UnionFinder
|
||||||
|
include CustomAttributesFilter
|
||||||
|
|
||||||
def initialize(current_user = nil, params = {})
|
def initialize(current_user = nil, params = {})
|
||||||
@current_user = current_user
|
@current_user = current_user
|
||||||
@params = params
|
@params = params
|
||||||
|
@ -22,8 +24,12 @@ class GroupsFinder < UnionFinder
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
items = all_groups.map do |item|
|
items = all_groups.map do |item|
|
||||||
by_parent(item)
|
item = by_parent(item)
|
||||||
|
item = by_custom_attributes(item)
|
||||||
|
|
||||||
|
item
|
||||||
end
|
end
|
||||||
|
|
||||||
find_union(items, Group).with_route.order_id_desc
|
find_union(items, Group).with_route.order_id_desc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ class Group < Namespace
|
||||||
has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
|
has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
|
||||||
has_many :labels, class_name: 'GroupLabel'
|
has_many :labels, class_name: 'GroupLabel'
|
||||||
has_many :variables, class_name: 'Ci::GroupVariable'
|
has_many :variables, class_name: 'Ci::GroupVariable'
|
||||||
|
has_many :custom_attributes, class_name: 'GroupCustomAttribute'
|
||||||
|
|
||||||
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
||||||
validate :visibility_level_allowed_by_projects
|
validate :visibility_level_allowed_by_projects
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
class GroupCustomAttribute < ActiveRecord::Base
|
||||||
|
belongs_to :group
|
||||||
|
|
||||||
|
validates :group, :key, :value, presence: true
|
||||||
|
validates :key, uniqueness: { scope: [:group_id] }
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Support custom attributes on groups and projects
|
||||||
|
merge_request: 14593
|
||||||
|
author: Markus Koller
|
||||||
|
type: changed
|
|
@ -0,0 +1,19 @@
|
||||||
|
class CreateGroupCustomAttributes < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
def change
|
||||||
|
create_table :group_custom_attributes do |t|
|
||||||
|
t.timestamps_with_timezone null: false
|
||||||
|
t.references :group, null: false
|
||||||
|
t.string :key, null: false
|
||||||
|
t.string :value, null: false
|
||||||
|
|
||||||
|
t.index [:group_id, :key], unique: true
|
||||||
|
t.index [:key, :value]
|
||||||
|
end
|
||||||
|
|
||||||
|
add_foreign_key :group_custom_attributes, :namespaces, column: :group_id, on_delete: :cascade # rubocop: disable Migration/AddConcurrentForeignKey
|
||||||
|
end
|
||||||
|
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -693,6 +693,17 @@ ActiveRecord::Schema.define(version: 20171026082505) do
|
||||||
add_index "gpg_signatures", ["gpg_key_subkey_id"], name: "index_gpg_signatures_on_gpg_key_subkey_id", using: :btree
|
add_index "gpg_signatures", ["gpg_key_subkey_id"], name: "index_gpg_signatures_on_gpg_key_subkey_id", using: :btree
|
||||||
add_index "gpg_signatures", ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree
|
add_index "gpg_signatures", ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree
|
||||||
|
|
||||||
|
create_table "group_custom_attributes", force: :cascade do |t|
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.integer "group_id", null: false
|
||||||
|
t.string "key", null: false
|
||||||
|
t.string "value", null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "group_custom_attributes", ["group_id", "key"], name: "index_group_custom_attributes_on_group_id_and_key", unique: true, using: :btree
|
||||||
|
add_index "group_custom_attributes", ["key", "value"], name: "index_group_custom_attributes_on_key_and_value", using: :btree
|
||||||
|
|
||||||
create_table "identities", force: :cascade do |t|
|
create_table "identities", force: :cascade do |t|
|
||||||
t.string "extern_uid"
|
t.string "extern_uid"
|
||||||
t.string "provider"
|
t.string "provider"
|
||||||
|
@ -1840,6 +1851,7 @@ ActiveRecord::Schema.define(version: 20171026082505) do
|
||||||
add_foreign_key "gpg_signatures", "gpg_key_subkeys", on_delete: :nullify
|
add_foreign_key "gpg_signatures", "gpg_key_subkeys", on_delete: :nullify
|
||||||
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
|
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
|
||||||
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
|
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
|
||||||
|
add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade
|
||||||
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
|
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
|
||||||
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
|
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
|
||||||
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
|
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# Custom Attributes API
|
# Custom Attributes API
|
||||||
|
|
||||||
Every API call to custom attributes must be authenticated as administrator.
|
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.
|
Custom attributes are currently available on users, groups, and projects,
|
||||||
|
which will be referred to as "resource" in this documentation.
|
||||||
|
|
||||||
## List custom attributes
|
## List custom attributes
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ Get all custom attributes on a resource.
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /users/:id/custom_attributes
|
GET /users/:id/custom_attributes
|
||||||
|
GET /groups/:id/custom_attributes
|
||||||
GET /projects/:id/custom_attributes
|
GET /projects/:id/custom_attributes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ Get a single custom attribute on a resource.
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /users/:id/custom_attributes/:key
|
GET /users/:id/custom_attributes/:key
|
||||||
|
GET /groups/:id/custom_attributes/:key
|
||||||
GET /projects/:id/custom_attributes/:key
|
GET /projects/:id/custom_attributes/:key
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -70,6 +73,7 @@ or newly created otherwise.
|
||||||
|
|
||||||
```
|
```
|
||||||
PUT /users/:id/custom_attributes/:key
|
PUT /users/:id/custom_attributes/:key
|
||||||
|
PUT /groups/:id/custom_attributes/:key
|
||||||
PUT /projects/:id/custom_attributes/:key
|
PUT /projects/:id/custom_attributes/:key
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -98,6 +102,7 @@ Delete a custom attribute on a resource.
|
||||||
|
|
||||||
```
|
```
|
||||||
DELETE /users/:id/custom_attributes/:key
|
DELETE /users/:id/custom_attributes/:key
|
||||||
|
DELETE /groups/:id/custom_attributes/:key
|
||||||
DELETE /projects/:id/custom_attributes/:key
|
DELETE /projects/:id/custom_attributes/:key
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,12 @@ GET /groups?statistics=true
|
||||||
|
|
||||||
You can search for groups by name or path, see below.
|
You can search for groups by name or path, see below.
|
||||||
|
|
||||||
|
You can filter by [custom attributes](custom_attributes.md) with:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /groups?custom_attributes[key]=value&custom_attributes[other_key]=other_value
|
||||||
|
```
|
||||||
|
|
||||||
## List a group's projects
|
## List a group's projects
|
||||||
|
|
||||||
Get a list of projects in this group. When accessed without authentication, only
|
Get a list of projects in this group. When accessed without authentication, only
|
||||||
|
|
|
@ -37,6 +37,8 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
resource :groups do
|
resource :groups do
|
||||||
|
include CustomAttributesEndpoints
|
||||||
|
|
||||||
desc 'Get a groups list' do
|
desc 'Get a groups list' do
|
||||||
success Entities::Group
|
success Entities::Group
|
||||||
end
|
end
|
||||||
|
@ -51,7 +53,12 @@ module API
|
||||||
use :pagination
|
use :pagination
|
||||||
end
|
end
|
||||||
get do
|
get do
|
||||||
find_params = { all_available: params[:all_available], owned: params[:owned] }
|
find_params = {
|
||||||
|
all_available: params[:all_available],
|
||||||
|
owned: params[:owned],
|
||||||
|
custom_attributes: params[:custom_attributes]
|
||||||
|
}
|
||||||
|
|
||||||
groups = GroupsFinder.new(current_user, find_params).execute
|
groups = GroupsFinder.new(current_user, find_params).execute
|
||||||
groups = groups.search(params[:search]) if params[:search].present?
|
groups = groups.search(params[:search]) if params[:search].present?
|
||||||
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
|
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :group_custom_attribute do
|
||||||
|
group
|
||||||
|
sequence(:key) { |n| "key#{n}" }
|
||||||
|
sequence(:value) { |n| "value#{n}" }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe GroupCustomAttribute do
|
||||||
|
describe 'assocations' do
|
||||||
|
it { is_expected.to belong_to(:group) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
subject { build :group_custom_attribute }
|
||||||
|
|
||||||
|
it { is_expected.to validate_presence_of(:group) }
|
||||||
|
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(:group_id) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,6 +17,7 @@ describe Group do
|
||||||
it { is_expected.to have_many(:variables).class_name('Ci::GroupVariable') }
|
it { is_expected.to have_many(:variables).class_name('Ci::GroupVariable') }
|
||||||
it { is_expected.to have_many(:uploads).dependent(:destroy) }
|
it { is_expected.to have_many(:uploads).dependent(:destroy) }
|
||||||
it { is_expected.to have_one(:chat_team) }
|
it { is_expected.to have_one(:chat_team) }
|
||||||
|
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
|
||||||
|
|
||||||
describe '#members & #requesters' do
|
describe '#members & #requesters' do
|
||||||
let(:requester) { create(:user) }
|
let(:requester) { create(:user) }
|
||||||
|
|
|
@ -618,4 +618,14 @@ describe API::Groups do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'custom attributes endpoints', 'groups' do
|
||||||
|
let(:attributable) { group1 }
|
||||||
|
let(:other_attributable) { group2 }
|
||||||
|
let(:user) { user1 }
|
||||||
|
|
||||||
|
before do
|
||||||
|
group2.add_owner(user1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue