logs: restrict permalinks to datasources with id fields (#71729)

* logs: restrict permalinks to datasources with id fields

* simplified code

* more tests

* more test

* removed unnecessary comment

* fixed tests

* updated tests
This commit is contained in:
Gábor Farkas 2023-07-17 15:01:48 +02:00 committed by GitHub
parent ab58466d09
commit d0351bac57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 192 additions and 9 deletions

View File

@ -55,6 +55,9 @@ export interface LogRowModel {
// the same as rows final index when rendered.
rowIndex: number;
// The value of the the dataframe's id field, if it exists
rowId?: string;
// Full DataFrame from which we parsed this log.
// TODO: refactor this so we do not need to pass whole dataframes in addition to also parsed data.
dataFrame: DataFrame;

View File

@ -97,9 +97,9 @@ describe('Logs', () => {
const getComponent = (partialProps?: Partial<ComponentProps<typeof Logs>>, logs?: LogRowModel[]) => {
const rows = [
makeLog({ uid: '1', timeEpochMs: 1 }),
makeLog({ uid: '2', timeEpochMs: 2 }),
makeLog({ uid: '3', timeEpochMs: 3 }),
makeLog({ uid: '1', rowId: 'id1', timeEpochMs: 1 }),
makeLog({ uid: '2', rowId: 'id2', timeEpochMs: 2 }),
makeLog({ uid: '3', rowId: 'id3', timeEpochMs: 3 }),
];
const testDataFrame = {

View File

@ -374,6 +374,14 @@ class UnthemedLogs extends PureComponent<Props, State> {
};
onPermalinkClick = async (row: LogRowModel) => {
// 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) {
return;
}
// get explore state, add log-row-id and make timerange absolute
const urlState = getUrlStateFromPaneState(getState().explore.panes[this.props.exploreId]!);
urlState.panelsState = { ...this.props.panelState, logs: { id: row.uid } };

View File

@ -102,7 +102,7 @@ export const LogRowMenuCell = React.memo(
aria-label="Pin line"
/>
)}
{onPermalinkClick && row.uid && (
{onPermalinkClick && row.rowId !== undefined && row.uid && (
<IconButton
tooltip="Copy shortlink"
aria-label="Copy shortlink"

View File

@ -66,12 +66,18 @@ describe('LogRowMessage', () => {
});
describe('with permalinking', () => {
it('should show permalinking button when `onPermalinkClick` is defined', async () => {
setup({ onPermalinkClick: jest.fn() });
it('should show permalinking button when `onPermalinkClick` is defined and rowId is defined', async () => {
setup({ onPermalinkClick: jest.fn() }, { rowId: 'id1' });
await userEvent.hover(screen.getByText('test123'));
expect(screen.queryByLabelText('Copy shortlink')).toBeInTheDocument();
});
it('should not show permalinking button when `onPermalinkClick` is defined and rowId is not defined', async () => {
setup({ onPermalinkClick: jest.fn() });
await userEvent.hover(screen.getByText('test123'));
expect(screen.queryByLabelText('Copy shortlink')).not.toBeInTheDocument();
});
it('should not show permalinking button when `onPermalinkClick` is not defined', () => {
setup();
expect(screen.queryByLabelText('Copy shortlink')).not.toBeInTheDocument();
@ -79,7 +85,7 @@ describe('LogRowMessage', () => {
it('should call `onPermalinkClick` with row on click', async () => {
const permalinkClick = jest.fn();
const props = setup({ onPermalinkClick: permalinkClick });
const props = setup({ onPermalinkClick: permalinkClick }, { rowId: 'id1' });
await userEvent.hover(screen.getByText('test123'));
const button = screen.getByLabelText('Copy shortlink');

View File

@ -424,7 +424,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[], queries: DataQuery[
const datasourceType = queries.find((query) => query.refId === series.refId)?.datasource?.type;
rows.push({
const row: LogRowModel = {
entryFieldIndex: stringField.index,
rowIndex: j,
dataFrame: series,
@ -444,7 +444,13 @@ export function logSeriesToLogsModel(logSeries: DataFrame[], queries: DataQuery[
// prepend refId to uid to make it unique across all series in a case when series contain duplicates
uid: `${series.refId}_${idField ? idField.values[j] : j.toString()}`,
datasourceType,
});
};
if (idField !== null) {
row.rowId = idField.values[j];
}
rows.push(row);
}
}

View File

@ -137,6 +137,7 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
rows: [
{
dataFrame: frames[0],
rowId: 'id1',
datasourceType: undefined,
entry: 'line1',
entryFieldIndex: 1,
@ -164,6 +165,7 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
},
{
dataFrame: frames[1],
rowId: 'id2',
datasourceType: undefined,
entry: 'line2',
entryFieldIndex: 1,
@ -191,6 +193,7 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
},
{
dataFrame: frames[2],
rowId: 'id3',
datasourceType: undefined,
entry: 'line3',
entryFieldIndex: 1,
@ -290,6 +293,7 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
rows: [
{
dataFrame: frames[0],
rowId: 'id1',
datasourceType: undefined,
entry: 'line1',
entryFieldIndex: 2,
@ -318,6 +322,7 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
},
{
dataFrame: frames[0],
rowId: 'id2',
datasourceType: undefined,
entry: 'line2',
entryFieldIndex: 2,
@ -346,6 +351,7 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
},
{
dataFrame: frames[0],
rowId: 'id3',
datasourceType: undefined,
entry: 'line3',
entryFieldIndex: 2,
@ -925,4 +931,158 @@ describe('logSeriesToLogsModel should parse different logs-dataframe formats', (
expect(logSeriesToLogsModel(frames)).toStrictEqual(expected);
});
it('should add rowId when id field exists)', () => {
const frames: DataFrame[] = [
{
refId: 'A',
fields: [
{
name: 'Time',
type: FieldType.time,
config: {},
values: [1686142519756, 1686142520411],
nanos: [641000, 0],
},
{
name: 'Line',
type: FieldType.string,
config: {},
values: ['line1', 'line2'],
},
{
name: 'id',
type: FieldType.string,
config: {},
values: ['id1', 'id2'],
},
],
length: 2,
},
];
const expected = {
hasUniqueLabels: false,
meta: [],
rows: [
{
dataFrame: frames[0],
rowId: 'id1',
datasourceType: undefined,
entry: 'line1',
entryFieldIndex: 1,
hasAnsi: false,
hasUnescapedContent: false,
labels: {},
logLevel: 'unknown',
raw: 'line1',
rowIndex: 0,
searchWords: [],
timeEpochMs: 1686142519756,
timeEpochNs: '1686142519756641000',
timeFromNow: 'mock:dateTimeFormatTimeAgo:2023-06-07T06:55:19-06:00',
timeLocal: '2023-06-07 06:55:19',
timeUtc: '2023-06-07 12:55:19',
uid: 'A_id1',
uniqueLabels: {},
},
{
dataFrame: frames[0],
rowId: 'id2',
datasourceType: undefined,
entry: 'line2',
entryFieldIndex: 1,
hasAnsi: false,
hasUnescapedContent: false,
labels: {},
logLevel: 'unknown',
raw: 'line2',
rowIndex: 1,
searchWords: [],
timeEpochMs: 1686142520411,
timeEpochNs: '1686142520411000000',
timeFromNow: 'mock:dateTimeFormatTimeAgo:2023-06-07T06:55:20-06:00',
timeLocal: '2023-06-07 06:55:20',
timeUtc: '2023-06-07 12:55:20',
uid: 'A_id2',
uniqueLabels: {},
},
],
};
expect(logSeriesToLogsModel(frames)).toStrictEqual(expected);
});
it('should not add rowId when id field does not exist)', () => {
const frames: DataFrame[] = [
{
refId: 'A',
fields: [
{
name: 'Time',
type: FieldType.time,
config: {},
values: [1686142519756, 1686142520411],
nanos: [641000, 0],
},
{
name: 'Line',
type: FieldType.string,
config: {},
values: ['line1', 'line2'],
},
],
length: 2,
},
];
const expected = {
hasUniqueLabels: false,
meta: [],
rows: [
{
dataFrame: frames[0],
datasourceType: undefined,
entry: 'line1',
entryFieldIndex: 1,
hasAnsi: false,
hasUnescapedContent: false,
labels: {},
logLevel: 'unknown',
raw: 'line1',
rowIndex: 0,
searchWords: [],
timeEpochMs: 1686142519756,
timeEpochNs: '1686142519756641000',
timeFromNow: 'mock:dateTimeFormatTimeAgo:2023-06-07T06:55:19-06:00',
timeLocal: '2023-06-07 06:55:19',
timeUtc: '2023-06-07 12:55:19',
uid: 'A_0',
uniqueLabels: {},
},
{
dataFrame: frames[0],
datasourceType: undefined,
entry: 'line2',
entryFieldIndex: 1,
hasAnsi: false,
hasUnescapedContent: false,
labels: {},
logLevel: 'unknown',
raw: 'line2',
rowIndex: 1,
searchWords: [],
timeEpochMs: 1686142520411,
timeEpochNs: '1686142520411000000',
timeFromNow: 'mock:dateTimeFormatTimeAgo:2023-06-07T06:55:20-06:00',
timeLocal: '2023-06-07 06:55:20',
timeUtc: '2023-06-07 12:55:20',
uid: 'A_1',
uniqueLabels: {},
},
],
};
expect(logSeriesToLogsModel(frames)).toStrictEqual(expected);
});
});