Commit 7b23102e authored by charlie ablett's avatar charlie ablett

Merge branch 'jivanvl-backend-metrics-dashboard-template' into 'master'

Add metrics dashboard template finder

See merge request gitlab-org/gitlab!37523
parents 3420f033 34d02f09
......@@ -6,7 +6,8 @@ class TemplateFinder
VENDORED_TEMPLATES = HashWithIndifferentAccess.new(
dockerfiles: ::Gitlab::Template::DockerfileTemplate,
gitignores: ::Gitlab::Template::GitignoreTemplate,
gitlab_ci_ymls: ::Gitlab::Template::GitlabCiYmlTemplate
gitlab_ci_ymls: ::Gitlab::Template::GitlabCiYmlTemplate,
metrics_dashboard_ymls: ::Gitlab::Template::MetricsDashboardTemplate
).freeze
class << self
......
......@@ -200,6 +200,10 @@ module BlobHelper
@gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute)
end
def metrics_dashboard_ymls(project)
@metrics_dashboard_ymls ||= template_dropdown_names(TemplateFinder.build(:metrics_dashboard_ymls, project).execute)
end
def dockerfile_names(project)
@dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute)
end
......
---
title: Add custom metrics dashboard templates supports
merge_request: 37523
author:
type: added
---
stage: Monitor
group: APM
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Developing templates for custom dashboards **(CORE)**
GitLab provides a template to make it easier for you to create templates for
[custom dashboards](index.md). Templates provide helpful guidance and
commented-out examples you can use.
## Apply a dashboard template
Navigate to the browser-based editor of your choice:
- In the **Repository view**:
1. Navigate to **{doc-text}** **Repository > Files**.
1. Click **{plus}** **Add to tree** and select **New file**,
then click **Select a template type** to see a list of available templates:
![Metrics dashboard template selection](img/metrics_dashboard_template_selection_v13_3.png)
- In the **[Web IDE](../../../user/project/web_ide/index.md)**:
1. Click **Web IDE** when viewing your repository.
1. Click **{doc-new}** **New file**, then click **Choose a template** to see a list of available templates:
![Metrics dashboard template selection WebIDE](img/metrics_dashboard_template_selection_web_ide_v13_3.png)
## Custom dashboard templates **(PREMIUM ONLY)**
To enable and use a custom dashboard templates on your GitLab instance, read the
[guide for creating custom templates](../../../user/admin_area/settings/instance_template_repository.md).
......@@ -39,6 +39,7 @@ are supported:
| `.gitignore` | `gitignore` | `.gitignore` |
| `.gitlab-ci.yml` | `gitlab-ci` | `.yml` |
| `LICENSE` | `LICENSE` | `.txt` |
| `metrics-dashboard.yml` | `metrics-dashboards` | `.yml` |
Each template must go in its respective subdirectory, have the correct
extension and not be empty. So, the hierarchy should look like this:
......@@ -57,6 +58,9 @@ extension and not be empty. So, the hierarchy should look like this:
|-- LICENSE
|-- custom_license.txt
|-- another_license.txt
|-- metrics-dashboards
|-- custom_metrics-dashboard.yml
|-- another_metrics-dashboard.yml
```
Once this is established, the list of custom templates will be included when
......
......@@ -7,7 +7,8 @@ module EE
CUSTOM_TEMPLATES = HashWithIndifferentAccess.new(
dockerfiles: ::Gitlab::Template::CustomDockerfileTemplate,
gitignores: ::Gitlab::Template::CustomGitignoreTemplate,
gitlab_ci_ymls: ::Gitlab::Template::CustomGitlabCiYmlTemplate
gitlab_ci_ymls: ::Gitlab::Template::CustomGitlabCiYmlTemplate,
metrics_dashboard_ymls: ::Gitlab::Template::CustomMetricsDashboardYmlTemplate
).freeze
attr_reader :custom_templates
......
# frozen_string_literal: true
module Gitlab
module Template
class CustomMetricsDashboardYmlTemplate < CustomTemplate
class << self
def extension
'.yml'
end
def base_dir
'metrics-dashboards/'
end
end
end
end
end
......@@ -20,6 +20,10 @@ RSpec.describe "Custom file template classes" do
'LICENSE/bar.txt' => 'CustomLicenseTemplate Bar',
'LICENSE/bad.xyz' => 'CustomLicenseTemplate Bad',
'metrics-dashboards/foo.yml' => 'CustomMetricsDashboardYmlTemplate Foo',
'metrics-dashboards/bar.yml' => 'CustomMetricsDashboardYmlTemplate Bar',
'metrics-dashboards/bad.xyz' => 'CustomMetricsDashboardYmlTemplate Bad',
'Dockerfile/category/baz.txt' => 'CustomDockerfileTemplate category baz',
'gitignore/category/baz.txt' => 'CustomGitignoreTemplate category baz',
'gitlab-ci/category/baz.yml' => 'CustomGitlabCiYmlTemplate category baz',
......@@ -32,7 +36,8 @@ RSpec.describe "Custom file template classes" do
::Gitlab::Template::CustomDockerfileTemplate,
::Gitlab::Template::CustomGitignoreTemplate,
::Gitlab::Template::CustomGitlabCiYmlTemplate,
::Gitlab::Template::CustomLicenseTemplate
::Gitlab::Template::CustomLicenseTemplate,
::Gitlab::Template::CustomMetricsDashboardYmlTemplate
].each do |template_class|
describe template_class do
let(:name) { template_class.name.demodulize }
......
......@@ -4,7 +4,7 @@ module API
class ProjectTemplates < Grape::API::Instance
include PaginationParams
TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses].freeze
TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses metrics_dashboard_ymls].freeze
# The regex is needed to ensure a period (e.g. agpl-3.0)
# isn't confused with a format type. We also need to allow encoded
# values (e.g. C%2B%2B for C++), so allow % and + as well.
......@@ -14,7 +14,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses) of the template'
requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses|metrics_dashboard_ymls) of the template'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of templates available to this project' do
......
# Only one dashboard should be defined per file
# More info: https://docs.gitlab.com/ee/operations/metrics/dashboards/yaml.html
dashboard: 'Single Stat'
# This is where all of the variables that can be manipulated via the UI
# are initialized
# Check out: https://docs.gitlab.com/ee/operations/metrics/dashboards/templating_variables.html#templating-variables-for-metrics-dashboards-core
templating:
variables:
job: 'prometheus'
# For more information about the required properties of panel_groups
# please visit: https://docs.gitlab.com/ee/operations/metrics/dashboards/yaml.html#panel-group-panel_groups-properties
panel_groups:
- group: 'Memory'
panels:
- title: Prometheus
type: single-stat
metrics:
# Queries that make use of variables need to have double curly brackets {}
# set to the variables, per the example below
- query: 'max(go_memstats_alloc_bytes{job="{{job}}"}) / 1024 /1024'
unit: '%'
label: "Max"
# Development guide for Metrics Dashboard templates
Please follow [the development guideline](../../../../doc/development/operations/metrics/templates.md)
# frozen_string_literal: true
module Gitlab
module Template
class MetricsDashboardTemplate < BaseTemplate
def content
explanation = "# This file is a template, and might need editing before it works on your project."
[explanation, super].join("\n")
end
class << self
def extension
'.metrics-dashboard.yml'
end
def categories
{
"General" => ''
}
end
def base_dir
Rails.root.join('lib/gitlab/metrics/templates')
end
def finder(project = nil)
Gitlab::Template::Finders::GlobalTemplateFinder.new(self.base_dir, self.extension, self.categories)
end
end
end
end
end
......@@ -13,6 +13,7 @@ RSpec.describe TemplateFinder do
:gitignores | described_class
:gitlab_ci_ymls | described_class
:licenses | ::LicenseTemplateFinder
:metrics_dashboard_ymls | described_class
end
with_them do
......@@ -28,6 +29,7 @@ RSpec.describe TemplateFinder do
:dockerfiles | 'Binary'
:gitignores | 'Actionscript'
:gitlab_ci_ymls | 'Android'
:metrics_dashboard_ymls | 'Default'
end
with_them do
......
......@@ -6,10 +6,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
subject { described_class }
describe '.all' do
it 'strips the gitlab-ci suffix' do
expect(subject.all.first.name).not_to end_with('.gitlab-ci.yml')
end
it 'combines the globals and rest' do
all = subject.all.map(&:name)
......@@ -17,34 +13,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
expect(all).to include('Docker')
expect(all).to include('Ruby')
end
it 'ensure that the template name is used exactly once' do
all = subject.all.group_by(&:name)
duplicates = all.select { |_, templates| templates.length > 1 }
expect(duplicates).to be_empty
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
expect(subject.find('mepmep-yadida')).to be nil
end
it 'returns the GitlabCiYml object of a valid file' do
ruby = subject.find('Ruby')
expect(ruby).to be_a described_class
expect(ruby.name).to eq('Ruby')
end
end
describe '.by_category' do
it 'returns sorted results' do
result = described_class.by_category('General')
expect(result).to eq(result.sort)
end
end
describe '#content' do
......@@ -56,13 +24,5 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
end
end
describe '#<=>' do
it 'sorts lexicographically' do
one = described_class.new('a.gitlab-ci.yml')
other = described_class.new('z.gitlab-ci.yml')
expect(one.<=>(other)).to be(-1)
expect([other, one].sort).to eq([one, other])
end
end
it_behaves_like 'file template shared examples', 'Ruby', '.gitlab-ci.yml'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Template::MetricsDashboardTemplate do
subject { described_class }
describe '.all' do
it 'combines the globals and rest' do
all = subject.all.map(&:name)
expect(all).to include('Default')
end
end
describe '#content' do
it 'loads the full file' do
example_dashboard = subject.new(Rails.root.join('lib/gitlab/metrics/templates/Default.metrics-dashboard.yml'))
expect(example_dashboard.name).to eq 'Default'
expect(example_dashboard.content).to start_with('#')
end
end
it_behaves_like 'file template shared examples', 'Default', '.metrics-dashboard.yml'
end
......@@ -62,6 +62,15 @@ RSpec.describe API::ProjectTemplates do
expect(json_response).to satisfy_one { |template| template['key'] == 'mit' }
end
it 'returns metrics_dashboard_ymls' do
get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls")
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('public_api/v4/template_list')
expect(json_response).to satisfy_one { |template| template['key'] == 'Default' }
end
it 'returns 400 for an unknown template type' do
get api("/projects/#{public_project.id}/templates/unknown")
......@@ -136,6 +145,14 @@ RSpec.describe API::ProjectTemplates do
expect(json_response['name']).to eq('Android')
end
it 'returns a specific metrics_dashboard_yml' do
get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls/Default")
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/template')
expect(json_response['name']).to eq('Default')
end
it 'returns a specific license' do
get api("/projects/#{public_project.id}/templates/licenses/mit")
......@@ -166,6 +183,10 @@ RSpec.describe API::ProjectTemplates do
subject { get api("/projects/#{url_encoded_path}/templates/gitlab_ci_ymls/Android") }
end
it_behaves_like 'accepts project paths with dots' do
subject { get api("/projects/#{url_encoded_path}/templates/metrics_dashboard_ymls/Default") }
end
shared_examples 'path traversal attempt' do |template_type|
it 'rejects invalid filenames' do
get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea")
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples 'file template shared examples' do |filename, file_extension|
describe '.all' do
it "strips the #{file_extension} suffix" do
expect(subject.all.first.name).not_to end_with(file_extension)
end
it 'ensures that the template name is used exactly once' do
all = subject.all.group_by(&:name)
duplicates = all.select { |_, templates| templates.length > 1 }
expect(duplicates).to be_empty
end
end
describe '.by_category' do
it 'returns sorted results' do
result = described_class.by_category('General')
expect(result).to eq(result.sort)
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
expect(subject.find('nonexistent-file')).to be nil
end
it 'returns the corresponding object of a valid file' do
template = subject.find(filename)
expect(template).to be_a described_class
expect(template.name).to eq(filename)
end
end
describe '#<=>' do
it 'sorts lexicographically' do
one = described_class.new("a.#{file_extension}")
other = described_class.new("z.#{file_extension}")
expect(one.<=>(other)).to be(-1)
expect([other, one].sort).to eq([one, other])
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