<!-- <!--
# Read me first! # Read me first!
Create this issue under https://dev.gitlab.org/gitlab/gitlabhq Create this issue under https://gitlab.com/gitlab-org/security
Set the title to: `Description of the original issue` Set the title to: `Description of the original issue`
--> -->
### Prior to starting the security release work ## Prior to starting the security release work
- [ ] Read the [security process for developers] if you are not familiar with it. - [ ] Read the [security process for developers] if you are not familiar with it.
- [ ] Link to the original issue adding it to the [links section](#links) - [ ] Link this issue in the Security Release issue on GitLab.com. You can find this issue in the topic of the `#releases` channel.
- [ ] Run `scripts/security-harness` in the CE, EE, and/or Omnibus to prevent pushing to any remote besides `dev.gitlab.org` - [ ] Add a link to the confidential `gitlab-org/gitlab` issue describing the vulnerability next to **Original issue** in the [links table](#links).
- [ ] Create a new branch prefixing it with `security-` - [ ] Add a link to the confidential `gitlab-org/gitlab` Security release issue next to **Security release issue** in the [links table](#links).
- [ ] Create a MR targeting `dev.gitlab.org` `master` - [ ] Run `scripts/security-harness` in your local repository to prevent accidentally pushing to any remote besides `gitlab.com/gitlab-org/security`.
- [ ] Add a link to this issue in the original security issue on `gitlab.com`.
#### Backports ## Development
- [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches - [ ] Create a new branch prefixing it with `security-`.
- [ ] At this point, it might be easy to squash the commits from the MR into one - [ ] Create a merge request targeting `master` on `gitlab.com/gitlab-org/security` and use the [Security Release merge request template].
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation] - [ ] Follow the same [code review process]: Assign to a reviewer, then to a maintainer.
- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the "Security Release" merge request template.
- Every merge request will have its own set of TODOs, so make sure to
complete those.
- [ ] Make sure all MRs have a link in the [links section](#links)
[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script After your merge request has being approved according to our [approval guidelines], you're ready to prepare the backports
## Backports
#### Documentation and final details - [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches
* At this point, it might be easy to squash the commits from the MR into one
* You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the [Security Release merge request template].
* Every merge request will have its own set of TODOs, so make sure to complete those.
- [ ] Make sure all MRs are linked in the [Links section](#links)
## Documentation and final details
- [ ] Check the topic on #releases to see when the next release is going to happen and add a link to the [links section](#links) - [ ] Ensure the [Links section](#links) is completed.
- [ ] Add links to this issue and your MRs in the description of the security release issue
- [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) - [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details)
- [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) - [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details)
- [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) - [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details)
- [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details) - [ ] Add the nickname of the external user who found the issue (and/or HackerOne profile) to the Thanks row in the [details section](#details)
- [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed. - [ ] Once your `master` MR is merged, comment on the original security issue with a link to that MR indicating the issue is fixed.
### Summary ## Summary
#### Links ### Links
| Description | Link | | Description | Link |
| -------- | -------- | | -------- | -------- |
| Original issue | #TODO | | Original issue | #TODO |
| Security release issue | #TODO | | Security release issue | #TODO |
| `master` MR | !TODO | | `master` MR | !TODO |
| `master` MR (EE) | !TODO |
| `Backport X.Y` MR | !TODO | | `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR | !TODO | | `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR | !TODO | | `Backport X.Y` MR | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
| `Backport X.Y` MR (EE) | !TODO |
#### Details ### Details
| Description | Details | Further details| | Description | Details | Further details|
| -------- | -------- | -------- | | -------- | -------- | -------- |
...@@ -65,6 +64,9 @@ Set the title to: `Description of the original issue` ...@@ -65,6 +64,9 @@ Set the title to: `Description of the original issue`
| Thanks | | | | Thanks | | |
[security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md [security process for developers]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
[RM list]: https://about.gitlab.com/release-managers/ [secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
[security Release merge request template]: https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md
[code review process]: https://docs.gitlab.com/ee/development/code_review.html
[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
/label ~security /label ~security
<!-- <!--
# README first! # README first!
This MR should be created on `dev.gitlab.org`. This MR should be created on `gitlab.com/gitlab-org/security/gitlab`.
See [the general developer security release guidelines](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md). See [the general developer security release guidelines](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md).
This merge request _must not_ close the corresponding security issue _unless_ it
targets master.
When submitting a merge request for CE, a corresponding EE merge request is
always required. This makes it easier to merge security merge requests, as
manually merging CE into EE is no longer required.
--> -->
## Related issues ## Related issues
<!-- Mention the issue(s) this MR is related to --> <!-- Mention the issue(s) this MR is related to -->
## Developer checklist ## Developer checklist
- [ ] Link to the developer security workflow issue on `dev.gitlab.org` - [ ] Link this MR in the `links` section of the related issue on [GitLab Security].
- [ ] MR targets `master`, or `X-Y-stable` for backports - [ ] Merge request targets `master`, or `X-Y-stable` for backports.
- [ ] Milestone is set for the version this MR applies to - [ ] Milestone is set for the version this merge request applies to.
- [ ] Title of this MR is the same as for all backports - [ ] Title of this merge request is the same as for all backports.
- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security` - [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security`
- [ ] Add a link to this MR in the `links` section of related issue - [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] Set up an EE MR (always required for CE merge requests): EE_MR_LINK_HERE - [ ] If this merge request targets `master`, ensure it's approved according to our [Approval Guidelines].
- [ ] Assign to a reviewer (that is not a release manager) - [ ] Merge request _must not_ close the corresponding security issue, _unless_ it targets `master`.
**Note:** Reviewer/maintainer should not be a Release Manager
## Reviewer checklist ## Reviewer checklist
...@@ -33,3 +29,7 @@ manually merging CE into EE is no longer required. ...@@ -33,3 +29,7 @@ manually merging CE into EE is no longer required.
- [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines - [ ] Assigned to `@gitlab-release-tools-bot` with passing CI pipelines
/label ~security /label ~security
[GitLab Security]: https://gitlab.com/gitlab-org/security/gitlab
[approval guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
[Code Review process]: https://docs.gitlab.com/ee/development/code_review.html
...@@ -25,8 +25,8 @@ export const receiveProjectsError = ({ commit }) => { ...@@ -25,8 +25,8 @@ export const receiveProjectsError = ({ commit }) => {
export const fetchProjects = ({ dispatch, state }) => { export const fetchProjects = ({ dispatch, state }) => {
dispatch('requestProjects'); dispatch('requestProjects');
return axios return axios
.post(state.listProjectsEndpoint, { .get(state.listProjectsEndpoint, {
error_tracking_setting: { params: {
api_host: state.apiHost, api_host: state.apiHost,
token: state.token, token: state.token,
}, },
... ...
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
&:focus, &:focus,
&:active { &:active {
background-color: $btn-active-gray; background-color: $btn-active-gray;
box-shadow: $gl-btn-active-background; box-shadow: none;
} }
} }
... ...
......
...@@ -17,6 +17,7 @@ class Projects::BlameController < Projects::ApplicationController ...@@ -17,6 +17,7 @@ class Projects::BlameController < Projects::ApplicationController
end end
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
@blame_groups = Gitlab::Blame.new(@blob, @commit).groups @blame_groups = Gitlab::Blame.new(@blob, @commit).groups
... ...
......
...@@ -205,6 +205,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -205,6 +205,7 @@ class Projects::BlobController < Projects::ApplicationController
def show_html def show_html
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
@last_commit = @repository.last_commit_for_path(@commit.id, @blob.path) @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
... ...
......
...@@ -151,7 +151,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -151,7 +151,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts) @diffs = commit.diffs(opts)
@notes_count = commit.notes.count @notes_count = commit.notes.count
@environment = EnvironmentsFinder.new(@project, current_user, commit: @commit).execute.last @environment = EnvironmentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
... ...
......
...@@ -101,6 +101,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -101,6 +101,7 @@ class Projects::CompareController < Projects::ApplicationController
def define_environment def define_environment
if compare if compare
environment_params = @repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit } environment_params = @repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit }
environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
end end
end end
... ...
......
# frozen_string_literal: true
module Projects
module ErrorTracking
class ProjectsController < Projects::ApplicationController
respond_to :json
before_action :authorize_read_sentry_issue!
def index
service = ::ErrorTracking::ListProjectsService.new(
project,
current_user,
list_projects_params
)
result = service.execute
if result[:status] == :success
render json: { projects: serialize_projects(result[:projects]) }
else
render(
status: result[:http_status] || :bad_request,
json: { message: result[:message] }
)
end
end
private
def list_projects_params
{ api_host: params[:api_host], token: params[:token] }
end
def serialize_projects(projects)
::ErrorTracking::ProjectSerializer
.new(project: project, user: current_user)
.represent(projects)
end
end
end
end
...@@ -33,14 +33,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController ...@@ -33,14 +33,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
end end
end end
def list_projects
respond_to do |format|
format.json do
render_project_list_json
end
end
end
private private
def render_index_json def render_index_json
...@@ -84,28 +76,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController ...@@ -84,28 +76,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
} }
end end
def render_project_list_json
service = ErrorTracking::ListProjectsService.new(
project,
current_user,
list_projects_params
)
result = service.execute
if result[:status] == :success
render json: {
projects: serialize_projects(result[:projects])
}
else
return render(
status: result[:http_status] || :bad_request,
json: {
message: result[:message]
}
)
end
end
def handle_errors(result) def handle_errors(result)
unless result[:status] == :success unless result[:status] == :success
render json: { message: result[:message] }, render json: { message: result[:message] },
...@@ -117,10 +87,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController ...@@ -117,10 +87,6 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
params.permit(:search_term, :sort, :cursor) params.permit(:search_term, :sort, :cursor)
end end
def list_projects_params
params.require(:error_tracking_setting).permit([:api_host, :token])
end
def issue_details_params def issue_details_params
params.permit(:issue_id) params.permit(:issue_id)
end end
...@@ -150,10 +116,4 @@ class Projects::ErrorTrackingController < Projects::ApplicationController ...@@ -150,10 +116,4 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
.new(project: project, user: current_user) .new(project: project, user: current_user)
.represent(event) .represent(event)
end end
def serialize_projects(projects)
ErrorTracking::ProjectSerializer
.new(project: project, user: current_user)
.represent(projects)
end
end end
...@@ -52,7 +52,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -52,7 +52,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@diff_notes_disabled = true @diff_notes_disabled = true
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user, latest: true).last
render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) } render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) }
end end
... ...
......
...@@ -51,7 +51,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -51,7 +51,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
# Deprecated: https://gitlab.com/gitlab-org/gitlab/issues/37735 # Deprecated: https://gitlab.com/gitlab-org/gitlab/issues/37735
def render_diffs def render_diffs
diffs = @compare.diffs(diff_options) diffs = @compare.diffs(diff_options)
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user, latest: true).last
diffs.unfold_diff_files(note_positions.unfoldable) diffs.unfold_diff_files(note_positions.unfoldable)
diffs.write_cache diffs.write_cache
... ...
......
...@@ -25,25 +25,13 @@ class EnvironmentsFinder ...@@ -25,25 +25,13 @@ class EnvironmentsFinder
.select(:environment_id) .select(:environment_id)
environments = project.environments.available environments = project.environments.available
.where(id: environment_ids).order_by_last_deployed_at.to_a .where(id: environment_ids)
environments.select! do |environment| if params[:find_latest]
Ability.allowed?(current_user, :read_environment, environment) find_one(environments.order_by_last_deployed_at_desc)
end else
find_all(environments.order_by_last_deployed_at.to_a)
if ref && commit
environments.select! do |environment|
environment.includes_commit?(commit)
end
end
if ref && params[:recently_updated]
environments.select! do |environment|
environment.recently_updated_on_branch?(ref)
end
end end
environments
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -62,6 +50,24 @@ class EnvironmentsFinder ...@@ -62,6 +50,24 @@ class EnvironmentsFinder
private private
def find_one(environments)
[environments.find { |environment| valid_environment?(environment) }].compact
end
def find_all(environments)
environments.select { |environment| valid_environment?(environment) }
end
def valid_environment?(environment)
# Go in order of cost: SQL calls are cheaper than Gitaly calls
return false unless Ability.allowed?(current_user, :read_environment, environment)
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
return false if ref && commit && !environment.includes_commit?(commit)
true
end
def ref def ref
params[:ref].try(:to_s) params[:ref].try(:to_s)
end end
... ...
......
...@@ -6,7 +6,7 @@ class EventsFinder ...@@ -6,7 +6,7 @@ class EventsFinder
MAX_PER_PAGE = 100 MAX_PER_PAGE = 100
attr_reader :source, :params, :current_user attr_reader :source, :params, :current_user, :scope
requires_cross_project_access unless: -> { source.is_a?(Project) }, model: Event requires_cross_project_access unless: -> { source.is_a?(Project) }, model: Event
...@@ -15,6 +15,7 @@ class EventsFinder ...@@ -15,6 +15,7 @@ class EventsFinder
# Arguments: # Arguments:
# source - which user or project to looks for events on # source - which user or project to looks for events on
# current_user - only return events for projects visible to this user # current_user - only return events for projects visible to this user
# scope - return all events across a user's projects
# params: # params:
# action: string # action: string
# target_type: string # target_type: string
...@@ -27,11 +28,12 @@ class EventsFinder ...@@ -27,11 +28,12 @@ class EventsFinder
def initialize(params = {}) def initialize(params = {})
@source = params.delete(:source) @source = params.delete(:source)
@current_user = params.delete(:current_user) @current_user = params.delete(:current_user)
@scope = params.delete(:scope)
@params = params @params = params
end end
def execute def execute
events = source.events events = get_events
events = by_current_user_access(events) events = by_current_user_access(events)
events = by_action(events) events = by_action(events)
...@@ -47,6 +49,12 @@ class EventsFinder ...@@ -47,6 +49,12 @@ class EventsFinder
private private
def get_events
return EventCollection.new(current_user.authorized_projects).all_project_events if scope == 'all'
source.events
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_current_user_access(events) def by_current_user_access(events)
events.merge(Project.public_or_visible_to_user(current_user)) events.merge(Project.public_or_visible_to_user(current_user))
... ...
......
...@@ -58,6 +58,10 @@ module DashboardHelper ...@@ -58,6 +58,10 @@ module DashboardHelper
links += [:activity, :milestones] links += [:activity, :milestones]
end end
if can?(current_user, :read_instance_statistics)
links << :analytics
end
links links
end end
end end
... ...
......
...@@ -48,13 +48,14 @@ class Environment < ApplicationRecord ...@@ -48,13 +48,14 @@ class Environment < ApplicationRecord
scope :available, -> { with_state(:available) } scope :available, -> { with_state(:available) }
scope :stopped, -> { with_state(:stopped) } scope :stopped, -> { with_state(:stopped) }
scope :order_by_last_deployed_at, -> do scope :order_by_last_deployed_at, -> do
max_deployment_id_sql =
Deployment.select(Deployment.arel_table[:id].maximum)
.where(Deployment.arel_table[:environment_id].eq(arel_table[:id]))
.to_sql
order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC')) order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC'))
end end
scope :order_by_last_deployed_at_desc, -> do
order(Gitlab::Database.nulls_last_order("(#{max_deployment_id_sql})", 'DESC'))
end
scope :in_review_folder, -> { where(environment_type: "review") } scope :in_review_folder, -> { where(environment_type: "review") }
scope :for_name, -> (name) { where(name: name) } scope :for_name, -> (name) { where(name: name) }
scope :preload_cluster, -> { preload(last_deployment: :cluster) } scope :preload_cluster, -> { preload(last_deployment: :cluster) }
...@@ -90,6 +91,12 @@ class Environment < ApplicationRecord ...@@ -90,6 +91,12 @@ class Environment < ApplicationRecord
end end
end end
def self.max_deployment_id_sql
Deployment.select(Deployment.arel_table[:id].maximum)
.where(Deployment.arel_table[:environment_id].eq(arel_table[:id]))
.to_sql
end
def self.pluck_names def self.pluck_names
pluck(:name) pluck(:name)
end end
... ...
......
...@@ -30,17 +30,24 @@ class EventCollection ...@@ -30,17 +30,24 @@ class EventCollection
relation = if groups relation = if groups
project_and_group_events project_and_group_events
else else
relation_with_join_lateral('project_id', projects) project_events
end end
relation = paginate_events(relation) relation = paginate_events(relation)
relation.with_associations.to_a relation.with_associations.to_a
end end
def all_project_events
Event.from_union([project_events]).recent
end
private private
def project_events
relation_with_join_lateral('project_id', projects)
end
def project_and_group_events def project_and_group_events
project_events = relation_with_join_lateral('project_id', projects)
group_events = relation_with_join_lateral('group_id', groups) group_events = relation_with_join_lateral('group_id', groups)
Event.from_union([project_events, group_events]).recent Event.from_union([project_events, group_events]).recent
... ...
......
...@@ -1122,22 +1122,18 @@ class MergeRequest < ApplicationRecord ...@@ -1122,22 +1122,18 @@ class MergeRequest < ApplicationRecord
actual_head_pipeline.success? actual_head_pipeline.success?
end end
def environments_for(current_user) def environments_for(current_user, latest: false)
return [] unless diff_head_commit return [] unless diff_head_commit
@environments ||= Hash.new do |h, current_user| envs = EnvironmentsFinder.new(target_project, current_user,
envs = EnvironmentsFinder.new(target_project, current_user, ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute
ref: target_branch, commit: diff_head_commit, with_tags: true).execute
if source_project if source_project
envs.concat EnvironmentsFinder.new(source_project, current_user, envs.concat EnvironmentsFinder.new(source_project, current_user,
ref: source_branch, commit: diff_head_commit).execute ref: source_branch, commit: diff_head_commit, find_latest: latest).execute
end
h[current_user] = envs.uniq
end end
@environments[current_user] envs.uniq
end end
## ##
... ...
......
...@@ -30,7 +30,7 @@ module Projects ...@@ -30,7 +30,7 @@ module Projects
settings = params[:error_tracking_setting_attributes] settings = params[:error_tracking_setting_attributes]
return {} if settings.blank? return {} if settings.blank?
api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from( api_url = ::ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
api_host: settings[:api_host], api_host: settings[:api_host],
project_slug: settings.dig(:project, :slug), project_slug: settings.dig(:project, :slug),
organization_slug: settings.dig(:project, :organization_slug) organization_slug: settings.dig(:project, :organization_slug)
... ...
......
- page_title _('Instance Statistics') - page_title _('Analytics')
- header_title _('Instance Statistics'), instance_statistics_root_path - header_title _('Analytics'), instance_statistics_root_path
- nav 'instance_statistics' - nav 'instance_statistics'
- @left_sidebar = true - @left_sidebar = true
... ...
......