Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
89636b0
added field for url and necessary variables
Edaurd Feb 26, 2024
48e7d94
ability to save has been restored
Edaurd Feb 27, 2024
417d6cd
log renderlegendformat
Edaurd Feb 27, 2024
1d47bce
added MD Url as label
Edaurd Mar 7, 2024
c3a831c
Revert "added field for url and necessary variables"
Edaurd Mar 8, 2024
e9571ca
new url field and ability to save it
Edaurd Mar 12, 2024
5409b7e
Introduce temporary constant for URL field in backend
Edaurd Mar 25, 2024
7537ed1
Implement clickable labels in frontend using backend URLs
Edaurd Mar 25, 2024
b5275e2
enable dynamic datasource URL configuration via UI
Edaurd Mar 25, 2024
726333a
Refactored duplicate code into a unified helper function for improved…
Edaurd Mar 29, 2024
8dbecd9
Implement backend support for Loki drilldown link
Edaurd Apr 4, 2024
a868c28
Refactored duplicate code into a unified helper function
Edaurd Apr 4, 2024
4783569
Implement front end support for Loki drilldown link
Edaurd Apr 5, 2024
af2f52a
Collapsed info url now cuts of with ... if too long in the dashboard
Edaurd Apr 5, 2024
e8e73d4
Renamed Legend URL to URL for consistency in collapsed info
Edaurd Apr 5, 2024
e773494
Refactored the "..." for backwards compatability
Edaurd Apr 8, 2024
a94430f
Introduced tests for cutting off the url
Edaurd Apr 8, 2024
335556e
added field for url and necessary variables
Edaurd Feb 26, 2024
cf47b2f
ability to save has been restored
Edaurd Feb 27, 2024
5009754
log renderlegendformat
Edaurd Feb 27, 2024
f69e45d
added MD Url as label
Edaurd Mar 7, 2024
619679f
Revert "added field for url and necessary variables"
Edaurd Mar 8, 2024
da30d6d
new url field and ability to save it
Edaurd Mar 12, 2024
00aaabd
Introduce temporary constant for URL field in backend
Edaurd Mar 25, 2024
2cc3b48
Implement clickable labels in frontend using backend URLs
Edaurd Mar 25, 2024
a110d22
enable dynamic datasource URL configuration via UI
Edaurd Mar 25, 2024
d853f3c
Refactored duplicate code into a unified helper function for improved…
Edaurd Mar 29, 2024
c788843
Implement backend support for Loki drilldown link
Edaurd Apr 4, 2024
f5588d4
Refactored duplicate code into a unified helper function
Edaurd Apr 4, 2024
1dae7ae
Implement front end support for Loki drilldown link
Edaurd Apr 5, 2024
6507d3b
Collapsed info url now cuts of with ... if too long in the dashboard
Edaurd Apr 5, 2024
e3674c1
Renamed Legend URL to URL for consistency in collapsed info
Edaurd Apr 5, 2024
6415842
Refactored the "..." for backwards compatability
Edaurd Apr 8, 2024
26fc104
Introduced tests for cutting off the url
Edaurd Apr 8, 2024
c87f46b
Solved issue after rebase
Edaurd Apr 8, 2024
a21c77c
Refactored test to be more robust
Edaurd Apr 15, 2024
7cd4ded
Added comment to explain legendUrl
Edaurd Apr 15, 2024
8567e71
Renamed legendUrlFormat to legendUrl for clarity
Edaurd Apr 15, 2024
a7b9056
Refactored the tooltip & placeholders, continued rename to legendUrl
Edaurd Apr 15, 2024
b02ecf4
Merge branch 'DrilldownURL' of gitlab.com:essensium-mind/fleet-minder…
Edaurd Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/grafana-data/src/types/dataFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export interface FieldConfig<TOptions = any> {
*/
displayNameFromDS?: string;

/**
* This is the URL that the field value links to. This supports template variables.
*/
urlFromDS?: string;

/**
* Human readable field metadata
*/
Expand Down
1 change: 1 addition & 0 deletions packages/grafana-e2e-selectors/src/selectors/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const Components = {
editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle
options: 'data-testid prometheus options', // wrapper for options group
legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents
legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents
format: 'data-testid prometheus format',
step: 'prometheus-step', // id for autosize component
type: 'data-testid prometheus type', //wrapper for radio button group
Expand Down
5 changes: 5 additions & 0 deletions packages/grafana-prometheus/src/dataquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface Prometheus extends common.DataQuery {
* Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname
*/
legendFormat?: string;
legendUrlFormat?: string;
/**
* Drilldown URL for LegendFormat with template support. Ex. https://example.com/{{instance}} will replace the variable with its value
*/
legendUrl?: string;
/**
* Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export const VizLegendListItem = <T = unknown,>({
const onClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (onLabelClick) {
onLabelClick(item, event);
if( item.url != null) {
window.location.href = item.url;
} else { onLabelClick(item, event); }
}
},
[item, onLabelClick]
Expand Down
1 change: 1 addition & 0 deletions packages/grafana-ui/src/components/VizLegend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface LegendProps<T = any> extends VizLegendBaseProps<T>, VizLegendTa
export interface VizLegendItem<T = any> {
getItemKey?: () => string;
label: string;
url?: string;
color?: string;
gradient?: string;
yAxis: number;
Expand Down
2 changes: 2 additions & 0 deletions packages/grafana-ui/src/components/uPlot/PlotLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const PlotLegend = React.memo(
}

const label = getFieldDisplayName(field, data[fieldIndex.frameIndex]!, data);
const url = field.config?.urlFromDS;
const scaleColor = getFieldSeriesColor(field, theme);
const seriesColor = scaleColor.color;

Expand All @@ -79,6 +80,7 @@ export const PlotLegend = React.memo(
fieldIndex,
color: seriesColor,
label,
url,
yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2,
getDisplayValues: () => {
if (!calcs?.length) {
Expand Down
5 changes: 5 additions & 0 deletions pkg/promlib/models/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ type PrometheusQueryProperties struct {
// Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname
LegendFormat string `json:"legendFormat,omitempty"`

// Drilldown URL for LegendFormat with template support. Ex. https://example.com/{{instance}} will replace the variable with its value
LegendUrl string `json:"legendUrl,omitempty"`

// ???
Scope *ScopeSpec `json:"scope,omitempty"`
}
Expand Down Expand Up @@ -152,6 +155,7 @@ type Query struct {
Expr string
Step time.Duration
LegendFormat string
LegendUrl string
Start time.Time
End time.Time
RefId string
Expand Down Expand Up @@ -208,6 +212,7 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator
Expr: expr,
Step: calculatedStep,
LegendFormat: model.LegendFormat,
LegendUrl: model.LegendUrl,
Start: query.TimeRange.From,
End: query.TimeRange.To,
RefId: query.RefID,
Expand Down
38 changes: 26 additions & 12 deletions pkg/promlib/querydata/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,11 @@ func addMetadataToMultiFrame(q *models.Query, frame *data.Frame, enableDataplane
frame.Fields[0].Config = &data.FieldConfig{Interval: float64(q.Step.Milliseconds())}

customName := getName(q, frame.Fields[1])
labelUrl := getUrl(q, frame.Fields[1])

if customName != "" {
frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName}
frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName, UrlFromDS: labelUrl}
}

if enableDataplane {
valueField := frame.Fields[1]
if n, ok := valueField.Labels["__name__"]; ok {
Expand Down Expand Up @@ -169,8 +170,27 @@ func getName(q *models.Query, field *data.Field) string {
if len(labels) > 0 {
legend = ""
}
} else if q.LegendFormat != "" {
result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendFormat), func(in []byte) []byte {
} else {
legend = convertLabel(q.LegendFormat, field)
}

// If legend is empty brackets, use query expression
if legend == "{}" {
return q.Expr
}

return legend
}

func getUrl(q *models.Query, field *data.Field) string {
return convertLabel(q.LegendUrl, field)
}

func convertLabel(label string, field *data.Field) string {
var convertedLabel string
labels := field.Labels
if label != "" {
result := legendFormatRegexp.ReplaceAllFunc([]byte(label), func(in []byte) []byte {
labelName := strings.Replace(string(in), "{{", "", 1)
labelName = strings.Replace(labelName, "}}", "", 1)
labelName = strings.TrimSpace(labelName)
Expand All @@ -179,15 +199,9 @@ func getName(q *models.Query, field *data.Field) string {
}
return []byte{}
})
legend = string(result)
convertedLabel = string(result)
}

// If legend is empty brackets, use query expression
if legend == "{}" {
return q.Expr
}

return legend
return convertedLabel
}

func isExemplarFrame(frame *data.Frame) bool {
Expand Down
33 changes: 23 additions & 10 deletions pkg/tsdb/loki/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func adjustMetricFrame(frame *data.Frame, query *lokiQuery, setFrameName bool) e
isMetricRange := query.QueryType == QueryTypeRange

name := formatName(labels, query)
url := formatUrl(labels, query)
if setFrameName {
frame.Name = name
}
Expand Down Expand Up @@ -78,6 +79,7 @@ func adjustMetricFrame(frame *data.Frame, query *lokiQuery, setFrameName bool) e
valueField.Config = &data.FieldConfig{}
}
valueField.Config.DisplayNameFromDS = name
valueField.Config.UrlFromDS = url

return nil
}
Expand Down Expand Up @@ -283,17 +285,28 @@ func formatName(labels map[string]string, query *lokiQuery) string {
return formatNamePrometheusStyle(labels)
}

result := legendFormat.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte {
labelName := strings.Replace(string(in), "{{", "", 1)
labelName = strings.Replace(labelName, "}}", "", 1)
labelName = strings.TrimSpace(labelName)
if val, exists := labels[labelName]; exists {
return []byte(val)
}
return []byte{}
})
return convertLabel(query.LegendFormat, labels)
}

func formatUrl(labels map[string]string, query *lokiQuery) string {
return convertLabel(query.LegendUrl, labels)
}

return string(result)
func convertLabel(label string, labels map[string]string) string {
var convertedLabel string
if label != "" {
result := legendFormat.ReplaceAllFunc([]byte(label), func(in []byte) []byte {
labelName := strings.Replace(string(in), "{{", "", 1)
labelName = strings.Replace(labelName, "}}", "", 1)
labelName = strings.TrimSpace(labelName)
if val, exists := labels[labelName]; exists {
return []byte(val)
}
return []byte{}
})
convertedLabel = string(result)
}
return convertedLabel
}

func getFrameLabels(frame *data.Frame) map[string]string {
Expand Down
3 changes: 3 additions & 0 deletions pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/tsdb/loki/parse_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) {
if model.LegendFormat != nil {
legendFormat = *model.LegendFormat
}
var legendUrl string
if model.LegendUrl != nil {
legendUrl = *model.LegendUrl
}

supportingQueryType := parseSupportingQueryType(model.SupportingQueryType)

Expand All @@ -178,6 +182,7 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) {
Step: step,
MaxLines: int(maxLines),
LegendFormat: legendFormat,
LegendUrl: legendUrl,
Start: start,
End: end,
RefID: query.RefID,
Expand Down
1 change: 1 addition & 0 deletions pkg/tsdb/loki/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type lokiQuery struct {
Step time.Duration
MaxLines int
LegendFormat string
LegendUrl string
Start time.Time
End time.Time
RefID string
Expand Down
4 changes: 4 additions & 0 deletions public/app/plugins/datasource/loki/dataquery.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export interface LokiDataQuery extends common.DataQuery {
* Used to override the name of the series.
*/
legendFormat?: string;
/**
* Used for drilldown/clickable legend labels.
*/
legendUrl?: string;
/**
* Used to limit the number of log rows returned.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('LokiQueryBuilderOptions', () => {

await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// Second autosize input is a Line limit
const element = screen.getAllByTestId('autosize-input')[1];
const element = screen.getAllByTestId('autosize-input')[2];
await userEvent.type(element, '10');
await userEvent.keyboard('{enter}');

Expand All @@ -59,7 +59,7 @@ describe('LokiQueryBuilderOptions', () => {

await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// Second autosize input is a Line limit
const element = screen.getAllByTestId('autosize-input')[1];
const element = screen.getAllByTestId('autosize-input')[2];
await userEvent.type(element, '-10');
await userEvent.keyboard('{enter}');

Expand All @@ -76,7 +76,7 @@ describe('LokiQueryBuilderOptions', () => {

await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// Second autosize input is a Line limit
const element = screen.getAllByTestId('autosize-input')[1];
const element = screen.getAllByTestId('autosize-input')[2];
await userEvent.type(element, 'asd');
await userEvent.keyboard('{enter}');

Expand Down Expand Up @@ -154,6 +154,32 @@ describe('LokiQueryBuilderOptions', () => {
});
});

describe('getCollapsedInfo', () => {
it('displays a clipped legend URL for long URLs', () => {
const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate';
setup({ legendUrl: longUrl });

const urlText = screen.getByText((content, node) => {
const hasText = (node: any) => node.textContent === `URL: extremelyL...`;
const nodeHasText = hasText(node);
const childrenDontHaveText = Array.from(node!.children).every(
(child) => !hasText(child)
);
return nodeHasText && childrenDontHaveText;
});
expect(urlText).toBeInTheDocument();
});

it('displays the full legend URL for short URLs', () => {
const shortUrl = 'shortUrl';
setup({ legendUrl: shortUrl });

const urlTextElement = screen.getByText(`URL: ${shortUrl}`);
expect(urlTextElement).toBeInTheDocument();
});
});


function setup(queryOverrides: Partial<LokiQuery> = {}) {
const props = {
query: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
onChange({ ...query, legendFormat: evt.currentTarget.value });
onRunQuery();
};
const onLegendUrlChanged = (evt: React.FormEvent<HTMLInputElement>) => {
onChange({ ...query, legendUrl: evt.currentTarget.value });
onRunQuery();
};

function onMaxLinesChange(e: React.SyntheticEvent<HTMLInputElement>) {
const newMaxLines = preprocessMaxLines(e.currentTarget.value);
Expand Down Expand Up @@ -95,6 +99,18 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
onCommitChange={onLegendFormatChanged}
/>
</EditorField>
<EditorField
label="Legend URL"
tooltip="If the url is set, the label in the legend will become clickable, you can use variables similar to Legend Label."
>
<AutoSizeInput
placeholder="/your/{{example}}/url"
type="string"
minWidth={14}
defaultValue={query.legendUrl}
onCommitChange={onLegendUrlChanged}
/>
</EditorField>
<EditorField label="Type">
<RadioButtonGroup options={queryTypeOptions} value={queryType} onChange={onQueryTypeChange} />
</EditorField>
Expand Down Expand Up @@ -185,6 +201,16 @@ function getCollapsedInfo(
items.push(`Legend: ${query.legendFormat}`);
}

if (query.legendUrl) {
let legendUrl = query.legendUrl;

if (typeof legendUrl === 'string' && legendUrl.length > 10) {
legendUrl = legendUrl.slice(0, 10) + "...";
}

items.push(`URL: ${legendUrl?.length === 0 ? "None" : legendUrl}`);
}

items.push(`Type: ${queryTypeLabel?.label}`);

if (isLogQuery) {
Expand Down
4 changes: 4 additions & 0 deletions public/app/plugins/datasource/prometheus/dataquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export interface Prometheus extends common.DataQuery {
* Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname
*/
legendFormat?: string;
/**
* Drilldown URL for LegendFormat with template support. Ex. https://example.com/{{instance}} will replace the variable with its value
*/
legendUrl?: string;
/**
* Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series
*/
Expand Down
Loading