mirror of https://github.com/grafana/grafana
commit
10302140be
@ -0,0 +1,246 @@ |
||||
{ |
||||
"apiVersion": "dashboard.grafana.app/v2alpha1", |
||||
"kind": "Dashboard", |
||||
"metadata": { |
||||
"name": "sample-dash-v2" |
||||
}, |
||||
"spec": { |
||||
"annotations": [ |
||||
{ |
||||
"kind": "AnnotationQuery", |
||||
"spec": { |
||||
"builtIn": true, |
||||
"datasource": { |
||||
"type": "grafana", |
||||
"uid": "-- Grafana --" |
||||
}, |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations \u0026 Alerts" |
||||
} |
||||
} |
||||
], |
||||
"cursorSync": "Off", |
||||
"description": "", |
||||
"editable": true, |
||||
"elements": { |
||||
"panel-1": { |
||||
"kind": "Panel", |
||||
"spec": { |
||||
"data": { |
||||
"kind": "QueryGroup", |
||||
"spec": { |
||||
"queries": [ |
||||
{ |
||||
"kind": "PanelQuery", |
||||
"spec": { |
||||
"hidden": false, |
||||
"query": { |
||||
"kind": "grafana-testdata-datasource", |
||||
"spec": {} |
||||
}, |
||||
"refId": "A" |
||||
} |
||||
} |
||||
], |
||||
"queryOptions": {}, |
||||
"transformations": [] |
||||
} |
||||
}, |
||||
"description": "", |
||||
"id": 1, |
||||
"links": [], |
||||
"title": "Simle timeseries", |
||||
"vizConfig": { |
||||
"kind": "timeseries", |
||||
"spec": { |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "palette-classic" |
||||
}, |
||||
"custom": { |
||||
"axisBorderShow": false, |
||||
"axisCenteredZero": false, |
||||
"axisColorMode": "text", |
||||
"axisLabel": "", |
||||
"axisPlacement": "auto", |
||||
"barAlignment": 0, |
||||
"barWidthFactor": 0.6, |
||||
"drawStyle": "line", |
||||
"fillOpacity": 0, |
||||
"gradientMode": "none", |
||||
"hideFrom": { |
||||
"legend": false, |
||||
"tooltip": false, |
||||
"viz": false |
||||
}, |
||||
"insertNulls": false, |
||||
"lineInterpolation": "linear", |
||||
"lineWidth": 1, |
||||
"pointSize": 5, |
||||
"scaleDistribution": { |
||||
"type": "linear" |
||||
}, |
||||
"showPoints": "auto", |
||||
"spanNulls": false, |
||||
"stacking": { |
||||
"group": "A", |
||||
"mode": "none" |
||||
}, |
||||
"thresholdsStyle": { |
||||
"mode": "off" |
||||
} |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"options": { |
||||
"legend": { |
||||
"calcs": [], |
||||
"displayMode": "list", |
||||
"placement": "bottom", |
||||
"showLegend": true |
||||
}, |
||||
"tooltip": { |
||||
"hideZeros": false, |
||||
"mode": "single", |
||||
"sort": "none" |
||||
} |
||||
}, |
||||
"pluginVersion": "12.0.0-pre" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"panel-2": { |
||||
"kind": "Panel", |
||||
"spec": { |
||||
"data": { |
||||
"kind": "QueryGroup", |
||||
"spec": { |
||||
"queries": [ |
||||
{ |
||||
"kind": "PanelQuery", |
||||
"spec": { |
||||
"hidden": false, |
||||
"query": { |
||||
"kind": "grafana-testdata-datasource", |
||||
"spec": {} |
||||
}, |
||||
"refId": "A" |
||||
} |
||||
} |
||||
], |
||||
"queryOptions": {}, |
||||
"transformations": [] |
||||
} |
||||
}, |
||||
"description": "", |
||||
"id": 2, |
||||
"links": [], |
||||
"title": "Simple stat", |
||||
"vizConfig": { |
||||
"kind": "stat", |
||||
"spec": { |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "thresholds" |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"options": { |
||||
"colorMode": "value", |
||||
"graphMode": "area", |
||||
"justifyMode": "auto", |
||||
"orientation": "auto", |
||||
"percentChangeColorMode": "standard", |
||||
"reduceOptions": { |
||||
"calcs": ["lastNotNull"], |
||||
"fields": "", |
||||
"values": false |
||||
}, |
||||
"showPercentChange": false, |
||||
"textMode": "auto", |
||||
"wideLayout": true |
||||
}, |
||||
"pluginVersion": "12.0.0-pre" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"layout": { |
||||
"kind": "AutoGridLayout", |
||||
"spec": { |
||||
"columnWidthMode": "standard", |
||||
"items": [ |
||||
{ |
||||
"kind": "AutoGridLayoutItem", |
||||
"spec": { |
||||
"element": { |
||||
"kind": "ElementReference", |
||||
"name": "panel-2" |
||||
} |
||||
} |
||||
}, |
||||
{ |
||||
"kind": "AutoGridLayoutItem", |
||||
"spec": { |
||||
"element": { |
||||
"kind": "ElementReference", |
||||
"name": "panel-1" |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"maxColumnCount": 3, |
||||
"rowHeightMode": "standard" |
||||
} |
||||
}, |
||||
"links": [], |
||||
"liveNow": false, |
||||
"preload": false, |
||||
"tags": [], |
||||
"timeSettings": { |
||||
"autoRefresh": "", |
||||
"autoRefreshIntervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], |
||||
"fiscalYearStartMonth": 0, |
||||
"from": "now-6h", |
||||
"hideTimepicker": false, |
||||
"timezone": "browser", |
||||
"to": "now" |
||||
}, |
||||
"title": "v2alpha1 dashboard", |
||||
"variables": [] |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
import testDashboard from '../dashboards/DataLinkWithoutSlugTest.json'; |
||||
import { e2e } from '../utils'; |
||||
|
||||
describe('Dashboard with data links that have no slug', () => { |
||||
beforeEach(() => { |
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); |
||||
}); |
||||
|
||||
it('Should not reload if linking to same dashboard', () => { |
||||
cy.intercept({ |
||||
pathname: '/api/ds/query', |
||||
}).as('query'); |
||||
|
||||
e2e.flows.importDashboard(testDashboard, 1000, true); |
||||
cy.wait('@query'); |
||||
|
||||
e2e.components.Panels.Panel.title('Data links without slug').should('exist'); |
||||
|
||||
e2e.components.DataLinksContextMenu.singleLink().contains('9yy21uzzxypg').click(); |
||||
cy.contains('Loading', { timeout: 500 }) |
||||
.should(() => {}) // prevent test from failing if it does not find loading
|
||||
.then(throwIfLoadingFound); |
||||
cy.url().should('include', urlShouldContain); |
||||
|
||||
e2e.components.DataLinksContextMenu.singleLink().contains('dr199bpvpcru').click(); |
||||
cy.contains('Loading', { timeout: 500 }) |
||||
.should(() => {}) // prevent test from failing if it does not find loading
|
||||
.then(throwIfLoadingFound); |
||||
cy.url().should('include', urlShouldContain); |
||||
|
||||
e2e.components.DataLinksContextMenu.singleLink().contains('dre33fzyxcrz').click(); |
||||
cy.contains('Loading', { timeout: 500 }) |
||||
.should(() => {}) // prevent test from failing if it does not find loading
|
||||
.then(throwIfLoadingFound); |
||||
cy.url().should('include', urlShouldContain); |
||||
}); |
||||
}); |
||||
|
||||
const urlShouldContain = '/d/data-link-no-slug/data-link-without-slug-test'; |
||||
|
||||
const throwIfLoadingFound = (el: JQuery) => { |
||||
if (el.length) { |
||||
// This means dashboard refreshes when clicking self-referencing data link
|
||||
// that has no slug in it
|
||||
throw new Error('Should not contain Loading'); |
||||
} |
||||
}; |
@ -0,0 +1,256 @@ |
||||
{ |
||||
"annotations": { |
||||
"list": [ |
||||
{ |
||||
"builtIn": 1, |
||||
"datasource": { |
||||
"type": "grafana", |
||||
"uid": "-- Grafana --" |
||||
}, |
||||
"enable": true, |
||||
"hide": true, |
||||
"iconColor": "rgba(0, 211, 255, 1)", |
||||
"name": "Annotations & Alerts", |
||||
"type": "dashboard" |
||||
} |
||||
] |
||||
}, |
||||
"editable": true, |
||||
"fiscalYearStartMonth": 0, |
||||
"graphTooltip": 0, |
||||
"id": 135, |
||||
"links": [], |
||||
"panels": [ |
||||
{ |
||||
"datasource": { |
||||
"type": "grafana-testdata-datasource", |
||||
"uid": "PD8C576611E62080A" |
||||
}, |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "thresholds" |
||||
}, |
||||
"custom": { |
||||
"align": "auto", |
||||
"cellOptions": { |
||||
"type": "auto" |
||||
}, |
||||
"inspect": false |
||||
}, |
||||
"links": [ |
||||
{ |
||||
"title": "", |
||||
"url": "/d/${__dashboard.uid}?var-instance=${__data.fields.test1}&${__url_time_range}" |
||||
} |
||||
], |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"gridPos": { |
||||
"h": 8, |
||||
"w": 12, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 4, |
||||
"options": { |
||||
"cellHeight": "sm", |
||||
"footer": { |
||||
"countRows": false, |
||||
"fields": "", |
||||
"reducer": ["sum"], |
||||
"show": false |
||||
}, |
||||
"showHeader": true |
||||
}, |
||||
"pluginVersion": "11.6.0-pre", |
||||
"targets": [ |
||||
{ |
||||
"alias": "test1", |
||||
"datasource": { |
||||
"type": "grafana-testdata-datasource", |
||||
"uid": "PD8C576611E62080A" |
||||
}, |
||||
"refId": "A", |
||||
"scenarioId": "csv_metric_values", |
||||
"stringInput": "9wvfgzurfzb, 9yy21uzzxypg, dr199bpvpcru, dre33fzyxcrz, gc6j7crvrcpf, u6g9zuxvxypv" |
||||
} |
||||
], |
||||
"title": "Data links without slug", |
||||
"type": "table" |
||||
}, |
||||
{ |
||||
"datasource": { |
||||
"type": "prometheus", |
||||
"uid": "gdev-prometheus" |
||||
}, |
||||
"fieldConfig": { |
||||
"defaults": { |
||||
"color": { |
||||
"mode": "palette-classic" |
||||
}, |
||||
"custom": { |
||||
"axisBorderShow": false, |
||||
"axisCenteredZero": false, |
||||
"axisColorMode": "text", |
||||
"axisLabel": "", |
||||
"axisPlacement": "auto", |
||||
"barAlignment": 0, |
||||
"barWidthFactor": 0.6, |
||||
"drawStyle": "line", |
||||
"fillOpacity": 0, |
||||
"gradientMode": "none", |
||||
"hideFrom": { |
||||
"legend": false, |
||||
"tooltip": false, |
||||
"viz": false |
||||
}, |
||||
"insertNulls": false, |
||||
"lineInterpolation": "linear", |
||||
"lineWidth": 1, |
||||
"pointSize": 5, |
||||
"scaleDistribution": { |
||||
"type": "linear" |
||||
}, |
||||
"showPoints": "auto", |
||||
"spanNulls": false, |
||||
"stacking": { |
||||
"group": "A", |
||||
"mode": "none" |
||||
}, |
||||
"thresholdsStyle": { |
||||
"mode": "off" |
||||
} |
||||
}, |
||||
"mappings": [], |
||||
"thresholds": { |
||||
"mode": "absolute", |
||||
"steps": [ |
||||
{ |
||||
"color": "green" |
||||
}, |
||||
{ |
||||
"color": "red", |
||||
"value": 80 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"overrides": [] |
||||
}, |
||||
"gridPos": { |
||||
"h": 8, |
||||
"w": 12, |
||||
"x": 12, |
||||
"y": 0 |
||||
}, |
||||
"id": 3, |
||||
"options": { |
||||
"legend": { |
||||
"calcs": [], |
||||
"displayMode": "list", |
||||
"placement": "bottom", |
||||
"showLegend": true |
||||
}, |
||||
"tooltip": { |
||||
"hideZeros": false, |
||||
"mode": "single", |
||||
"sort": "none" |
||||
} |
||||
}, |
||||
"pluginVersion": "11.6.0-pre", |
||||
"targets": [ |
||||
{ |
||||
"datasource": { |
||||
"type": "prometheus", |
||||
"uid": "gdev-prometheus" |
||||
}, |
||||
"disableTextWrap": false, |
||||
"editorMode": "builder", |
||||
"expr": "counters_logins{geohash=\"$instance\"}", |
||||
"fullMetaSearch": false, |
||||
"includeNullMetadata": true, |
||||
"instant": false, |
||||
"legendFormat": "__auto", |
||||
"range": true, |
||||
"refId": "A", |
||||
"useBackend": false |
||||
} |
||||
], |
||||
"title": "Panel Title", |
||||
"type": "timeseries" |
||||
} |
||||
], |
||||
"preload": false, |
||||
"refresh": "", |
||||
"schemaVersion": 41, |
||||
"tags": [], |
||||
"templating": { |
||||
"list": [ |
||||
{ |
||||
"current": { |
||||
"text": "9wvfgzurfzb", |
||||
"value": "9wvfgzurfzb" |
||||
}, |
||||
"name": "instance", |
||||
"options": [ |
||||
{ |
||||
"selected": true, |
||||
"text": "9wvfgzurfzb", |
||||
"value": "9wvfgzurfzb" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "9yy21uzzxypg", |
||||
"value": "9yy21uzzxypg" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "dr199bpvpcru", |
||||
"value": "dr199bpvpcru" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "dre33fzyxcrz", |
||||
"value": "dre33fzyxcrz" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "gc6j7crvrcpf", |
||||
"value": "gc6j7crvrcpf" |
||||
}, |
||||
{ |
||||
"selected": false, |
||||
"text": "u6g9zuxvxypv", |
||||
"value": "u6g9zuxvxypv" |
||||
} |
||||
], |
||||
"query": "9wvfgzurfzb, 9yy21uzzxypg, dr199bpvpcru, dre33fzyxcrz, gc6j7crvrcpf, u6g9zuxvxypv", |
||||
"type": "custom" |
||||
} |
||||
] |
||||
}, |
||||
"time": { |
||||
"from": "now-1h", |
||||
"to": "now" |
||||
}, |
||||
"timepicker": {}, |
||||
"timezone": "utc", |
||||
"title": "Data Link without slug test", |
||||
"uid": "data-link-no-slug", |
||||
"version": 3 |
||||
} |
@ -1,4 +1,4 @@ |
||||
package v0alpha1 |
||||
package v1 |
||||
|
||||
import ( |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
@ -1,2 +1,2 @@ |
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/folder/v0alpha1,DescendantCounts,Counts |
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/folder/v0alpha1,FolderInfoList,Items |
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/folder/v1,DescendantCounts,Counts |
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/folder/v1,FolderInfoList,Items |
@ -0,0 +1,233 @@ |
||||
package pullrequest |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"net/url" |
||||
"path" |
||||
"strings" |
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging" |
||||
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1alpha1" |
||||
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" |
||||
"github.com/grafana/grafana/pkg/infra/slugify" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" |
||||
) |
||||
|
||||
type changeInfo struct { |
||||
GrafanaBaseURL string |
||||
|
||||
// Files we tried to read
|
||||
Changes []fileChangeInfo |
||||
|
||||
// More files changed than we processed
|
||||
SkippedFiles int |
||||
|
||||
// Requested image render, but it is not available
|
||||
MissingImageRenderer bool |
||||
HasScreenshot bool |
||||
} |
||||
|
||||
type fileChangeInfo struct { |
||||
Change repository.VersionedFileChange |
||||
Error string |
||||
|
||||
// The parsed value
|
||||
Parsed *resources.ParsedResource |
||||
|
||||
// The title from inside the resource (or name if not found)
|
||||
Title string |
||||
|
||||
// The URL where this will appear (target)
|
||||
GrafanaURL string |
||||
GrafanaScreenshotURL string |
||||
|
||||
// URL where we can see a preview of this particular change
|
||||
PreviewURL string |
||||
PreviewScreenshotURL string |
||||
} |
||||
|
||||
type evaluator struct { |
||||
render ScreenshotRenderer |
||||
parsers resources.ParserFactory |
||||
urlProvider func(namespace string) string |
||||
} |
||||
|
||||
func NewEvaluator(render ScreenshotRenderer, parsers resources.ParserFactory, urlProvider func(namespace string) string) Evaluator { |
||||
return &evaluator{ |
||||
render: render, |
||||
parsers: parsers, |
||||
urlProvider: urlProvider, |
||||
} |
||||
} |
||||
|
||||
// This will process the list of versioned file changes into changeInfo
|
||||
func (e *evaluator) Evaluate(ctx context.Context, repo repository.Reader, opts provisioning.PullRequestJobOptions, changes []repository.VersionedFileChange, progress jobs.JobProgressRecorder) (changeInfo, error) { |
||||
cfg := repo.Config() |
||||
parser, err := e.parsers.GetParser(ctx, repo) |
||||
if err != nil { |
||||
return changeInfo{}, fmt.Errorf("failed to get parser for %s: %w", cfg.Name, err) |
||||
} |
||||
|
||||
baseURL := e.urlProvider(cfg.Namespace) |
||||
info := changeInfo{ |
||||
GrafanaBaseURL: baseURL, |
||||
} |
||||
|
||||
var shouldRender bool |
||||
switch { |
||||
case e.render == nil: |
||||
shouldRender = false |
||||
case !e.render.IsAvailable(ctx): |
||||
info.MissingImageRenderer = true |
||||
shouldRender = false |
||||
case len(changes) > 1 || !cfg.Spec.GitHub.GenerateDashboardPreviews: |
||||
// Only render images when there is just one change
|
||||
shouldRender = false |
||||
default: |
||||
shouldRender = true |
||||
} |
||||
|
||||
logger := logging.FromContext(ctx) |
||||
for i, change := range changes { |
||||
// process maximum 10 files
|
||||
if i >= 10 { |
||||
info.SkippedFiles = len(changes) - i |
||||
break |
||||
} |
||||
|
||||
progress.SetMessage(ctx, fmt.Sprintf("processing: %s", change.Path)) |
||||
logger.With("action", change.Action).With("path", change.Path) |
||||
|
||||
v, err := calculateFileChangeInfo(ctx, repo, info.GrafanaBaseURL, change, opts, parser) |
||||
if err != nil { |
||||
return info, fmt.Errorf("error calculating changes %w", err) |
||||
} |
||||
|
||||
// If everything applied OK, then render screenshots
|
||||
if shouldRender && v.GrafanaURL != "" && v.Parsed != nil && v.Parsed.DryRunResponse != nil { |
||||
progress.SetMessage(ctx, fmt.Sprintf("rendering screenshots: %s", change.Path)) |
||||
if err = v.renderScreenshots(ctx, info.GrafanaBaseURL, e.render); err != nil { |
||||
info.MissingImageRenderer = true |
||||
if v.Error == "" { |
||||
v.Error = "Error running image rendering" |
||||
} |
||||
|
||||
if v.GrafanaScreenshotURL != "" || v.PreviewScreenshotURL != "" { |
||||
info.HasScreenshot = true |
||||
} |
||||
} |
||||
} |
||||
|
||||
info.Changes = append(info.Changes, v) |
||||
} |
||||
return info, nil |
||||
} |
||||
|
||||
var dashboardKind = dashboard.DashboardResourceInfo.GroupVersionKind().Kind |
||||
|
||||
func calculateFileChangeInfo(ctx context.Context, repo repository.Reader, baseURL string, change repository.VersionedFileChange, opts provisioning.PullRequestJobOptions, parser resources.Parser) (fileChangeInfo, error) { |
||||
if change.Action == repository.FileActionDeleted { |
||||
return calculateFileDeleteInfo(ctx, baseURL, change) |
||||
} |
||||
|
||||
info := fileChangeInfo{Change: change} |
||||
fileInfo, err := repo.Read(ctx, change.Path, change.Ref) |
||||
if err != nil { |
||||
logger.Info("unable to read file", "err", err) |
||||
info.Error = err.Error() |
||||
return info, nil |
||||
} |
||||
|
||||
// Read the file as a resource
|
||||
info.Parsed, err = parser.Parse(ctx, fileInfo) |
||||
if err != nil { |
||||
info.Error = err.Error() |
||||
return info, nil |
||||
} |
||||
|
||||
// Find a name within the file
|
||||
obj := info.Parsed.Obj |
||||
info.Title = info.Parsed.Meta.FindTitle(obj.GetName()) |
||||
|
||||
// Check what happens when we apply changes
|
||||
// NOTE: this will also invoke any server side validation
|
||||
err = info.Parsed.DryRun(ctx) |
||||
if err != nil { |
||||
info.Error = err.Error() |
||||
return info, nil |
||||
} |
||||
|
||||
// Dashboards get special handling
|
||||
if info.Parsed.GVK.Kind == dashboardKind { |
||||
if info.Parsed.Existing != nil { |
||||
info.GrafanaURL = fmt.Sprintf("%sd/%s/%s", baseURL, obj.GetName(), |
||||
slugify.Slugify(info.Title)) |
||||
} |
||||
|
||||
// Load this file directly
|
||||
info.PreviewURL = baseURL + path.Join("admin/provisioning", |
||||
info.Parsed.Repo.Name, "dashboard/preview", info.Parsed.Info.Path) |
||||
|
||||
query := url.Values{} |
||||
query.Set("ref", info.Parsed.Info.Ref) |
||||
if opts.URL != "" { |
||||
query.Set("pull_request_url", url.QueryEscape(opts.URL)) |
||||
} |
||||
info.PreviewURL += "?" + query.Encode() |
||||
} |
||||
|
||||
return info, nil |
||||
} |
||||
|
||||
func calculateFileDeleteInfo(_ context.Context, _ string, change repository.VersionedFileChange) (fileChangeInfo, error) { |
||||
// TODO -- read the old and verify
|
||||
return fileChangeInfo{Change: change, Error: "delete feedback not yet implemented"}, nil |
||||
} |
||||
|
||||
// This will update render the linked screenshots and update the screenshotURLs
|
||||
func (f *fileChangeInfo) renderScreenshots(ctx context.Context, baseURL string, renderer ScreenshotRenderer) (err error) { |
||||
if f.GrafanaURL != "" { |
||||
f.GrafanaScreenshotURL, err = renderScreenshotFromGrafanaURL(ctx, baseURL, renderer, f.Parsed.Repo, f.GrafanaURL) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
if f.PreviewURL != "" { |
||||
f.PreviewScreenshotURL, err = renderScreenshotFromGrafanaURL(ctx, baseURL, renderer, f.Parsed.Repo, f.PreviewURL) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func renderScreenshotFromGrafanaURL(ctx context.Context, |
||||
baseURL string, |
||||
renderer ScreenshotRenderer, |
||||
repo provisioning.ResourceRepositoryInfo, |
||||
grafanaURL string, |
||||
) (string, error) { |
||||
parsed, err := url.Parse(grafanaURL) |
||||
if err != nil { |
||||
logging.FromContext(ctx).Warn("invalid", "url", grafanaURL, "err", err) |
||||
return "", err |
||||
} |
||||
snap, err := renderer.RenderScreenshot(ctx, repo, strings.TrimPrefix(parsed.Path, "/"), parsed.Query()) |
||||
if err != nil { |
||||
logging.FromContext(ctx).Warn("render failed", "url", grafanaURL, "err", err) |
||||
return "", fmt.Errorf("error rendering screenshot %w", err) |
||||
} |
||||
if strings.Contains(snap, "://") { |
||||
return snap, nil // it is a full URL already (can happen when the blob storage returns CDN urls)
|
||||
} |
||||
base, err := url.Parse(baseURL) |
||||
if err != nil { |
||||
logger.Warn("invalid base", "url", baseURL, "err", err) |
||||
return "", err |
||||
} |
||||
return base.JoinPath(snap).String(), nil |
||||
} |
@ -0,0 +1,208 @@ |
||||
package pullrequest |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/sha256" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils" |
||||
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" |
||||
"github.com/stretchr/testify/mock" |
||||
"github.com/stretchr/testify/require" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
||||
"k8s.io/apimachinery/pkg/runtime/schema" |
||||
) |
||||
|
||||
func TestCalculateChanges(t *testing.T) { |
||||
parser := resources.NewMockParser(t) |
||||
reader := repository.NewMockReader(t) |
||||
progress := jobs.NewMockJobProgressRecorder(t) |
||||
|
||||
finfo := &repository.FileInfo{ |
||||
Path: "path/to/file.json", |
||||
Ref: "ref", |
||||
Data: []byte("xxxx"), // not a valid JSON!
|
||||
} |
||||
obj := &unstructured.Unstructured{ |
||||
Object: map[string]interface{}{ |
||||
"apiVersion": resources.DashboardResource.GroupVersion().String(), |
||||
"kind": dashboardKind, // will trigger creating a URL
|
||||
"metadata": map[string]interface{}{ |
||||
"name": "the-uid", |
||||
}, |
||||
"spec": map[string]interface{}{ |
||||
"title": "hello world", // has spaces
|
||||
}, |
||||
}, |
||||
} |
||||
meta, _ := utils.MetaAccessor(obj) |
||||
|
||||
progress.On("SetMessage", mock.Anything, mock.Anything).Return() |
||||
reader.On("Read", mock.Anything, "path/to/file.json", "ref").Return(finfo, nil) |
||||
reader.On("Config").Return(&v0alpha1.Repository{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Name: "test-repo", |
||||
Namespace: "x", |
||||
}, |
||||
Spec: v0alpha1.RepositorySpec{ |
||||
GitHub: &v0alpha1.GitHubRepositoryConfig{ |
||||
GenerateDashboardPreviews: true, |
||||
}, |
||||
}, |
||||
}) |
||||
parser.On("Parse", mock.Anything, finfo).Return(&resources.ParsedResource{ |
||||
Info: finfo, |
||||
Repo: v0alpha1.ResourceRepositoryInfo{ |
||||
Namespace: "x", |
||||
Name: "y", |
||||
}, |
||||
GVK: schema.GroupVersionKind{ |
||||
Kind: dashboardKind, |
||||
}, |
||||
Obj: obj, |
||||
Existing: obj, |
||||
Meta: meta, |
||||
DryRunResponse: obj, // avoid hitting the client
|
||||
}, nil) |
||||
|
||||
pullRequest := v0alpha1.PullRequestJobOptions{ |
||||
Ref: "ref", |
||||
PR: 123, |
||||
URL: "http://github.com/pr/", |
||||
} |
||||
createdFileChange := repository.VersionedFileChange{ |
||||
Action: repository.FileActionCreated, |
||||
Path: "path/to/file.json", |
||||
Ref: "ref", |
||||
} |
||||
|
||||
t.Run("with-screenshot", func(t *testing.T) { |
||||
renderer := NewMockScreenshotRenderer(t) |
||||
renderer.On("IsAvailable", mock.Anything, mock.Anything).Return(true) |
||||
renderer.On("RenderScreenshot", mock.Anything, mock.Anything, mock.Anything, mock.Anything). |
||||
Return(getDummyRenderedURL("x"), nil) |
||||
changes := []repository.VersionedFileChange{createdFileChange} |
||||
|
||||
parserFactory := resources.NewMockParserFactory(t) |
||||
parserFactory.On("GetParser", mock.Anything, mock.Anything).Return(parser, nil) |
||||
evaluator := NewEvaluator(renderer, parserFactory, func(_ string) string { |
||||
return "http://host/" |
||||
}) |
||||
|
||||
info, err := evaluator.Evaluate(context.Background(), reader, pullRequest, changes, progress) |
||||
require.NoError(t, err) |
||||
|
||||
require.False(t, info.MissingImageRenderer) |
||||
require.Equal(t, map[string]string{ |
||||
"Grafana": "http://host/d/the-uid/hello-world", |
||||
"GrafanaSnapshot": "https://cdn2.thecatapi.com/images/9e2.jpg", |
||||
"Preview": "http://host/admin/provisioning/y/dashboard/preview/path/to/file.json?pull_request_url=http%253A%252F%252Fgithub.com%252Fpr%252F&ref=ref", |
||||
"PreviewSnapshot": "https://cdn2.thecatapi.com/images/9e2.jpg", |
||||
}, map[string]string{ |
||||
"Grafana": info.Changes[0].GrafanaURL, |
||||
"GrafanaSnapshot": info.Changes[0].GrafanaScreenshotURL, |
||||
"Preview": info.Changes[0].PreviewURL, |
||||
"PreviewSnapshot": info.Changes[0].PreviewScreenshotURL, |
||||
}) |
||||
}) |
||||
|
||||
t.Run("without-screenshot", func(t *testing.T) { |
||||
renderer := NewMockScreenshotRenderer(t) |
||||
renderer.On("IsAvailable", mock.Anything, mock.Anything).Return(false) |
||||
changes := []repository.VersionedFileChange{createdFileChange} |
||||
parserFactory := resources.NewMockParserFactory(t) |
||||
parserFactory.On("GetParser", mock.Anything, mock.Anything).Return(parser, nil) |
||||
evaluator := NewEvaluator(renderer, parserFactory, func(_ string) string { |
||||
return "http://host/" |
||||
}) |
||||
|
||||
info, err := evaluator.Evaluate(context.Background(), reader, pullRequest, changes, progress) |
||||
require.NoError(t, err) |
||||
|
||||
require.True(t, info.MissingImageRenderer) |
||||
require.Equal(t, map[string]string{ |
||||
"Grafana": "http://host/d/the-uid/hello-world", |
||||
"GrafanaSnapshot": "", |
||||
"Preview": "http://host/admin/provisioning/y/dashboard/preview/path/to/file.json?pull_request_url=http%253A%252F%252Fgithub.com%252Fpr%252F&ref=ref", |
||||
"PreviewSnapshot": "", |
||||
}, map[string]string{ |
||||
"Grafana": info.Changes[0].GrafanaURL, |
||||
"GrafanaSnapshot": info.Changes[0].GrafanaScreenshotURL, |
||||
"Preview": info.Changes[0].PreviewURL, |
||||
"PreviewSnapshot": info.Changes[0].PreviewScreenshotURL, |
||||
}) |
||||
}) |
||||
|
||||
t.Run("process first 10 files", func(t *testing.T) { |
||||
renderer := NewMockScreenshotRenderer(t) |
||||
renderer.On("IsAvailable", mock.Anything, mock.Anything).Return(true) |
||||
|
||||
changes := []repository.VersionedFileChange{} |
||||
for range 15 { |
||||
changes = append(changes, createdFileChange) |
||||
} |
||||
|
||||
parserFactory := resources.NewMockParserFactory(t) |
||||
parserFactory.On("GetParser", mock.Anything, mock.Anything).Return(parser, nil) |
||||
evaluator := NewEvaluator(renderer, parserFactory, func(_ string) string { |
||||
return "http://host/" |
||||
}) |
||||
|
||||
info, err := evaluator.Evaluate(context.Background(), reader, pullRequest, changes, progress) |
||||
require.NoError(t, err) |
||||
|
||||
require.False(t, info.MissingImageRenderer) |
||||
require.Equal(t, 10, len(info.Changes)) |
||||
require.Equal(t, 5, info.SkippedFiles) |
||||
|
||||
// Make sure we linked a URL, but no screenshot for each item
|
||||
for _, change := range info.Changes { |
||||
require.NotEmpty(t, change.GrafanaURL) |
||||
require.Empty(t, change.GrafanaScreenshotURL) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func TestDummyImageURL(t *testing.T) { |
||||
urls := []string{} |
||||
for i := range 10 { |
||||
urls = append(urls, getDummyRenderedURL(fmt.Sprintf("http://%d", i))) |
||||
} |
||||
require.Equal(t, []string{ |
||||
"https://cdn2.thecatapi.com/images/9e2.jpg", |
||||
"https://cdn2.thecatapi.com/images/bhs.jpg", |
||||
"https://cdn2.thecatapi.com/images/d54.jpg", |
||||
"https://cdn2.thecatapi.com/images/99c.jpg", |
||||
"https://cdn2.thecatapi.com/images/9e2.jpg", |
||||
"https://cdn2.thecatapi.com/images/bhs.jpg", |
||||
"https://cdn2.thecatapi.com/images/d54.jpg", |
||||
"https://cdn2.thecatapi.com/images/99c.jpg", |
||||
"https://cdn2.thecatapi.com/images/9e2.jpg", |
||||
"https://cdn2.thecatapi.com/images/bhs.jpg", |
||||
}, urls) |
||||
} |
||||
|
||||
// Returns a random (but stable) image for a string
|
||||
func getDummyRenderedURL(url string) string { |
||||
dummy := []string{ |
||||
"https://cdn2.thecatapi.com/images/9e2.jpg", |
||||
"https://cdn2.thecatapi.com/images/bhs.jpg", |
||||
"https://cdn2.thecatapi.com/images/d54.jpg", |
||||
"https://cdn2.thecatapi.com/images/99c.jpg", |
||||
} |
||||
|
||||
idx := 0 |
||||
hash := sha256.New() |
||||
bytes := hash.Sum([]byte(url)) |
||||
if len(bytes) > 8 { |
||||
v := binary.BigEndian.Uint64(bytes[0:8]) |
||||
idx = int(v) % len(dummy) |
||||
} |
||||
return dummy[idx] |
||||
} |
@ -0,0 +1,124 @@ |
||||
package pullrequest |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
"html/template" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
type commenter struct { |
||||
templateDashboard *template.Template |
||||
templateTable *template.Template |
||||
templateRenderInfo *template.Template |
||||
} |
||||
|
||||
func NewCommenter() Commenter { |
||||
return &commenter{ |
||||
templateDashboard: template.Must(template.New("dashboard").Parse(commentTemplateSingleDashboard)), |
||||
templateTable: template.Must(template.New("table").Parse(commentTemplateTable)), |
||||
templateRenderInfo: template.Must(template.New("setup").Parse(commentTemplateMissingImageRenderer)), |
||||
} |
||||
} |
||||
|
||||
func (c *commenter) Comment(ctx context.Context, prRepo PullRequestRepo, pr int, info changeInfo) error { |
||||
comment, err := c.generateComment(ctx, info) |
||||
if err != nil { |
||||
return fmt.Errorf("unable to generate comment text: %w", err) |
||||
} |
||||
|
||||
if err := prRepo.CommentPullRequest(ctx, pr, comment); err != nil { |
||||
return fmt.Errorf("comment pull request: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (c *commenter) generateComment(_ context.Context, info changeInfo) (string, error) { |
||||
if len(info.Changes) == 0 { |
||||
return "no changes found", nil |
||||
} |
||||
|
||||
var buf bytes.Buffer |
||||
if len(info.Changes) == 1 && info.Changes[0].Parsed.GVK.Kind == dashboardKind { |
||||
if err := c.templateDashboard.Execute(&buf, info.Changes[0]); err != nil { |
||||
return "", fmt.Errorf("unable to execute template: %w", err) |
||||
} |
||||
} else { |
||||
if err := c.templateTable.Execute(&buf, info); err != nil { |
||||
return "", fmt.Errorf("unable to execute template: %w", err) |
||||
} |
||||
} |
||||
|
||||
if info.MissingImageRenderer { |
||||
if err := c.templateRenderInfo.Execute(&buf, info); err != nil { |
||||
return "", fmt.Errorf("unable to execute template: %w", err) |
||||
} |
||||
} |
||||
|
||||
return strings.TrimSpace(buf.String()), nil |
||||
} |
||||
|
||||
const commentTemplateSingleDashboard = `Hey there! 🎉 |
||||
Grafana spotted some changes to your dashboard. |
||||
|
||||
{{- if and .GrafanaScreenshotURL .PreviewScreenshotURL}} |
||||
### Side by Side Comparison of {{.Parsed.Info.Path}} |
||||
| Before | After | |
||||
|----------|---------| |
||||
|  |  | |
||||
{{- else if .GrafanaScreenshotURL}} |
||||
### Original of {{.Title}} |
||||
 |
||||
{{- else if .PreviewScreenshotURL}} |
||||
### Preview of {{.Parsed.Info.Path}} |
||||
 |
||||
{{ end}} |
||||
|
||||
{{ if and .GrafanaURL .PreviewURL}} |
||||
See the [original]({{.GrafanaURL}}) and [preview]({{.PreviewURL}}) of {{.Parsed.Info.Path}}. |
||||
{{- else if .GrafanaURL}} |
||||
See the [original]({{.GrafanaURL}}) of {{.Title}}. |
||||
{{- else if .PreviewURL}} |
||||
See the [preview]({{.PreviewURL}}) of {{.Parsed.Info.Path}}. |
||||
{{- end}} |
||||
` |
||||
|
||||
const commentTemplateTable = `Hey there! 🎉 |
||||
Grafana spotted some changes. |
||||
|
||||
| Action | Kind | Resource | Preview | |
||||
|--------|------|----------|---------| |
||||
{{- range .Changes}} |
||||
| {{.Parsed.Action}} | {{.Kind}} | {{.ExistingLink}} | {{ if .PreviewURL}}[preview]({{.PreviewURL}}){{ end }} | |
||||
{{- end}} |
||||
|
||||
{{ if .SkippedFiles }} |
||||
and {{ .SkippedFiles }} more files. |
||||
{{ end}} |
||||
` |
||||
|
||||
// TODO: this should expand and show links to setup docs
|
||||
const commentTemplateMissingImageRenderer = ` |
||||
NOTE: The image renderer is not configured |
||||
` |
||||
|
||||
func (f *fileChangeInfo) Kind() string { |
||||
if f.Parsed == nil { |
||||
return filepath.Ext(f.Change.Path) |
||||
} |
||||
v := f.Parsed.GVK.Kind |
||||
if v == "" { |
||||
return filepath.Ext(f.Parsed.Info.Path) |
||||
} |
||||
return f.Parsed.GVK.Kind |
||||
} |
||||
|
||||
func (f *fileChangeInfo) ExistingLink() string { |
||||
if f.GrafanaURL != "" { |
||||
return fmt.Sprintf("[%s](%s)", f.Title, f.GrafanaURL) |
||||
} |
||||
return f.Title |
||||
} |
@ -0,0 +1,134 @@ |
||||
package pullrequest |
||||
|
||||
import ( |
||||
"context" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
"k8s.io/apimachinery/pkg/runtime/schema" |
||||
|
||||
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" |
||||
) |
||||
|
||||
func TestGenerateComment(t *testing.T) { |
||||
for _, tc := range []struct { |
||||
Name string |
||||
Input changeInfo |
||||
}{ |
||||
{"new dashboard", changeInfo{ |
||||
GrafanaBaseURL: "http://host/", |
||||
Changes: []fileChangeInfo{ |
||||
{ |
||||
Parsed: &resources.ParsedResource{ |
||||
Info: &repository.FileInfo{ |
||||
Path: "file.json", |
||||
}, |
||||
GVK: schema.GroupVersionKind{Kind: "Dashboard"}, |
||||
Action: v0alpha1.ResourceActionCreate, |
||||
}, |
||||
Title: "New Dashboard", |
||||
PreviewURL: "http://grafana/admin/preview", |
||||
PreviewScreenshotURL: getDummyRenderedURL("http://grafana/admin/preview"), |
||||
}, |
||||
}, |
||||
}}, |
||||
{"update dashboard", changeInfo{ |
||||
GrafanaBaseURL: "http://host/", |
||||
Changes: []fileChangeInfo{ |
||||
{ |
||||
Parsed: &resources.ParsedResource{ |
||||
Info: &repository.FileInfo{ |
||||
Path: "file.json", |
||||
}, |
||||
Action: v0alpha1.ResourceActionUpdate, |
||||
GVK: schema.GroupVersionKind{Kind: "Dashboard"}, |
||||
}, |
||||
Title: "Existing Dashboard", |
||||
GrafanaURL: "http://grafana/d/uid", |
||||
PreviewURL: "http://grafana/admin/preview", |
||||
|
||||
GrafanaScreenshotURL: getDummyRenderedURL("http://grafana/d/uid"), |
||||
PreviewScreenshotURL: getDummyRenderedURL("http://grafana/admin/preview"), |
||||
}, |
||||
}, |
||||
}}, |
||||
{"update dashboard missing renderer", changeInfo{ |
||||
GrafanaBaseURL: "http://host/", |
||||
Changes: []fileChangeInfo{ |
||||
{ |
||||
Parsed: &resources.ParsedResource{ |
||||
Info: &repository.FileInfo{ |
||||
Path: "file.json", |
||||
}, |
||||
Action: v0alpha1.ResourceActionUpdate, |
||||
GVK: schema.GroupVersionKind{Kind: "Dashboard"}, |
||||
}, |
||||
Title: "Existing Dashboard", |
||||
GrafanaURL: "http://grafana/d/uid", |
||||
PreviewURL: "http://grafana/admin/preview", |
||||
}, |
||||
}, |
||||
MissingImageRenderer: true, |
||||
}}, |
||||
{"multiple files", changeInfo{ |
||||
GrafanaBaseURL: "http://host/", |
||||
SkippedFiles: 5, |
||||
Changes: []fileChangeInfo{ |
||||
{ |
||||
Parsed: &resources.ParsedResource{ |
||||
Info: &repository.FileInfo{ |
||||
Path: "aaa.json", |
||||
}, |
||||
Action: v0alpha1.ResourceActionCreate, |
||||
GVK: schema.GroupVersionKind{Kind: "Dashboard"}, |
||||
}, |
||||
Title: "Dash A", |
||||
PreviewURL: "http://grafana/admin/preview", |
||||
}, |
||||
{ |
||||
Parsed: &resources.ParsedResource{ |
||||
Info: &repository.FileInfo{ |
||||
Path: "bbb.json", |
||||
}, |
||||
Action: v0alpha1.ResourceActionUpdate, |
||||
GVK: schema.GroupVersionKind{Kind: "Dashboard"}, |
||||
}, |
||||
Title: "Dash B", |
||||
GrafanaURL: "http://grafana/d/bbb", |
||||
PreviewURL: "http://grafana/admin/preview", |
||||
}, |
||||
{ |
||||
Parsed: &resources.ParsedResource{ |
||||
Info: &repository.FileInfo{ |
||||
Path: "bbb.json", |
||||
}, |
||||
Action: v0alpha1.ResourceActionCreate, |
||||
GVK: schema.GroupVersionKind{Kind: "Playlist"}, |
||||
}, |
||||
Title: "My Playlist", |
||||
}, |
||||
}, |
||||
}}, |
||||
} { |
||||
t.Run(tc.Name, func(t *testing.T) { |
||||
repo := NewMockPullRequestRepo(t) |
||||
|
||||
// expectation on the comment
|
||||
fpath := filepath.Join("testdata", strings.ReplaceAll(tc.Name, " ", "-")+".md") |
||||
// We can ignore the gosec G304 because this is only for tests
|
||||
// nolint:gosec
|
||||
expect, err := os.ReadFile(fpath) |
||||
require.NoError(t, err) |
||||
repo.On("CommentPullRequest", context.Background(), 1, string(expect)).Return(nil) |
||||
|
||||
commenter := NewCommenter() |
||||
err = commenter.Comment(context.Background(), repo, 1, tc.Input) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,85 @@ |
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package pullrequest |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
mock "github.com/stretchr/testify/mock" |
||||
) |
||||
|
||||
// MockCommenter is an autogenerated mock type for the Commenter type
|
||||
type MockCommenter struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockCommenter_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockCommenter) EXPECT() *MockCommenter_Expecter { |
||||
return &MockCommenter_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// Comment provides a mock function with given fields: ctx, repo, pr, changeInfo3
|
||||
func (_m *MockCommenter) Comment(ctx context.Context, repo PullRequestRepo, pr int, changeInfo3 changeInfo) error { |
||||
ret := _m.Called(ctx, repo, pr, changeInfo3) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Comment") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, PullRequestRepo, int, changeInfo) error); ok { |
||||
r0 = rf(ctx, repo, pr, changeInfo3) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockCommenter_Comment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Comment'
|
||||
type MockCommenter_Comment_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// Comment is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repo PullRequestRepo
|
||||
// - pr int
|
||||
// - changeInfo3 changeInfo
|
||||
func (_e *MockCommenter_Expecter) Comment(ctx interface{}, repo interface{}, pr interface{}, changeInfo3 interface{}) *MockCommenter_Comment_Call { |
||||
return &MockCommenter_Comment_Call{Call: _e.mock.On("Comment", ctx, repo, pr, changeInfo3)} |
||||
} |
||||
|
||||
func (_c *MockCommenter_Comment_Call) Run(run func(ctx context.Context, repo PullRequestRepo, pr int, changeInfo3 changeInfo)) *MockCommenter_Comment_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(PullRequestRepo), args[2].(int), args[3].(changeInfo)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockCommenter_Comment_Call) Return(_a0 error) *MockCommenter_Comment_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockCommenter_Comment_Call) RunAndReturn(run func(context.Context, PullRequestRepo, int, changeInfo) error) *MockCommenter_Comment_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockCommenter creates a new instance of MockCommenter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockCommenter(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockCommenter { |
||||
mock := &MockCommenter{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,101 @@ |
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package pullrequest |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
jobs "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" |
||||
mock "github.com/stretchr/testify/mock" |
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" |
||||
) |
||||
|
||||
// MockEvaluator is an autogenerated mock type for the Evaluator type
|
||||
type MockEvaluator struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockEvaluator_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockEvaluator) EXPECT() *MockEvaluator_Expecter { |
||||
return &MockEvaluator_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// Evaluate provides a mock function with given fields: ctx, repo, opts, changes, progress
|
||||
func (_m *MockEvaluator) Evaluate(ctx context.Context, repo repository.Reader, opts v0alpha1.PullRequestJobOptions, changes []repository.VersionedFileChange, progress jobs.JobProgressRecorder) (changeInfo, error) { |
||||
ret := _m.Called(ctx, repo, opts, changes, progress) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Evaluate") |
||||
} |
||||
|
||||
var r0 changeInfo |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Reader, v0alpha1.PullRequestJobOptions, []repository.VersionedFileChange, jobs.JobProgressRecorder) (changeInfo, error)); ok { |
||||
return rf(ctx, repo, opts, changes, progress) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.Reader, v0alpha1.PullRequestJobOptions, []repository.VersionedFileChange, jobs.JobProgressRecorder) changeInfo); ok { |
||||
r0 = rf(ctx, repo, opts, changes, progress) |
||||
} else { |
||||
r0 = ret.Get(0).(changeInfo) |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, repository.Reader, v0alpha1.PullRequestJobOptions, []repository.VersionedFileChange, jobs.JobProgressRecorder) error); ok { |
||||
r1 = rf(ctx, repo, opts, changes, progress) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockEvaluator_Evaluate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Evaluate'
|
||||
type MockEvaluator_Evaluate_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// Evaluate is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repo repository.Reader
|
||||
// - opts v0alpha1.PullRequestJobOptions
|
||||
// - changes []repository.VersionedFileChange
|
||||
// - progress jobs.JobProgressRecorder
|
||||
func (_e *MockEvaluator_Expecter) Evaluate(ctx interface{}, repo interface{}, opts interface{}, changes interface{}, progress interface{}) *MockEvaluator_Evaluate_Call { |
||||
return &MockEvaluator_Evaluate_Call{Call: _e.mock.On("Evaluate", ctx, repo, opts, changes, progress)} |
||||
} |
||||
|
||||
func (_c *MockEvaluator_Evaluate_Call) Run(run func(ctx context.Context, repo repository.Reader, opts v0alpha1.PullRequestJobOptions, changes []repository.VersionedFileChange, progress jobs.JobProgressRecorder)) *MockEvaluator_Evaluate_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(repository.Reader), args[2].(v0alpha1.PullRequestJobOptions), args[3].([]repository.VersionedFileChange), args[4].(jobs.JobProgressRecorder)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockEvaluator_Evaluate_Call) Return(_a0 changeInfo, _a1 error) *MockEvaluator_Evaluate_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockEvaluator_Evaluate_Call) RunAndReturn(run func(context.Context, repository.Reader, v0alpha1.PullRequestJobOptions, []repository.VersionedFileChange, jobs.JobProgressRecorder) (changeInfo, error)) *MockEvaluator_Evaluate_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockEvaluator creates a new instance of MockEvaluator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockEvaluator(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockEvaluator { |
||||
mock := &MockEvaluator{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,351 @@ |
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package pullrequest |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
mock "github.com/stretchr/testify/mock" |
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" |
||||
) |
||||
|
||||
// MockPullRequestRepo is an autogenerated mock type for the PullRequestRepo type
|
||||
type MockPullRequestRepo struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockPullRequestRepo_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockPullRequestRepo) EXPECT() *MockPullRequestRepo_Expecter { |
||||
return &MockPullRequestRepo_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// ClearAllPullRequestFileComments provides a mock function with given fields: ctx, pr
|
||||
func (_m *MockPullRequestRepo) ClearAllPullRequestFileComments(ctx context.Context, pr int) error { |
||||
ret := _m.Called(ctx, pr) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for ClearAllPullRequestFileComments") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, int) error); ok { |
||||
r0 = rf(ctx, pr) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockPullRequestRepo_ClearAllPullRequestFileComments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearAllPullRequestFileComments'
|
||||
type MockPullRequestRepo_ClearAllPullRequestFileComments_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// ClearAllPullRequestFileComments is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - pr int
|
||||
func (_e *MockPullRequestRepo_Expecter) ClearAllPullRequestFileComments(ctx interface{}, pr interface{}) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { |
||||
return &MockPullRequestRepo_ClearAllPullRequestFileComments_Call{Call: _e.mock.On("ClearAllPullRequestFileComments", ctx, pr)} |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_ClearAllPullRequestFileComments_Call) Run(run func(ctx context.Context, pr int)) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(int)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_ClearAllPullRequestFileComments_Call) Return(_a0 error) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_ClearAllPullRequestFileComments_Call) RunAndReturn(run func(context.Context, int) error) *MockPullRequestRepo_ClearAllPullRequestFileComments_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// CommentPullRequest provides a mock function with given fields: ctx, pr, comment
|
||||
func (_m *MockPullRequestRepo) CommentPullRequest(ctx context.Context, pr int, comment string) error { |
||||
ret := _m.Called(ctx, pr, comment) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for CommentPullRequest") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, int, string) error); ok { |
||||
r0 = rf(ctx, pr, comment) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockPullRequestRepo_CommentPullRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommentPullRequest'
|
||||
type MockPullRequestRepo_CommentPullRequest_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// CommentPullRequest is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - pr int
|
||||
// - comment string
|
||||
func (_e *MockPullRequestRepo_Expecter) CommentPullRequest(ctx interface{}, pr interface{}, comment interface{}) *MockPullRequestRepo_CommentPullRequest_Call { |
||||
return &MockPullRequestRepo_CommentPullRequest_Call{Call: _e.mock.On("CommentPullRequest", ctx, pr, comment)} |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CommentPullRequest_Call) Run(run func(ctx context.Context, pr int, comment string)) *MockPullRequestRepo_CommentPullRequest_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(int), args[2].(string)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CommentPullRequest_Call) Return(_a0 error) *MockPullRequestRepo_CommentPullRequest_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CommentPullRequest_Call) RunAndReturn(run func(context.Context, int, string) error) *MockPullRequestRepo_CommentPullRequest_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// CommentPullRequestFile provides a mock function with given fields: ctx, pr, path, ref, comment
|
||||
func (_m *MockPullRequestRepo) CommentPullRequestFile(ctx context.Context, pr int, path string, ref string, comment string) error { |
||||
ret := _m.Called(ctx, pr, path, ref, comment) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for CommentPullRequestFile") |
||||
} |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, int, string, string, string) error); ok { |
||||
r0 = rf(ctx, pr, path, ref, comment) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockPullRequestRepo_CommentPullRequestFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommentPullRequestFile'
|
||||
type MockPullRequestRepo_CommentPullRequestFile_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// CommentPullRequestFile is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - pr int
|
||||
// - path string
|
||||
// - ref string
|
||||
// - comment string
|
||||
func (_e *MockPullRequestRepo_Expecter) CommentPullRequestFile(ctx interface{}, pr interface{}, path interface{}, ref interface{}, comment interface{}) *MockPullRequestRepo_CommentPullRequestFile_Call { |
||||
return &MockPullRequestRepo_CommentPullRequestFile_Call{Call: _e.mock.On("CommentPullRequestFile", ctx, pr, path, ref, comment)} |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CommentPullRequestFile_Call) Run(run func(ctx context.Context, pr int, path string, ref string, comment string)) *MockPullRequestRepo_CommentPullRequestFile_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(int), args[2].(string), args[3].(string), args[4].(string)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CommentPullRequestFile_Call) Return(_a0 error) *MockPullRequestRepo_CommentPullRequestFile_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CommentPullRequestFile_Call) RunAndReturn(run func(context.Context, int, string, string, string) error) *MockPullRequestRepo_CommentPullRequestFile_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// CompareFiles provides a mock function with given fields: ctx, base, ref
|
||||
func (_m *MockPullRequestRepo) CompareFiles(ctx context.Context, base string, ref string) ([]repository.VersionedFileChange, error) { |
||||
ret := _m.Called(ctx, base, ref) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for CompareFiles") |
||||
} |
||||
|
||||
var r0 []repository.VersionedFileChange |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]repository.VersionedFileChange, error)); ok { |
||||
return rf(ctx, base, ref) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) []repository.VersionedFileChange); ok { |
||||
r0 = rf(ctx, base, ref) |
||||
} else { |
||||
if ret.Get(0) != nil { |
||||
r0 = ret.Get(0).([]repository.VersionedFileChange) |
||||
} |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { |
||||
r1 = rf(ctx, base, ref) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockPullRequestRepo_CompareFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompareFiles'
|
||||
type MockPullRequestRepo_CompareFiles_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// CompareFiles is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - base string
|
||||
// - ref string
|
||||
func (_e *MockPullRequestRepo_Expecter) CompareFiles(ctx interface{}, base interface{}, ref interface{}) *MockPullRequestRepo_CompareFiles_Call { |
||||
return &MockPullRequestRepo_CompareFiles_Call{Call: _e.mock.On("CompareFiles", ctx, base, ref)} |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CompareFiles_Call) Run(run func(ctx context.Context, base string, ref string)) *MockPullRequestRepo_CompareFiles_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(string), args[2].(string)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CompareFiles_Call) Return(_a0 []repository.VersionedFileChange, _a1 error) *MockPullRequestRepo_CompareFiles_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_CompareFiles_Call) RunAndReturn(run func(context.Context, string, string) ([]repository.VersionedFileChange, error)) *MockPullRequestRepo_CompareFiles_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// Config provides a mock function with no fields
|
||||
func (_m *MockPullRequestRepo) Config() *v0alpha1.Repository { |
||||
ret := _m.Called() |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Config") |
||||
} |
||||
|
||||
var r0 *v0alpha1.Repository |
||||
if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok { |
||||
r0 = rf() |
||||
} else { |
||||
if ret.Get(0) != nil { |
||||
r0 = ret.Get(0).(*v0alpha1.Repository) |
||||
} |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockPullRequestRepo_Config_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Config'
|
||||
type MockPullRequestRepo_Config_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// Config is a helper method to define mock.On call
|
||||
func (_e *MockPullRequestRepo_Expecter) Config() *MockPullRequestRepo_Config_Call { |
||||
return &MockPullRequestRepo_Config_Call{Call: _e.mock.On("Config")} |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_Config_Call) Run(run func()) *MockPullRequestRepo_Config_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run() |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_Config_Call) Return(_a0 *v0alpha1.Repository) *MockPullRequestRepo_Config_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_Config_Call) RunAndReturn(run func() *v0alpha1.Repository) *MockPullRequestRepo_Config_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// Read provides a mock function with given fields: ctx, path, ref
|
||||
func (_m *MockPullRequestRepo) Read(ctx context.Context, path string, ref string) (*repository.FileInfo, error) { |
||||
ret := _m.Called(ctx, path, ref) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Read") |
||||
} |
||||
|
||||
var r0 *repository.FileInfo |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (*repository.FileInfo, error)); ok { |
||||
return rf(ctx, path, ref) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) *repository.FileInfo); ok { |
||||
r0 = rf(ctx, path, ref) |
||||
} else { |
||||
if ret.Get(0) != nil { |
||||
r0 = ret.Get(0).(*repository.FileInfo) |
||||
} |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { |
||||
r1 = rf(ctx, path, ref) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockPullRequestRepo_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||
type MockPullRequestRepo_Read_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// Read is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockPullRequestRepo_Expecter) Read(ctx interface{}, path interface{}, ref interface{}) *MockPullRequestRepo_Read_Call { |
||||
return &MockPullRequestRepo_Read_Call{Call: _e.mock.On("Read", ctx, path, ref)} |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_Read_Call) Run(run func(ctx context.Context, path string, ref string)) *MockPullRequestRepo_Read_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(string), args[2].(string)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_Read_Call) Return(_a0 *repository.FileInfo, _a1 error) *MockPullRequestRepo_Read_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPullRequestRepo_Read_Call) RunAndReturn(run func(context.Context, string, string) (*repository.FileInfo, error)) *MockPullRequestRepo_Read_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockPullRequestRepo creates a new instance of MockPullRequestRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockPullRequestRepo(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockPullRequestRepo { |
||||
mock := &MockPullRequestRepo{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -1,192 +0,0 @@ |
||||
package pullrequest |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
"html/template" |
||||
"net/url" |
||||
"path" |
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging" |
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" |
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
) |
||||
|
||||
// resourcePreview represents a resource that has changed in a pull request.
|
||||
type resourcePreview struct { |
||||
Filename string |
||||
Path string |
||||
Action string |
||||
Kind string |
||||
OriginalURL string |
||||
OriginalScreenshotURL string |
||||
PreviewURL string |
||||
PreviewScreenshotURL string |
||||
} |
||||
|
||||
const previewsCommentTemplate = `Hey there! 🎉 |
||||
Grafana spotted some changes in your dashboard. |
||||
|
||||
{{- if and .OriginalScreenshotURL .PreviewScreenshotURL}} |
||||
### Side by Side Comparison of {{.Filename}} |
||||
| Original | Preview | |
||||
|----------|---------| |
||||
|  |  | |
||||
{{- else if .OriginalScreenshotURL}} |
||||
### Original of {{.Filename}} |
||||
 |
||||
{{- else if .PreviewScreenshotURL}} |
||||
### Preview of {{.Filename}} |
||||
 |
||||
{{ end}} |
||||
|
||||
{{ if and .OriginalURL .PreviewURL}} |
||||
See the [original]({{.OriginalURL}}) and [preview]({{.PreviewURL}}) of {{.Filename}}. |
||||
{{- else if .OriginalURL}} |
||||
See the [original]({{.OriginalURL}}) of {{.Filename}}. |
||||
{{- else if .PreviewURL}} |
||||
See the [preview]({{.PreviewURL}}) of {{.Filename}}. |
||||
{{- end}}` |
||||
|
||||
// PreviewRenderer is an interface for rendering a preview of a file
|
||||
//
|
||||
//go:generate mockery --name PreviewRenderer --structname MockPreviewRenderer --inpackage --filename preview_renderer_mock.go --with-expecter
|
||||
type PreviewRenderer interface { |
||||
IsAvailable(ctx context.Context) bool |
||||
RenderDashboardPreview(ctx context.Context, namespace, repoName, path, ref string) (string, error) |
||||
} |
||||
|
||||
// Previewer is a service for previewing dashboard changes in a pull request
|
||||
//
|
||||
//go:generate mockery --name Previewer --structname MockPreviewer --inpackage --filename previewer_mock.go --with-expecter
|
||||
type Previewer interface { |
||||
Preview(ctx context.Context, f repository.VersionedFileChange, namespace, repoName, base, ref, pullRequestURL string, generatePreview bool) (resourcePreview, error) |
||||
GenerateComment(preview resourcePreview) (string, error) |
||||
} |
||||
|
||||
type previewer struct { |
||||
template *template.Template |
||||
urlProvider func(namespace string) string |
||||
renderer PreviewRenderer |
||||
} |
||||
|
||||
func NewPreviewer(renderer PreviewRenderer, urlProvider func(namespace string) string) *previewer { |
||||
return &previewer{ |
||||
template: template.Must(template.New("comment").Parse(previewsCommentTemplate)), |
||||
urlProvider: urlProvider, |
||||
renderer: renderer, |
||||
} |
||||
} |
||||
|
||||
// GenerateComment creates a formatted comment for dashboard previews
|
||||
func (p *previewer) GenerateComment(preview resourcePreview) (string, error) { |
||||
var buf bytes.Buffer |
||||
if err := p.template.Execute(&buf, preview); err != nil { |
||||
return "", fmt.Errorf("execute previews comment template: %w", err) |
||||
} |
||||
return buf.String(), nil |
||||
} |
||||
|
||||
// getOriginalURL returns the URL for the original version of the file based on the action
|
||||
func (p *previewer) getOriginalURL(ctx context.Context, f repository.VersionedFileChange, baseURL *url.URL, repoName, base, pullRequestURL string) string { |
||||
switch f.Action { |
||||
case repository.FileActionCreated: |
||||
return "" // No original URL for new files
|
||||
case repository.FileActionUpdated: |
||||
return p.previewURL(baseURL, repoName, base, f.Path, pullRequestURL) |
||||
case repository.FileActionRenamed: |
||||
return p.previewURL(baseURL, repoName, base, f.PreviousPath, pullRequestURL) |
||||
case repository.FileActionDeleted: |
||||
return p.previewURL(baseURL, repoName, base, f.Path, pullRequestURL) |
||||
default: |
||||
logging.FromContext(ctx).Error("unknown file action for original URL", "action", f.Action) |
||||
return "" |
||||
} |
||||
} |
||||
|
||||
// getPreviewURL returns the URL for the preview version of the file based on the action
|
||||
func (p *previewer) getPreviewURL(ctx context.Context, f repository.VersionedFileChange, baseURL *url.URL, repoName, ref, pullRequestURL string) string { |
||||
switch f.Action { |
||||
case repository.FileActionCreated, repository.FileActionUpdated, repository.FileActionRenamed: |
||||
return p.previewURL(baseURL, repoName, ref, f.Path, pullRequestURL) |
||||
case repository.FileActionDeleted: |
||||
return "" // No preview URL for deleted files
|
||||
default: |
||||
logging.FromContext(ctx).Error("unknown file action for preview URL", "action", f.Action) |
||||
return "" |
||||
} |
||||
} |
||||
|
||||
// previewURL returns the URL to preview the file in Grafana
|
||||
func (p *previewer) previewURL(u *url.URL, repoName, ref, filePath, pullRequestURL string) string { |
||||
baseURL := *u |
||||
baseURL = *baseURL.JoinPath("/admin/provisioning", repoName, "dashboard/preview", filePath) |
||||
|
||||
query := baseURL.Query() |
||||
if ref != "" { |
||||
query.Set("ref", ref) |
||||
} |
||||
if pullRequestURL != "" { |
||||
query.Set("pull_request_url", url.QueryEscape(pullRequestURL)) |
||||
} |
||||
baseURL.RawQuery = query.Encode() |
||||
|
||||
return baseURL.String() |
||||
} |
||||
|
||||
// Preview creates a preview for a single file change
|
||||
func (p *previewer) Preview( |
||||
ctx context.Context, |
||||
f repository.VersionedFileChange, |
||||
namespace string, |
||||
repoName string, |
||||
base string, |
||||
ref string, |
||||
pullRequestURL string, |
||||
generatePreview bool, |
||||
) (resourcePreview, error) { |
||||
baseURL, err := url.Parse(p.urlProvider(namespace)) |
||||
if err != nil { |
||||
return resourcePreview{}, fmt.Errorf("error parsing base url: %w", err) |
||||
} |
||||
|
||||
preview := resourcePreview{ |
||||
Filename: path.Base(f.Path), |
||||
Path: f.Path, |
||||
Kind: "dashboard", // TODO: add more kinds
|
||||
Action: string(f.Action), |
||||
OriginalURL: p.getOriginalURL(ctx, f, baseURL, repoName, base, pullRequestURL), |
||||
PreviewURL: p.getPreviewURL(ctx, f, baseURL, repoName, ref, pullRequestURL), |
||||
} |
||||
|
||||
if !generatePreview { |
||||
logger.Info("skipping dashboard preview generation", "path", f.Path) |
||||
return preview, nil |
||||
} |
||||
|
||||
if preview.PreviewURL != "" { |
||||
screenshotURL, err := p.renderer.RenderDashboardPreview(ctx, namespace, repoName, f.Path, ref) |
||||
if err != nil { |
||||
return resourcePreview{}, fmt.Errorf("render dashboard preview: %w", err) |
||||
} |
||||
preview.PreviewScreenshotURL = screenshotURL |
||||
logger.Info("dashboard preview screenshot generated", "screenshotURL", screenshotURL) |
||||
} |
||||
|
||||
if preview.OriginalURL != "" { |
||||
originalPath := f.PreviousPath |
||||
if originalPath == "" { |
||||
originalPath = f.Path |
||||
} |
||||
|
||||
screenshotURL, err := p.renderer.RenderDashboardPreview(ctx, namespace, repoName, originalPath, base) |
||||
if err != nil { |
||||
return resourcePreview{}, fmt.Errorf("render dashboard preview: %w", err) |
||||
} |
||||
preview.OriginalScreenshotURL = screenshotURL |
||||
logger.Info("original dashboard screenshot generated", "screenshotURL", screenshotURL) |
||||
} |
||||
|
||||
return preview, nil |
||||
} |
@ -1,142 +0,0 @@ |
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package pullrequest |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
mock "github.com/stretchr/testify/mock" |
||||
) |
||||
|
||||
// MockPreviewRenderer is an autogenerated mock type for the PreviewRenderer type
|
||||
type MockPreviewRenderer struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockPreviewRenderer_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockPreviewRenderer) EXPECT() *MockPreviewRenderer_Expecter { |
||||
return &MockPreviewRenderer_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// IsAvailable provides a mock function with given fields: ctx
|
||||
func (_m *MockPreviewRenderer) IsAvailable(ctx context.Context) bool { |
||||
ret := _m.Called(ctx) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for IsAvailable") |
||||
} |
||||
|
||||
var r0 bool |
||||
if rf, ok := ret.Get(0).(func(context.Context) bool); ok { |
||||
r0 = rf(ctx) |
||||
} else { |
||||
r0 = ret.Get(0).(bool) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockPreviewRenderer_IsAvailable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAvailable'
|
||||
type MockPreviewRenderer_IsAvailable_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// IsAvailable is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockPreviewRenderer_Expecter) IsAvailable(ctx interface{}) *MockPreviewRenderer_IsAvailable_Call { |
||||
return &MockPreviewRenderer_IsAvailable_Call{Call: _e.mock.On("IsAvailable", ctx)} |
||||
} |
||||
|
||||
func (_c *MockPreviewRenderer_IsAvailable_Call) Run(run func(ctx context.Context)) *MockPreviewRenderer_IsAvailable_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewRenderer_IsAvailable_Call) Return(_a0 bool) *MockPreviewRenderer_IsAvailable_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewRenderer_IsAvailable_Call) RunAndReturn(run func(context.Context) bool) *MockPreviewRenderer_IsAvailable_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// RenderDashboardPreview provides a mock function with given fields: ctx, namespace, repoName, path, ref
|
||||
func (_m *MockPreviewRenderer) RenderDashboardPreview(ctx context.Context, namespace string, repoName string, path string, ref string) (string, error) { |
||||
ret := _m.Called(ctx, namespace, repoName, path, ref) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for RenderDashboardPreview") |
||||
} |
||||
|
||||
var r0 string |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (string, error)); ok { |
||||
return rf(ctx, namespace, repoName, path, ref) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) string); ok { |
||||
r0 = rf(ctx, namespace, repoName, path, ref) |
||||
} else { |
||||
r0 = ret.Get(0).(string) |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok { |
||||
r1 = rf(ctx, namespace, repoName, path, ref) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockPreviewRenderer_RenderDashboardPreview_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenderDashboardPreview'
|
||||
type MockPreviewRenderer_RenderDashboardPreview_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// RenderDashboardPreview is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - namespace string
|
||||
// - repoName string
|
||||
// - path string
|
||||
// - ref string
|
||||
func (_e *MockPreviewRenderer_Expecter) RenderDashboardPreview(ctx interface{}, namespace interface{}, repoName interface{}, path interface{}, ref interface{}) *MockPreviewRenderer_RenderDashboardPreview_Call { |
||||
return &MockPreviewRenderer_RenderDashboardPreview_Call{Call: _e.mock.On("RenderDashboardPreview", ctx, namespace, repoName, path, ref)} |
||||
} |
||||
|
||||
func (_c *MockPreviewRenderer_RenderDashboardPreview_Call) Run(run func(ctx context.Context, namespace string, repoName string, path string, ref string)) *MockPreviewRenderer_RenderDashboardPreview_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewRenderer_RenderDashboardPreview_Call) Return(_a0 string, _a1 error) *MockPreviewRenderer_RenderDashboardPreview_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewRenderer_RenderDashboardPreview_Call) RunAndReturn(run func(context.Context, string, string, string, string) (string, error)) *MockPreviewRenderer_RenderDashboardPreview_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockPreviewRenderer creates a new instance of MockPreviewRenderer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockPreviewRenderer(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockPreviewRenderer { |
||||
mock := &MockPreviewRenderer{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -1,156 +0,0 @@ |
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package pullrequest |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository" |
||||
mock "github.com/stretchr/testify/mock" |
||||
) |
||||
|
||||
// MockPreviewer is an autogenerated mock type for the Previewer type
|
||||
type MockPreviewer struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockPreviewer_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockPreviewer) EXPECT() *MockPreviewer_Expecter { |
||||
return &MockPreviewer_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// GenerateComment provides a mock function with given fields: preview
|
||||
func (_m *MockPreviewer) GenerateComment(preview resourcePreview) (string, error) { |
||||
ret := _m.Called(preview) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for GenerateComment") |
||||
} |
||||
|
||||
var r0 string |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(resourcePreview) (string, error)); ok { |
||||
return rf(preview) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(resourcePreview) string); ok { |
||||
r0 = rf(preview) |
||||
} else { |
||||
r0 = ret.Get(0).(string) |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(resourcePreview) error); ok { |
||||
r1 = rf(preview) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockPreviewer_GenerateComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateComment'
|
||||
type MockPreviewer_GenerateComment_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// GenerateComment is a helper method to define mock.On call
|
||||
// - preview resourcePreview
|
||||
func (_e *MockPreviewer_Expecter) GenerateComment(preview interface{}) *MockPreviewer_GenerateComment_Call { |
||||
return &MockPreviewer_GenerateComment_Call{Call: _e.mock.On("GenerateComment", preview)} |
||||
} |
||||
|
||||
func (_c *MockPreviewer_GenerateComment_Call) Run(run func(preview resourcePreview)) *MockPreviewer_GenerateComment_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(resourcePreview)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewer_GenerateComment_Call) Return(_a0 string, _a1 error) *MockPreviewer_GenerateComment_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewer_GenerateComment_Call) RunAndReturn(run func(resourcePreview) (string, error)) *MockPreviewer_GenerateComment_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// Preview provides a mock function with given fields: ctx, f, namespace, repoName, base, ref, pullRequestURL, generatePreview
|
||||
func (_m *MockPreviewer) Preview(ctx context.Context, f repository.VersionedFileChange, namespace string, repoName string, base string, ref string, pullRequestURL string, generatePreview bool) (resourcePreview, error) { |
||||
ret := _m.Called(ctx, f, namespace, repoName, base, ref, pullRequestURL, generatePreview) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for Preview") |
||||
} |
||||
|
||||
var r0 resourcePreview |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.VersionedFileChange, string, string, string, string, string, bool) (resourcePreview, error)); ok { |
||||
return rf(ctx, f, namespace, repoName, base, ref, pullRequestURL, generatePreview) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.VersionedFileChange, string, string, string, string, string, bool) resourcePreview); ok { |
||||
r0 = rf(ctx, f, namespace, repoName, base, ref, pullRequestURL, generatePreview) |
||||
} else { |
||||
r0 = ret.Get(0).(resourcePreview) |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, repository.VersionedFileChange, string, string, string, string, string, bool) error); ok { |
||||
r1 = rf(ctx, f, namespace, repoName, base, ref, pullRequestURL, generatePreview) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockPreviewer_Preview_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Preview'
|
||||
type MockPreviewer_Preview_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// Preview is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - f repository.VersionedFileChange
|
||||
// - namespace string
|
||||
// - repoName string
|
||||
// - base string
|
||||
// - ref string
|
||||
// - pullRequestURL string
|
||||
// - generatePreview bool
|
||||
func (_e *MockPreviewer_Expecter) Preview(ctx interface{}, f interface{}, namespace interface{}, repoName interface{}, base interface{}, ref interface{}, pullRequestURL interface{}, generatePreview interface{}) *MockPreviewer_Preview_Call { |
||||
return &MockPreviewer_Preview_Call{Call: _e.mock.On("Preview", ctx, f, namespace, repoName, base, ref, pullRequestURL, generatePreview)} |
||||
} |
||||
|
||||
func (_c *MockPreviewer_Preview_Call) Run(run func(ctx context.Context, f repository.VersionedFileChange, namespace string, repoName string, base string, ref string, pullRequestURL string, generatePreview bool)) *MockPreviewer_Preview_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(repository.VersionedFileChange), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].(string), args[7].(bool)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewer_Preview_Call) Return(_a0 resourcePreview, _a1 error) *MockPreviewer_Preview_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockPreviewer_Preview_Call) RunAndReturn(run func(context.Context, repository.VersionedFileChange, string, string, string, string, string, bool) (resourcePreview, error)) *MockPreviewer_Preview_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockPreviewer creates a new instance of MockPreviewer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockPreviewer(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockPreviewer { |
||||
mock := &MockPreviewer{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,144 @@ |
||||
// Code generated by mockery v2.52.4. DO NOT EDIT.
|
||||
|
||||
package pullrequest |
||||
|
||||
import ( |
||||
context "context" |
||||
url "net/url" |
||||
|
||||
mock "github.com/stretchr/testify/mock" |
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" |
||||
) |
||||
|
||||
// MockScreenshotRenderer is an autogenerated mock type for the ScreenshotRenderer type
|
||||
type MockScreenshotRenderer struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockScreenshotRenderer_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockScreenshotRenderer) EXPECT() *MockScreenshotRenderer_Expecter { |
||||
return &MockScreenshotRenderer_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// IsAvailable provides a mock function with given fields: ctx
|
||||
func (_m *MockScreenshotRenderer) IsAvailable(ctx context.Context) bool { |
||||
ret := _m.Called(ctx) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for IsAvailable") |
||||
} |
||||
|
||||
var r0 bool |
||||
if rf, ok := ret.Get(0).(func(context.Context) bool); ok { |
||||
r0 = rf(ctx) |
||||
} else { |
||||
r0 = ret.Get(0).(bool) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockScreenshotRenderer_IsAvailable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsAvailable'
|
||||
type MockScreenshotRenderer_IsAvailable_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// IsAvailable is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockScreenshotRenderer_Expecter) IsAvailable(ctx interface{}) *MockScreenshotRenderer_IsAvailable_Call { |
||||
return &MockScreenshotRenderer_IsAvailable_Call{Call: _e.mock.On("IsAvailable", ctx)} |
||||
} |
||||
|
||||
func (_c *MockScreenshotRenderer_IsAvailable_Call) Run(run func(ctx context.Context)) *MockScreenshotRenderer_IsAvailable_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockScreenshotRenderer_IsAvailable_Call) Return(_a0 bool) *MockScreenshotRenderer_IsAvailable_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockScreenshotRenderer_IsAvailable_Call) RunAndReturn(run func(context.Context) bool) *MockScreenshotRenderer_IsAvailable_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// RenderScreenshot provides a mock function with given fields: ctx, repo, path, values
|
||||
func (_m *MockScreenshotRenderer) RenderScreenshot(ctx context.Context, repo v0alpha1.ResourceRepositoryInfo, path string, values url.Values) (string, error) { |
||||
ret := _m.Called(ctx, repo, path, values) |
||||
|
||||
if len(ret) == 0 { |
||||
panic("no return value specified for RenderScreenshot") |
||||
} |
||||
|
||||
var r0 string |
||||
var r1 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.ResourceRepositoryInfo, string, url.Values) (string, error)); ok { |
||||
return rf(ctx, repo, path, values) |
||||
} |
||||
if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.ResourceRepositoryInfo, string, url.Values) string); ok { |
||||
r0 = rf(ctx, repo, path, values) |
||||
} else { |
||||
r0 = ret.Get(0).(string) |
||||
} |
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, v0alpha1.ResourceRepositoryInfo, string, url.Values) error); ok { |
||||
r1 = rf(ctx, repo, path, values) |
||||
} else { |
||||
r1 = ret.Error(1) |
||||
} |
||||
|
||||
return r0, r1 |
||||
} |
||||
|
||||
// MockScreenshotRenderer_RenderScreenshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RenderScreenshot'
|
||||
type MockScreenshotRenderer_RenderScreenshot_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// RenderScreenshot is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - repo v0alpha1.ResourceRepositoryInfo
|
||||
// - path string
|
||||
// - values url.Values
|
||||
func (_e *MockScreenshotRenderer_Expecter) RenderScreenshot(ctx interface{}, repo interface{}, path interface{}, values interface{}) *MockScreenshotRenderer_RenderScreenshot_Call { |
||||
return &MockScreenshotRenderer_RenderScreenshot_Call{Call: _e.mock.On("RenderScreenshot", ctx, repo, path, values)} |
||||
} |
||||
|
||||
func (_c *MockScreenshotRenderer_RenderScreenshot_Call) Run(run func(ctx context.Context, repo v0alpha1.ResourceRepositoryInfo, path string, values url.Values)) *MockScreenshotRenderer_RenderScreenshot_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(v0alpha1.ResourceRepositoryInfo), args[2].(string), args[3].(url.Values)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockScreenshotRenderer_RenderScreenshot_Call) Return(_a0 string, _a1 error) *MockScreenshotRenderer_RenderScreenshot_Call { |
||||
_c.Call.Return(_a0, _a1) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockScreenshotRenderer_RenderScreenshot_Call) RunAndReturn(run func(context.Context, v0alpha1.ResourceRepositoryInfo, string, url.Values) (string, error)) *MockScreenshotRenderer_RenderScreenshot_Call { |
||||
_c.Call.Return(run) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockScreenshotRenderer creates a new instance of MockScreenshotRenderer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockScreenshotRenderer(t interface { |
||||
mock.TestingT |
||||
Cleanup(func()) |
||||
}) *MockScreenshotRenderer { |
||||
mock := &MockScreenshotRenderer{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,11 @@ |
||||
Hey there! 🎉 |
||||
Grafana spotted some changes. |
||||
|
||||
| Action | Kind | Resource | Preview | |
||||
|--------|------|----------|---------| |
||||
| create | Dashboard | Dash A | [preview](http://grafana/admin/preview) | |
||||
| update | Dashboard | [Dash B](http://grafana/d/bbb) | [preview](http://grafana/admin/preview) | |
||||
| create | Playlist | My Playlist | | |
||||
|
||||
|
||||
and 5 more files. |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue