mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
Logs Panel: Permalink (copy shortlink) (#80764)
Compute permalink url for log lines within logs panels in dashboards. --------- Co-authored-by: Matias Chomicki <matyax@gmail.com>
This commit is contained in:
parent
05d858635c
commit
510dba72fc
@ -1,33 +1,42 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react';
|
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PanelProps,
|
CoreApp,
|
||||||
Field,
|
|
||||||
Labels,
|
|
||||||
GrafanaTheme2,
|
|
||||||
LogsSortOrder,
|
|
||||||
LogRowModel,
|
|
||||||
DataHoverClearEvent,
|
DataHoverClearEvent,
|
||||||
DataHoverEvent,
|
DataHoverEvent,
|
||||||
CoreApp,
|
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
LogRowContextOptions,
|
Field,
|
||||||
|
GrafanaTheme2,
|
||||||
hasLogsContextSupport,
|
hasLogsContextSupport,
|
||||||
|
Labels,
|
||||||
|
LogRowContextOptions,
|
||||||
|
LogRowModel,
|
||||||
|
LogsSortOrder,
|
||||||
|
PanelProps,
|
||||||
|
TimeRange,
|
||||||
|
toUtc,
|
||||||
|
urlUtil,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { CustomScrollbar, useStyles2, usePanelContext } from '@grafana/ui';
|
import { CustomScrollbar, usePanelContext, useStyles2 } from '@grafana/ui';
|
||||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||||
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
||||||
import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView';
|
import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView';
|
||||||
|
|
||||||
|
import { createAndCopyShortLink } from '../../../core/utils/shortLinks';
|
||||||
import { LogLabels } from '../../../features/logs/components/LogLabels';
|
import { LogLabels } from '../../../features/logs/components/LogLabels';
|
||||||
import { LogRows } from '../../../features/logs/components/LogRows';
|
import { LogRows } from '../../../features/logs/components/LogRows';
|
||||||
import { dataFrameToLogsModel, dedupLogRows, COMMON_LABELS } from '../../../features/logs/logsModel';
|
import { COMMON_LABELS, dataFrameToLogsModel, dedupLogRows } from '../../../features/logs/logsModel';
|
||||||
|
|
||||||
import { Options } from './types';
|
import { Options } from './types';
|
||||||
import { useDatasourcesFromTargets } from './useDatasourcesFromTargets';
|
import { useDatasourcesFromTargets } from './useDatasourcesFromTargets';
|
||||||
|
|
||||||
interface LogsPanelProps extends PanelProps<Options> {}
|
interface LogsPanelProps extends PanelProps<Options> {}
|
||||||
|
interface LogsPermalinkUrlState {
|
||||||
|
logs?: {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const LogsPanel = ({
|
export const LogsPanel = ({
|
||||||
data,
|
data,
|
||||||
@ -52,7 +61,7 @@ export const LogsPanel = ({
|
|||||||
const logsContainerRef = useRef<HTMLDivElement>(null);
|
const logsContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const [contextRow, setContextRow] = useState<LogRowModel | null>(null);
|
const [contextRow, setContextRow] = useState<LogRowModel | null>(null);
|
||||||
const [closeCallback, setCloseCallback] = useState<(() => void) | null>(null);
|
const [closeCallback, setCloseCallback] = useState<(() => void) | null>(null);
|
||||||
|
const timeRange = data.timeRange;
|
||||||
const dataSourcesMap = useDatasourcesFromTargets(data.request?.targets);
|
const dataSourcesMap = useDatasourcesFromTargets(data.request?.targets);
|
||||||
|
|
||||||
const { eventBus } = usePanelContext();
|
const { eventBus } = usePanelContext();
|
||||||
@ -85,6 +94,13 @@ export const LogsPanel = ({
|
|||||||
setCloseCallback(onClose);
|
setCloseCallback(onClose);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onPermalinkClick = useCallback(
|
||||||
|
async (row: LogRowModel) => {
|
||||||
|
return await copyDashboardUrl(row, timeRange);
|
||||||
|
},
|
||||||
|
[timeRange]
|
||||||
|
);
|
||||||
|
|
||||||
const showContextToggle = useCallback(
|
const showContextToggle = useCallback(
|
||||||
(row: LogRowModel): boolean => {
|
(row: LogRowModel): boolean => {
|
||||||
if (
|
if (
|
||||||
@ -104,6 +120,14 @@ export const LogsPanel = ({
|
|||||||
[dataSourcesMap, showLogContextToggle, data.request?.app]
|
[dataSourcesMap, showLogContextToggle, data.request?.app]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showPermaLink = useCallback(() => {
|
||||||
|
return !(
|
||||||
|
data.request?.app !== CoreApp.Dashboard &&
|
||||||
|
data.request?.app !== CoreApp.PanelEditor &&
|
||||||
|
data.request?.app !== CoreApp.PanelViewer
|
||||||
|
);
|
||||||
|
}, [data.request?.app]);
|
||||||
|
|
||||||
const getLogRowContext = useCallback(
|
const getLogRowContext = useCallback(
|
||||||
async (row: LogRowModel, origRow: LogRowModel, options: LogRowContextOptions): Promise<DataQueryResponse> => {
|
async (row: LogRowModel, origRow: LogRowModel, options: LogRowContextOptions): Promise<DataQueryResponse> => {
|
||||||
if (!origRow.dataFrame.refId || !dataSourcesMap) {
|
if (!origRow.dataFrame.refId || !dataSourcesMap) {
|
||||||
@ -151,6 +175,13 @@ export const LogsPanel = ({
|
|||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls the given row into view.
|
||||||
|
*/
|
||||||
|
const scrollIntoView = useCallback((row: HTMLElement) => {
|
||||||
|
row.scrollIntoView(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!data || logRows.length === 0) {
|
if (!data || logRows.length === 0) {
|
||||||
return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
|
return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
|
||||||
}
|
}
|
||||||
@ -178,6 +209,10 @@ export const LogsPanel = ({
|
|||||||
<div className={style.container} ref={logsContainerRef}>
|
<div className={style.container} ref={logsContainerRef}>
|
||||||
{showCommonLabels && !isAscending && renderCommonLabels()}
|
{showCommonLabels && !isAscending && renderCommonLabels()}
|
||||||
<LogRows
|
<LogRows
|
||||||
|
containerRendered={logsContainerRef.current !== null}
|
||||||
|
scrollIntoView={scrollIntoView}
|
||||||
|
permalinkedRowId={getLogsPanelState()?.logs?.id ?? undefined}
|
||||||
|
onPermalinkClick={showPermaLink() ? onPermalinkClick : undefined}
|
||||||
logRows={logRows}
|
logRows={logRows}
|
||||||
showContextToggle={showContextToggle}
|
showContextToggle={showContextToggle}
|
||||||
deduplicatedRows={deduplicatedRows}
|
deduplicatedRows={deduplicatedRows}
|
||||||
@ -220,3 +255,49 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getLogsPanelState(): LogsPermalinkUrlState | undefined {
|
||||||
|
const urlParams = urlUtil.getUrlSearchParams();
|
||||||
|
const panelStateEncoded = urlParams?.panelState;
|
||||||
|
if (
|
||||||
|
panelStateEncoded &&
|
||||||
|
Array.isArray(panelStateEncoded) &&
|
||||||
|
panelStateEncoded?.length > 0 &&
|
||||||
|
typeof panelStateEncoded[0] === 'string'
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(panelStateEncoded[0]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('error parsing logsPanelState', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyDashboardUrl(row: LogRowModel, timeRange: TimeRange) {
|
||||||
|
// this is an extra check, to be sure that we are not
|
||||||
|
// creating permalinks for logs without an id-field.
|
||||||
|
// normally it should never happen, because we do not
|
||||||
|
// display the permalink button in such cases.
|
||||||
|
if (row.rowId === undefined || !row.dataFrame.refId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get panel state, add log-row-id
|
||||||
|
const panelState = {
|
||||||
|
logs: { id: row.uid },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Grab the current dashboard URL
|
||||||
|
const currentURL = new URL(window.location.href);
|
||||||
|
|
||||||
|
// Add panel state containing the rowId, and absolute time range from the current query, but leave everything else the same, if the user is in edit mode when grabbing the link, that's what will be linked to, etc.
|
||||||
|
currentURL.searchParams.set('panelState', JSON.stringify(panelState));
|
||||||
|
currentURL.searchParams.set('from', toUtc(timeRange.from).valueOf().toString(10));
|
||||||
|
currentURL.searchParams.set('to', toUtc(timeRange.to).valueOf().toString(10));
|
||||||
|
|
||||||
|
await createAndCopyShortLink(currentURL.toString());
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user