Commit db054bc1 authored by syasonik's avatar syasonik

Support flat response for envs index route

To support environment folders in the UI on the Environments List
page, the environments index route previously returned one environment
per folder, excluding those other than the latest deploy. However, the
environtments dropdown on the metrics dashboard requires that any
environment be selectable.

To accommodate both use cases, support an optional 'nested' parameter
in the index route to return either a flat, complete response or a
nested response based on the use case in question. The new default
response structure is the flat response.
parent 3fd9e48a
...@@ -143,7 +143,7 @@ export default { ...@@ -143,7 +143,7 @@ export default {
*/ */
created() { created() {
this.service = new EnvironmentsService(this.endpoint); this.service = new EnvironmentsService(this.endpoint);
this.requestData = { page: this.page, scope: this.scope }; this.requestData = { page: this.page, scope: this.scope, nested: true };
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
......
...@@ -7,8 +7,8 @@ export default class EnvironmentsService { ...@@ -7,8 +7,8 @@ export default class EnvironmentsService {
} }
fetchEnvironments(options = {}) { fetchEnvironments(options = {}) {
const { scope, page } = options; const { scope, page, nested } = options;
return axios.get(this.environmentsEndpoint, { params: { scope, page } }); return axios.get(this.environmentsEndpoint, { params: { scope, page, nested } });
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
......
...@@ -20,7 +20,8 @@ export default class EnvironmentsStore { ...@@ -20,7 +20,8 @@ export default class EnvironmentsStore {
* *
* Stores the received environments. * Stores the received environments.
* *
* In the main environments endpoint, each environment has the following schema * In the main environments endpoint (with { nested: true } in params), each folder
* has the following schema:
* { name: String, size: Number, latest: Object } * { name: String, size: Number, latest: Object }
* In the endpoint to retrieve environments from each folder, the environment does * In the endpoint to retrieve environments from each folder, the environment does
* not have the `latest` key and the data is all in the root level. * not have the `latest` key and the data is all in the root level.
......
...@@ -196,13 +196,13 @@ export default { ...@@ -196,13 +196,13 @@ export default {
class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up" class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"
> >
<ul> <ul>
<li v-for="environment in store.environmentsData" :key="environment.latest.id"> <li v-for="environment in store.environmentsData" :key="environment.id">
<a <a
:href="environment.latest.metrics_path" :href="environment.metrics_path"
:class="{ 'is-active': environment.latest.name == currentEnvironmentName }" :class="{ 'is-active': environment.name == currentEnvironmentName }"
class="dropdown-item" class="dropdown-item"
> >
{{ environment.latest.name }} {{ environment.name }}
</a> </a>
</li> </li>
</ul> </ul>
......
...@@ -66,9 +66,7 @@ export default class MonitoringStore { ...@@ -66,9 +66,7 @@ export default class MonitoringStore {
} }
storeEnvironmentsData(environmentsData = []) { storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData.filter( this.environmentsData = environmentsData.filter(environment => !!environment.last_deployment);
environment => !!environment.latest.last_deployment,
);
} }
getMetricsCount() { getMetricsCount() {
......
...@@ -15,6 +15,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -15,6 +15,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:area_chart, project) push_frontend_feature_flag(:area_chart, project)
end end
# Returns all environments or all folders based on the :nested param
def index def index
@environments = project.environments @environments = project.environments
.with_state(params[:scope] || :available) .with_state(params[:scope] || :available)
...@@ -25,11 +26,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -25,11 +26,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
Gitlab::PollingInterval.set_header(response, interval: 3_000) Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: { render json: {
environments: EnvironmentSerializer environments: serialize_environments(request, response, params[:nested]),
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
.within_folders
.represent(@environments),
available_count: project.environments.available.count, available_count: project.environments.available.count,
stopped_count: project.environments.stopped.count stopped_count: project.environments.stopped.count
} }
...@@ -37,6 +34,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -37,6 +34,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end end
end end
# Returns all environments for a given folder
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def folder def folder
folder_environments = project.environments.where(environment_type: params[:id]) folder_environments = project.environments.where(environment_type: params[:id])
...@@ -48,10 +46,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -48,10 +46,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
format.html format.html
format.json do format.json do
render json: { render json: {
environments: EnvironmentSerializer environments: serialize_environments(request, response),
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
.represent(@environments),
available_count: folder_environments.available.count, available_count: folder_environments.available.count,
stopped_count: folder_environments.stopped.count stopped_count: folder_environments.stopped.count
} }
...@@ -186,6 +181,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -186,6 +181,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment ||= project.environments.find(params[:id]) @environment ||= project.environments.find(params[:id])
end end
def serialize_environments(request, response, nested = false)
serializer = EnvironmentSerializer
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
serializer = serializer.within_folders if nested
serializer.represent(@environments)
end
def authorize_stop_environment! def authorize_stop_environment!
access_denied! unless can?(current_user, :stop_environment, environment) access_denied! unless can?(current_user, :stop_environment, environment)
end end
......
---
title: Update metrics environment dropdown to show complete option set
merge_request: 24441
author:
type: fixed
...@@ -47,9 +47,43 @@ describe Projects::EnvironmentsController do ...@@ -47,9 +47,43 @@ describe Projects::EnvironmentsController do
let(:environments) { json_response['environments'] } let(:environments) { json_response['environments'] }
context 'with default parameters' do
before do
get :index, params: environment_params(format: :json)
end
it 'responds with a flat payload describing available environments' do
expect(environments.count).to eq 3
expect(environments.first['name']).to eq 'production'
expect(environments.second['name']).to eq 'staging/review-1'
expect(environments.third['name']).to eq 'staging/review-2'
expect(json_response['available_count']).to eq 3
expect(json_response['stopped_count']).to eq 1
end
it 'sets the polling interval header' do
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Poll-Interval']).to eq("3000")
end
end
context 'when a folder-based nested structure is requested' do
before do
get :index, params: environment_params(format: :json, nested: true)
end
it 'responds with a payload containing the latest environment for each folder' do
expect(environments.count).to eq 2
expect(environments.first['name']).to eq 'production'
expect(environments.second['name']).to eq 'staging'
expect(environments.second['size']).to eq 2
expect(environments.second['latest']['name']).to eq 'staging/review-2'
end
end
context 'when requesting available environments scope' do context 'when requesting available environments scope' do
before do before do
get :index, params: environment_params(format: :json, scope: :available) get :index, params: environment_params(format: :json, nested: true, scope: :available)
end end
it 'responds with a payload describing available environments' do it 'responds with a payload describing available environments' do
...@@ -64,16 +98,11 @@ describe Projects::EnvironmentsController do ...@@ -64,16 +98,11 @@ describe Projects::EnvironmentsController do
expect(json_response['available_count']).to eq 3 expect(json_response['available_count']).to eq 3
expect(json_response['stopped_count']).to eq 1 expect(json_response['stopped_count']).to eq 1
end end
it 'sets the polling interval header' do
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['Poll-Interval']).to eq("3000")
end
end end
context 'when requesting stopped environments scope' do context 'when requesting stopped environments scope' do
before do before do
get :index, params: environment_params(format: :json, scope: :stopped) get :index, params: environment_params(format: :json, nested: true, scope: :stopped)
end end
it 'responds with a payload describing stopped environments' do it 'responds with a payload describing stopped environments' do
......
...@@ -6597,58 +6597,46 @@ export function convertDatesMultipleSeries(multipleSeries) { ...@@ -6597,58 +6597,46 @@ export function convertDatesMultipleSeries(multipleSeries) {
export const environmentData = [ export const environmentData = [
{ {
id: 34,
name: 'production', name: 'production',
size: 1, state: 'available',
latest: { external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
id: 34, environment_type: null,
name: 'production', stop_action: false,
state: 'available', metrics_path: '/root/hello-prometheus/environments/34/metrics',
external_url: 'http://root-autodevops-deploy.my-fake-domain.com', environment_path: '/root/hello-prometheus/environments/34',
environment_type: null, stop_path: '/root/hello-prometheus/environments/34/stop',
stop_action: false, terminal_path: '/root/hello-prometheus/environments/34/terminal',
metrics_path: '/root/hello-prometheus/environments/34/metrics', folder_path: '/root/hello-prometheus/environments/folders/production',
environment_path: '/root/hello-prometheus/environments/34', created_at: '2018-06-29T16:53:38.301Z',
stop_path: '/root/hello-prometheus/environments/34/stop', updated_at: '2018-06-29T16:57:09.825Z',
terminal_path: '/root/hello-prometheus/environments/34/terminal', last_deployment: {
folder_path: '/root/hello-prometheus/environments/folders/production', id: 127,
created_at: '2018-06-29T16:53:38.301Z',
updated_at: '2018-06-29T16:57:09.825Z',
last_deployment: {
id: 127,
},
}, },
}, },
{ {
name: 'review', id: 35,
size: 1, name: 'review/noop-branch',
latest: { state: 'available',
id: 35, external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
name: 'review/noop-branch', environment_type: 'review',
state: 'available', stop_action: true,
external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com', metrics_path: '/root/hello-prometheus/environments/35/metrics',
environment_type: 'review', environment_path: '/root/hello-prometheus/environments/35',
stop_action: true, stop_path: '/root/hello-prometheus/environments/35/stop',
metrics_path: '/root/hello-prometheus/environments/35/metrics', terminal_path: '/root/hello-prometheus/environments/35/terminal',
environment_path: '/root/hello-prometheus/environments/35', folder_path: '/root/hello-prometheus/environments/folders/review',
stop_path: '/root/hello-prometheus/environments/35/stop', created_at: '2018-07-03T18:39:41.702Z',
terminal_path: '/root/hello-prometheus/environments/35/terminal', updated_at: '2018-07-03T18:44:54.010Z',
folder_path: '/root/hello-prometheus/environments/folders/review', last_deployment: {
created_at: '2018-07-03T18:39:41.702Z', id: 128,
updated_at: '2018-07-03T18:44:54.010Z',
last_deployment: {
id: 128,
},
}, },
}, },
{ {
name: 'no-deployment', id: 36,
size: 1, name: 'no-deployment/noop-branch',
latest: { state: 'available',
id: 36, created_at: '2018-07-04T18:39:41.702Z',
name: 'no-deployment/noop-branch', updated_at: '2018-07-04T18:44:54.010Z',
state: 'available',
created_at: '2018-07-04T18:39:41.702Z',
updated_at: '2018-07-04T18:44:54.010Z',
},
}, },
]; ];
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