summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShinya Maeda <shinya@gitlab.com>2019-02-05 16:14:30 +0900
committerShinya Maeda <shinya@gitlab.com>2019-02-06 18:14:18 +0900
commite8d9df83a6f5638b3207885220b9441b7b894e46 (patch)
treed28aa89c50dd1916721d2aad562fb95249d40f75
parent591380a3f1bf1b5220f176f082af297831a1886e (diff)
downloadgitlab-ce-introduce-environment-search-endpoint.tar.gz
Inroduce Internal API for searching environment namesintroduce-environment-search-endpoint
Add changelog Rename word to query User hash for limit Do not allow control limit Rename pluck names and add more specs
-rw-r--r--app/controllers/projects/environments_controller.rb16
-rw-r--r--app/models/environment.rb12
-rw-r--r--changelogs/unreleased/introduce-environment-search-endpoint.yml5
-rw-r--r--config/routes/project.rb1
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb73
-rw-r--r--spec/models/environment_spec.rb70
6 files changed, 177 insertions, 0 deletions
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 4e85de25c6b..79685e8b675 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -158,6 +158,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
+ def search
+ respond_to do |format|
+ format.json do
+ environment_names = search_environment_names
+
+ render json: environment_names, status: environment_names.any? ? :ok : :no_content
+ end
+ end
+ end
+
private
def verify_api_request!
@@ -181,6 +191,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment ||= project.environments.find(params[:id])
end
+ def search_environment_names
+ return [] unless params[:query]
+
+ project.environments.for_name_like(params[:query]).pluck_names
+ end
+
def serialize_environments(request, response, nested = false)
EnvironmentSerializer
.new(project: @project, current_user: @current_user)
diff --git a/app/models/environment.rb b/app/models/environment.rb
index cdfe3b7c023..1fc088b12ae 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -50,6 +50,14 @@ class Environment < ActiveRecord::Base
end
scope :in_review_folder, -> { where(environment_type: "review") }
scope :for_name, -> (name) { where(name: name) }
+
+ ##
+ # Search environments which have names like the given query.
+ # Do not set a large limit unless you've confirmed that it works on gitlab.com scale.
+ scope :for_name_like, -> (query, limit: 5) do
+ where('name LIKE ?', "#{sanitize_sql_like(query)}%").limit(limit)
+ end
+
scope :for_project, -> (project) { where(project_id: project) }
scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) }
@@ -70,6 +78,10 @@ class Environment < ActiveRecord::Base
end
end
+ def self.pluck_names
+ pluck(:name)
+ end
+
def predefined_variables
Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_ENVIRONMENT_NAME', value: name)
diff --git a/changelogs/unreleased/introduce-environment-search-endpoint.yml b/changelogs/unreleased/introduce-environment-search-endpoint.yml
new file mode 100644
index 00000000000..01851ba7d27
--- /dev/null
+++ b/changelogs/unreleased/introduce-environment-search-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce Internal API for searching environment names
+merge_request: 24923
+author:
+type: added
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 21793e7756a..d730479cf2b 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -224,6 +224,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
collection do
get :metrics, action: :metrics_redirect
get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ }
+ get :search
end
resources :deployments, only: [:index] do
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index a4d494a820f..aa97a417a98 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -422,6 +422,79 @@ describe Projects::EnvironmentsController do
end
end
+ describe 'GET #search' do
+ before do
+ create(:environment, name: 'staging', project: project)
+ create(:environment, name: 'review/patch-1', project: project)
+ create(:environment, name: 'review/patch-2', project: project)
+ end
+
+ let(:query) { 'pro' }
+
+ it 'responds with status code 200' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns matched results' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(json_response).to contain_exactly('production')
+ end
+
+ context 'when query is review' do
+ let(:query) { 'review' }
+
+ it 'returns matched results' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(json_response).to contain_exactly('review/patch-1', 'review/patch-2')
+ end
+ end
+
+ context 'when query is empty' do
+ let(:query) { '' }
+
+ it 'returns matched results' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(json_response)
+ .to contain_exactly('production', 'staging', 'review/patch-1', 'review/patch-2')
+ end
+ end
+
+ context 'when query is review/patch-3' do
+ let(:query) { 'review/patch-3' }
+
+ it 'responds with status code 204' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'when query is partially matched in the middle of environment name' do
+ let(:query) { 'patch' }
+
+ it 'responds with status code 204' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ context 'when query contains a wildcard character' do
+ let(:query) { 'review%' }
+
+ it 'prevents wildcard injection' do
+ get :search, params: environment_params(format: :json, query: query)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+
def environment_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace,
project_id: project,
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 9a3f1f1c5a1..2d554326f05 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -41,6 +41,76 @@ describe Environment do
end
end
+ describe '.for_name_like' do
+ subject { project.environments.for_name_like(query, limit: limit) }
+
+ let!(:environment) { create(:environment, name: 'production', project: project) }
+ let(:query) { 'pro' }
+ let(:limit) { 5 }
+
+ it 'returns a found name' do
+ is_expected.to include(environment)
+ end
+
+ context 'when query is production' do
+ let(:query) { 'production' }
+
+ it 'returns a found name' do
+ is_expected.to include(environment)
+ end
+ end
+
+ context 'when query is productionA' do
+ let(:query) { 'productionA' }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when query is empty' do
+ let(:query) { '' }
+
+ it 'returns a found name' do
+ is_expected.to include(environment)
+ end
+ end
+
+ context 'when query is nil' do
+ let(:query) { }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(NoMethodError)
+ end
+ end
+
+ context 'when query is partially matched in the middle of environment name' do
+ let(:query) { 'duction' }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when query contains a wildcard character' do
+ let(:query) { 'produc%' }
+
+ it 'prevents wildcard injection' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.pluck_names' do
+ subject { described_class.pluck_names }
+
+ let!(:environment) { create(:environment, name: 'production', project: project) }
+
+ it 'plucks names' do
+ is_expected.to eq(%w[production])
+ end
+ end
+
describe '#expire_etag_cache' do
let(:store) { Gitlab::EtagCaching::Store.new }