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 ...@@ -6,7 +6,8 @@ class TemplateFinder
VENDORED_TEMPLATES = HashWithIndifferentAccess.new( VENDORED_TEMPLATES = HashWithIndifferentAccess.new(
dockerfiles: ::Gitlab::Template::DockerfileTemplate, dockerfiles: ::Gitlab::Template::DockerfileTemplate,
gitignores: ::Gitlab::Template::GitignoreTemplate, gitignores: ::Gitlab::Template::GitignoreTemplate,
gitlab_ci_ymls: ::Gitlab::Template::GitlabCiYmlTemplate gitlab_ci_ymls: ::Gitlab::Template::GitlabCiYmlTemplate,
metrics_dashboard_ymls: ::Gitlab::Template::MetricsDashboardTemplate
).freeze ).freeze
class << self class << self
......
...@@ -200,6 +200,10 @@ module BlobHelper ...@@ -200,6 +200,10 @@ module BlobHelper
@gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute) @gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute)
end end
def metrics_dashboard_ymls(project)
@metrics_dashboard_ymls ||= template_dropdown_names(TemplateFinder.build(:metrics_dashboard_ymls, project).execute)
end
def dockerfile_names(project) def dockerfile_names(project)
@dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute) @dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute)
end 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).
...@@ -33,12 +33,13 @@ Templates must be added to a specific subdirectory in the repository, ...@@ -33,12 +33,13 @@ Templates must be added to a specific subdirectory in the repository,
corresponding to the kind of template. The following types of custom templates corresponding to the kind of template. The following types of custom templates
are supported: are supported:
| Type | Directory | Extension | | Type | Directory | Extension |
| :---------------: | :-----------: | :-----------: | | :---------------: | :-----------: | :-----------: |
| `Dockerfile` | `Dockerfile` | `.dockerfile` | | `Dockerfile` | `Dockerfile` | `.dockerfile` |
| `.gitignore` | `gitignore` | `.gitignore` | | `.gitignore` | `gitignore` | `.gitignore` |
| `.gitlab-ci.yml` | `gitlab-ci` | `.yml` | | `.gitlab-ci.yml` | `gitlab-ci` | `.yml` |
| `LICENSE` | `LICENSE` | `.txt` | | `LICENSE` | `LICENSE` | `.txt` |
| `metrics-dashboard.yml` | `metrics-dashboards` | `.yml` |
Each template must go in its respective subdirectory, have the correct Each template must go in its respective subdirectory, have the correct
extension and not be empty. So, the hierarchy should look like this: 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: ...@@ -57,6 +58,9 @@ extension and not be empty. So, the hierarchy should look like this:
|-- LICENSE |-- LICENSE
|-- custom_license.txt |-- custom_license.txt
|-- another_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 Once this is established, the list of custom templates will be included when
......
...@@ -7,7 +7,8 @@ module EE ...@@ -7,7 +7,8 @@ module EE
CUSTOM_TEMPLATES = HashWithIndifferentAccess.new( CUSTOM_TEMPLATES = HashWithIndifferentAccess.new(
dockerfiles: ::Gitlab::Template::CustomDockerfileTemplate, dockerfiles: ::Gitlab::Template::CustomDockerfileTemplate,
gitignores: ::Gitlab::Template::CustomGitignoreTemplate, gitignores: ::Gitlab::Template::CustomGitignoreTemplate,
gitlab_ci_ymls: ::Gitlab::Template::CustomGitlabCiYmlTemplate gitlab_ci_ymls: ::Gitlab::Template::CustomGitlabCiYmlTemplate,
metrics_dashboard_ymls: ::Gitlab::Template::CustomMetricsDashboardYmlTemplate
).freeze ).freeze
attr_reader :custom_templates 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 ...@@ -20,6 +20,10 @@ RSpec.describe "Custom file template classes" do
'LICENSE/bar.txt' => 'CustomLicenseTemplate Bar', 'LICENSE/bar.txt' => 'CustomLicenseTemplate Bar',
'LICENSE/bad.xyz' => 'CustomLicenseTemplate Bad', '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', 'Dockerfile/category/baz.txt' => 'CustomDockerfileTemplate category baz',
'gitignore/category/baz.txt' => 'CustomGitignoreTemplate category baz', 'gitignore/category/baz.txt' => 'CustomGitignoreTemplate category baz',
'gitlab-ci/category/baz.yml' => 'CustomGitlabCiYmlTemplate category baz', 'gitlab-ci/category/baz.yml' => 'CustomGitlabCiYmlTemplate category baz',
...@@ -32,7 +36,8 @@ RSpec.describe "Custom file template classes" do ...@@ -32,7 +36,8 @@ RSpec.describe "Custom file template classes" do
::Gitlab::Template::CustomDockerfileTemplate, ::Gitlab::Template::CustomDockerfileTemplate,
::Gitlab::Template::CustomGitignoreTemplate, ::Gitlab::Template::CustomGitignoreTemplate,
::Gitlab::Template::CustomGitlabCiYmlTemplate, ::Gitlab::Template::CustomGitlabCiYmlTemplate,
::Gitlab::Template::CustomLicenseTemplate ::Gitlab::Template::CustomLicenseTemplate,
::Gitlab::Template::CustomMetricsDashboardYmlTemplate
].each do |template_class| ].each do |template_class|
describe template_class do describe template_class do
let(:name) { template_class.name.demodulize } let(:name) { template_class.name.demodulize }
......
...@@ -4,7 +4,7 @@ module API ...@@ -4,7 +4,7 @@ module API
class ProjectTemplates < Grape::API::Instance class ProjectTemplates < Grape::API::Instance
include PaginationParams 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) # 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 # 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. # values (e.g. C%2B%2B for C++), so allow % and + as well.
...@@ -14,7 +14,7 @@ module API ...@@ -14,7 +14,7 @@ module API
params do params do
requires :id, type: String, desc: 'The ID of a project' 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 end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of templates available to this project' 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
...@@ -12,7 +12,8 @@ RSpec.describe TemplateFinder do ...@@ -12,7 +12,8 @@ RSpec.describe TemplateFinder do
:dockerfiles | described_class :dockerfiles | described_class
:gitignores | described_class :gitignores | described_class
:gitlab_ci_ymls | described_class :gitlab_ci_ymls | described_class
:licenses | ::LicenseTemplateFinder :licenses | ::LicenseTemplateFinder
:metrics_dashboard_ymls | described_class
end end
with_them do with_them do
...@@ -28,6 +29,7 @@ RSpec.describe TemplateFinder do ...@@ -28,6 +29,7 @@ RSpec.describe TemplateFinder do
:dockerfiles | 'Binary' :dockerfiles | 'Binary'
:gitignores | 'Actionscript' :gitignores | 'Actionscript'
:gitlab_ci_ymls | 'Android' :gitlab_ci_ymls | 'Android'
:metrics_dashboard_ymls | 'Default'
end end
with_them do with_them do
......
...@@ -6,10 +6,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do ...@@ -6,10 +6,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
subject { described_class } subject { described_class }
describe '.all' do 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 it 'combines the globals and rest' do
all = subject.all.map(&:name) all = subject.all.map(&:name)
...@@ -17,34 +13,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do ...@@ -17,34 +13,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
expect(all).to include('Docker') expect(all).to include('Docker')
expect(all).to include('Ruby') expect(all).to include('Ruby')
end 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 end
describe '#content' do describe '#content' do
...@@ -56,13 +24,5 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do ...@@ -56,13 +24,5 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
end end
end end
describe '#<=>' do it_behaves_like 'file template shared examples', 'Ruby', '.gitlab-ci.yml'
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
end 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 ...@@ -62,6 +62,15 @@ RSpec.describe API::ProjectTemplates do
expect(json_response).to satisfy_one { |template| template['key'] == 'mit' } expect(json_response).to satisfy_one { |template| template['key'] == 'mit' }
end 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 it 'returns 400 for an unknown template type' do
get api("/projects/#{public_project.id}/templates/unknown") get api("/projects/#{public_project.id}/templates/unknown")
...@@ -136,6 +145,14 @@ RSpec.describe API::ProjectTemplates do ...@@ -136,6 +145,14 @@ RSpec.describe API::ProjectTemplates do
expect(json_response['name']).to eq('Android') expect(json_response['name']).to eq('Android')
end 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 it 'returns a specific license' do
get api("/projects/#{public_project.id}/templates/licenses/mit") get api("/projects/#{public_project.id}/templates/licenses/mit")
...@@ -166,6 +183,10 @@ RSpec.describe API::ProjectTemplates do ...@@ -166,6 +183,10 @@ RSpec.describe API::ProjectTemplates do
subject { get api("/projects/#{url_encoded_path}/templates/gitlab_ci_ymls/Android") } subject { get api("/projects/#{url_encoded_path}/templates/gitlab_ci_ymls/Android") }
end 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| shared_examples 'path traversal attempt' do |template_type|
it 'rejects invalid filenames' do it 'rejects invalid filenames' do
get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea") 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