Commit 94e2a286 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'feature/user-datetime-search-api-mysql' into 'master'

Add creation time filters to user search API for admins

Closes #29507

See merge request !12682
parents ac09bbdc 6d28ad84
module CreatedAtFilter
def by_created_at(items)
items = items.created_before(params[:created_before]) if params[:created_before].present?
items = items.created_after(params[:created_after]) if params[:created_after].present?
items
end
end
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
# iids: integer[] # iids: integer[]
# #
class IssuableFinder class IssuableFinder
include CreatedAtFilter
NONE = '0'.freeze NONE = '0'.freeze
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
...@@ -32,6 +34,7 @@ class IssuableFinder ...@@ -32,6 +34,7 @@ class IssuableFinder
def execute def execute
items = init_collection items = init_collection
items = by_scope(items) items = by_scope(items)
items = by_created_at(items)
items = by_state(items) items = by_state(items)
items = by_group(items) items = by_group(items)
items = by_search(items) items = by_search(items)
...@@ -42,7 +45,6 @@ class IssuableFinder ...@@ -42,7 +45,6 @@ class IssuableFinder
items = by_iids(items) items = by_iids(items)
items = by_milestone(items) items = by_milestone(items)
items = by_label(items) items = by_label(items)
items = by_created_at(items)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far # Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items = by_project(items) items = by_project(items)
...@@ -411,18 +413,6 @@ class IssuableFinder ...@@ -411,18 +413,6 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items params[:non_archived].present? ? items.non_archived : items
end end
def by_created_at(items)
if params[:created_after].present?
items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
end
if params[:created_before].present?
items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
end
items
end
def current_user_related? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end end
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
# external: boolean # external: boolean
# #
class UsersFinder class UsersFinder
include CreatedAtFilter
attr_accessor :current_user, :params attr_accessor :current_user, :params
def initialize(current_user, params = {}) def initialize(current_user, params = {})
...@@ -29,6 +31,7 @@ class UsersFinder ...@@ -29,6 +31,7 @@ class UsersFinder
users = by_active(users) users = by_active(users)
users = by_external_identity(users) users = by_external_identity(users)
users = by_external(users) users = by_external(users)
users = by_created_at(users)
users users
end end
......
module CreatedAtFilterable
extend ActiveSupport::Concern
included do
scope :created_before, ->(date) { where(scoped_table[:created_at].lteq(date)) }
scope :created_after, ->(date) { where(scoped_table[:created_at].gteq(date)) }
def self.scoped_table
arel_table.alias(table_name)
end
end
end
...@@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base ...@@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys include FasterCacheKeys
include RelativePositioning include RelativePositioning
include IgnorableColumn include IgnorableColumn
include CreatedAtFilterable
ignore_column :position ignore_column :position
...@@ -50,8 +51,6 @@ class Issue < ActiveRecord::Base ...@@ -50,8 +51,6 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :preload_associations, -> { preload(:labels, project: :namespace) }
after_save :expire_etag_cache after_save :expire_etag_cache
......
...@@ -5,6 +5,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -5,6 +5,7 @@ class MergeRequest < ActiveRecord::Base
include Referable include Referable
include Sortable include Sortable
include IgnorableColumn include IgnorableColumn
include CreatedAtFilterable
ignore_column :position ignore_column :position
......
...@@ -12,6 +12,7 @@ class User < ActiveRecord::Base ...@@ -12,6 +12,7 @@ class User < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
include IgnorableColumn include IgnorableColumn
include FeatureGate include FeatureGate
include CreatedAtFilterable
DEFAULT_NOTIFICATION_LEVEL = :participating DEFAULT_NOTIFICATION_LEVEL = :participating
......
---
title: Add creation time filters to user search API for admins
merge_request: 12682
author:
...@@ -26,7 +26,8 @@ module Gitlab ...@@ -26,7 +26,8 @@ module Gitlab
#{config.root}/app/models/members #{config.root}/app/models/members
#{config.root}/app/models/project_services #{config.root}/app/models/project_services
#{config.root}/app/workers/concerns #{config.root}/app/workers/concerns
#{config.root}/app/services/concerns)) #{config.root}/app/services/concerns
#{config.root}/app/finders/concerns))
config.generators.templates.push("#{config.root}/generator_templates") config.generators.templates.push("#{config.root}/generator_templates")
......
...@@ -146,6 +146,12 @@ GET /users?extern_uid=1234567&provider=github ...@@ -146,6 +146,12 @@ GET /users?extern_uid=1234567&provider=github
You can search for users who are external with: `/users?external=true` You can search for users who are external with: `/users?external=true`
You can search users by creation date time range with:
```
GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060
```
## Single user ## Single user
Get a single user. Get a single user.
......
...@@ -48,6 +48,8 @@ module API ...@@ -48,6 +48,8 @@ module API
optional :active, type: Boolean, default: false, desc: 'Filters only active users' optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users' optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
use :pagination use :pagination
...@@ -55,6 +57,10 @@ module API ...@@ -55,6 +57,10 @@ module API
get do get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin?
params.except!(:created_after, :created_before)
end
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
authorized = can?(current_user, :read_users_list) authorized = can?(current_user, :read_users_list)
......
...@@ -45,6 +45,17 @@ describe UsersFinder do ...@@ -45,6 +45,17 @@ describe UsersFinder do
expect(users).to contain_exactly(user, user1, user2, omniauth_user) expect(users).to contain_exactly(user, user1, user2, omniauth_user)
end end
it 'filters by created_at' do
filtered_user_before = create(:user, created_at: 3.days.ago)
filtered_user_after = create(:user, created_at: Time.now + 3.days)
users = described_class.new(user,
created_after: 2.days.ago,
created_before: Time.now + 2.days).execute
expect(users.map(&:username)).not_to include([filtered_user_before.username, filtered_user_after.username])
end
end end
context 'with an admin user' do context 'with an admin user' do
......
...@@ -163,6 +163,35 @@ describe API::Users do ...@@ -163,6 +163,35 @@ describe API::Users do
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it "returns a user created before a specific date" do
user = create(:user, created_at: Date.new(2000, 1, 1))
get api("/users?created_before=2000-01-02T00:00:00.060Z", admin)
expect(response).to have_http_status(200)
expect(json_response.size).to eq(1)
expect(json_response.first['username']).to eq(user.username)
end
it "returns no users created before a specific date" do
create(:user, created_at: Date.new(2001, 1, 1))
get api("/users?created_before=2000-01-02T00:00:00.060Z", admin)
expect(response).to have_http_status(200)
expect(json_response.size).to eq(0)
end
it "returns users created before and after a specific date" do
user = create(:user, created_at: Date.new(2001, 1, 1))
get api("/users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060", admin)
expect(response).to have_http_status(200)
expect(json_response.size).to eq(1)
expect(json_response.first['username']).to eq(user.username)
end
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