Commit 1f773a8e authored by Markus Koller's avatar Markus Koller Committed by Markus Koller

Support custom attributes on groups

parent 6902848a
...@@ -15,6 +15,8 @@ ...@@ -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 ...@@ -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 ...@@ -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
......
class GroupCustomAttribute < ActiveRecord::Base
belongs_to :group
validates :group, :key, :value, presence: true
validates :key, uniqueness: { scope: [:group_id] }
end
---
title: Support custom attributes on groups and projects
merge_request: 14593
author: Markus Koller
type: changed
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
...@@ -693,6 +693,17 @@ ActiveRecord::Schema.define(version: 20171026082505) do ...@@ -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 ...@@ -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
......
# 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. ...@@ -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. ...@@ -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. ...@@ -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. ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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?
......
FactoryGirl.define do
factory :group_custom_attribute do
group
sequence(:key) { |n| "key#{n}" }
sequence(:value) { |n| "value#{n}" }
end
end
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 ...@@ -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 ...@@ -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
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