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.
This commit is contained in:
syasonik 2019-01-16 14:05:19 -06:00
parent 3fd9e48a7e
commit db054bc166
9 changed files with 97 additions and 73 deletions

View file

@ -143,7 +143,7 @@ export default {
*/
created() {
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({
resource: this.service,

View file

@ -7,8 +7,8 @@ export default class EnvironmentsService {
}
fetchEnvironments(options = {}) {
const { scope, page } = options;
return axios.get(this.environmentsEndpoint, { params: { scope, page } });
const { scope, page, nested } = options;
return axios.get(this.environmentsEndpoint, { params: { scope, page, nested } });
}
// eslint-disable-next-line class-methods-use-this

View file

@ -20,7 +20,8 @@ export default class EnvironmentsStore {
*
* 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 }
* 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.

View file

@ -196,13 +196,13 @@ export default {
class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"
>
<ul>
<li v-for="environment in store.environmentsData" :key="environment.latest.id">
<li v-for="environment in store.environmentsData" :key="environment.id">
<a
:href="environment.latest.metrics_path"
:class="{ 'is-active': environment.latest.name == currentEnvironmentName }"
:href="environment.metrics_path"
:class="{ 'is-active': environment.name == currentEnvironmentName }"
class="dropdown-item"
>
{{ environment.latest.name }}
{{ environment.name }}
</a>
</li>
</ul>

View file

@ -66,9 +66,7 @@ export default class MonitoringStore {
}
storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData.filter(
environment => !!environment.latest.last_deployment,
);
this.environmentsData = environmentsData.filter(environment => !!environment.last_deployment);
}
getMetricsCount() {

View file

@ -15,6 +15,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:area_chart, project)
end
# Returns all environments or all folders based on the :nested param
def index
@environments = project.environments
.with_state(params[:scope] || :available)
@ -25,11 +26,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: {
environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
.within_folders
.represent(@environments),
environments: serialize_environments(request, response, params[:nested]),
available_count: project.environments.available.count,
stopped_count: project.environments.stopped.count
}
@ -37,6 +34,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
# Returns all environments for a given folder
# rubocop: disable CodeReuse/ActiveRecord
def folder
folder_environments = project.environments.where(environment_type: params[:id])
@ -48,10 +46,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
format.html
format.json do
render json: {
environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user)
.with_pagination(request, response)
.represent(@environments),
environments: serialize_environments(request, response),
available_count: folder_environments.available.count,
stopped_count: folder_environments.stopped.count
}
@ -186,6 +181,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment ||= project.environments.find(params[:id])
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!
access_denied! unless can?(current_user, :stop_environment, environment)
end

View file

@ -0,0 +1,5 @@
---
title: Update metrics environment dropdown to show complete option set
merge_request: 24441
author:
type: fixed

View file

@ -47,9 +47,43 @@ describe Projects::EnvironmentsController do
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
before do
get :index, params: environment_params(format: :json, scope: :available)
get :index, params: environment_params(format: :json, nested: true, scope: :available)
end
it 'responds with a payload describing available environments' do
@ -64,16 +98,11 @@ describe Projects::EnvironmentsController do
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 requesting stopped environments scope' do
before do
get :index, params: environment_params(format: :json, scope: :stopped)
get :index, params: environment_params(format: :json, nested: true, scope: :stopped)
end
it 'responds with a payload describing stopped environments' do

View file

@ -6597,58 +6597,46 @@ export function convertDatesMultipleSeries(multipleSeries) {
export const environmentData = [
{
id: 34,
name: 'production',
size: 1,
latest: {
id: 34,
name: 'production',
state: 'available',
external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
environment_type: null,
stop_action: false,
metrics_path: '/root/hello-prometheus/environments/34/metrics',
environment_path: '/root/hello-prometheus/environments/34',
stop_path: '/root/hello-prometheus/environments/34/stop',
terminal_path: '/root/hello-prometheus/environments/34/terminal',
folder_path: '/root/hello-prometheus/environments/folders/production',
created_at: '2018-06-29T16:53:38.301Z',
updated_at: '2018-06-29T16:57:09.825Z',
last_deployment: {
id: 127,
},
state: 'available',
external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
environment_type: null,
stop_action: false,
metrics_path: '/root/hello-prometheus/environments/34/metrics',
environment_path: '/root/hello-prometheus/environments/34',
stop_path: '/root/hello-prometheus/environments/34/stop',
terminal_path: '/root/hello-prometheus/environments/34/terminal',
folder_path: '/root/hello-prometheus/environments/folders/production',
created_at: '2018-06-29T16:53:38.301Z',
updated_at: '2018-06-29T16:57:09.825Z',
last_deployment: {
id: 127,
},
},
{
name: 'review',
size: 1,
latest: {
id: 35,
name: 'review/noop-branch',
state: 'available',
external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
environment_type: 'review',
stop_action: true,
metrics_path: '/root/hello-prometheus/environments/35/metrics',
environment_path: '/root/hello-prometheus/environments/35',
stop_path: '/root/hello-prometheus/environments/35/stop',
terminal_path: '/root/hello-prometheus/environments/35/terminal',
folder_path: '/root/hello-prometheus/environments/folders/review',
created_at: '2018-07-03T18:39:41.702Z',
updated_at: '2018-07-03T18:44:54.010Z',
last_deployment: {
id: 128,
},
id: 35,
name: 'review/noop-branch',
state: 'available',
external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
environment_type: 'review',
stop_action: true,
metrics_path: '/root/hello-prometheus/environments/35/metrics',
environment_path: '/root/hello-prometheus/environments/35',
stop_path: '/root/hello-prometheus/environments/35/stop',
terminal_path: '/root/hello-prometheus/environments/35/terminal',
folder_path: '/root/hello-prometheus/environments/folders/review',
created_at: '2018-07-03T18:39:41.702Z',
updated_at: '2018-07-03T18:44:54.010Z',
last_deployment: {
id: 128,
},
},
{
name: 'no-deployment',
size: 1,
latest: {
id: 36,
name: 'no-deployment/noop-branch',
state: 'available',
created_at: '2018-07-04T18:39:41.702Z',
updated_at: '2018-07-04T18:44:54.010Z',
},
id: 36,
name: 'no-deployment/noop-branch',
state: 'available',
created_at: '2018-07-04T18:39:41.702Z',
updated_at: '2018-07-04T18:44:54.010Z',
},
];