Search: pass the 'explain' flag from the UI and debug paging issues (#51847)

This commit is contained in:
Ryan McKinley 2022-07-08 12:28:21 -07:00 committed by GitHub
parent 25edee88a7
commit eb6d6d0d2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 135 additions and 19 deletions

View File

@ -429,7 +429,8 @@ func doSearchQuery(
hasConstraints = true
}
if q.Query == "*" || q.Query == "" {
isMatchAllQuery := q.Query == "*" || q.Query == ""
if isMatchAllQuery {
if !hasConstraints {
fullQuery.AddShould(bluge.NewMatchAllQuery())
}
@ -600,7 +601,11 @@ func doSearchQuery(
}
if q.Explain {
fScore.Append(match.Score)
if isMatchAllQuery {
fScore.Append(float64(fieldLen + q.From))
} else {
fScore.Append(match.Score)
}
if match.Explanation != nil {
js, _ := json.Marshal(&match.Explanation)
jsb := json.RawMessage(js)

View File

@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { DataFrame } from '@grafana/data';
import { CodeEditor, Modal, ModalTabsHeader, TabContent } from '@grafana/ui';
import { DataHoverView } from 'app/plugins/panel/geomap/components/DataHoverView';
export interface Props {
name: string;
explain: {};
frame: DataFrame;
row: number;
}
const tabs = [
{ label: 'Score', value: 'score' },
{ label: 'Fields', value: 'fields' },
];
export function ExplainScorePopup({ name, explain, frame, row }: Props) {
const [isOpen, setOpen] = useState<boolean>(true);
const [activeTab, setActiveTab] = useState('score');
const modalHeader = (
<ModalTabsHeader
title={name}
icon={'info'}
tabs={tabs}
activeTab={activeTab}
onChangeTab={(t) => {
setActiveTab(t.value);
}}
/>
);
return (
<Modal title={modalHeader} isOpen={isOpen} onDismiss={() => setOpen(false)} closeOnBackdropClick closeOnEscape>
<TabContent>
{activeTab === tabs[0].value && (
<CodeEditor
width="100%"
height="70vh"
language="json"
showLineNumbers={false}
showMiniMap={true}
value={JSON.stringify(explain, null, 2)}
readOnly={false}
/>
)}
{activeTab === tabs[1].value && (
<div>
<DataHoverView data={frame} rowIndex={row} />
</div>
)}
</TabContent>
</Modal>
);
}

View File

@ -282,6 +282,11 @@ const getColumnStyles = (theme: GrafanaTheme2) => {
text-align: right;
padding: ${theme.spacing(1)} ${theme.spacing(3)} ${theme.spacing(1)} ${theme.spacing(1)};
`,
explainItem: css`
text-align: right;
padding: ${theme.spacing(1)} ${theme.spacing(3)} ${theme.spacing(1)} ${theme.spacing(1)};
cursor: pointer;
`,
locationCellStyle: css`
padding-top: ${theme.spacing(1)};
padding-right: ${theme.spacing(1)};

View File

@ -64,6 +64,7 @@ export const SearchView = ({
ds_uid: query.datasource as string,
location: folderDTO?.uid, // This will scope all results to the prefix
sort: query.sort?.value,
explain: query.explain,
};
// Only dashboards have additional properties

View File

@ -5,11 +5,14 @@ import SVG from 'react-inlinesvg';
import { Field, FieldType, formattedValueToString, getDisplayProcessor, getFieldDisplayName } from '@grafana/data';
import { config, getDataSourceSrv } from '@grafana/runtime';
import { Checkbox, Icon, IconButton, IconName, TagList } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { PluginIconName } from 'app/features/plugins/admin/types';
import { ShowModalReactEvent } from 'app/types/events';
import { QueryResponse, SearchResultMeta } from '../../service';
import { SelectionChecker, SelectionToggle } from '../selection';
import { ExplainScorePopup } from './ExplainScorePopup';
import { TableColumn } from './SearchResultsTable';
const TYPE_COLUMN_WIDTH = 175;
@ -40,6 +43,10 @@ export const generateColumns = (
availableWidth -= sortFieldWith; // pre-allocate the space for the last column
}
if (access.explain && access.score) {
availableWidth -= 100; // pre-allocate the space for the last column
}
let width = 50;
if (selection && selectionToggle) {
width = 30;
@ -107,7 +114,8 @@ export const generateColumns = (
let classNames = cx(styles.nameCellStyle);
let name = access.name.values.get(p.row.index);
if (!name?.length) {
name = 'Missing title'; // normal for panels
const loading = p.row.index >= response.view.dataFrame.length;
name = loading ? 'Loading...' : 'Missing title'; // normal for panels
classNames += ' ' + styles.missingTitleText;
}
return (
@ -196,6 +204,37 @@ export const generateColumns = (
});
}
if (access.explain && access.score) {
const vals = access.score.values;
const showExplainPopup = (row: number) => {
appEvents.publish(
new ShowModalReactEvent({
component: ExplainScorePopup,
props: {
name: access.name.values.get(row),
explain: access.explain.values.get(row),
frame: response.view.dataFrame,
row: row,
},
})
);
};
columns.push({
Header: () => <div className={styles.sortedHeader}>Score</div>,
Cell: (p) => {
return (
<div {...p.cellProps} className={styles.explainItem} onClick={() => showExplainPopup(p.row.index)}>
{vals.get(p.row.index)}
</div>
);
},
id: `column-score-field`,
field: access.score,
width: 100,
});
}
return columns;
};

View File

@ -17,9 +17,6 @@ export const defaultQuery: DashboardQuery = {
query: '',
tag: [],
starred: false,
skipRecent: false,
skipStarred: false,
folderIds: [],
sort: null,
layout: SearchLayout.Folders,
prevSort: null,

View File

@ -121,15 +121,12 @@ async function doSearchQuery(query: SearchQuery): Promise<QueryResponse> {
}
}
const view = new DataFrameView<DashboardQueryResult>(first);
return {
totalRows: meta.count ?? first.length,
view,
loadMoreItems: async (startIndex: number, stopIndex: number): Promise<void> => {
console.log('LOAD NEXT PAGE', { startIndex, stopIndex, length: view.dataFrame.length });
let loadMax = 0;
let pending: Promise<void> | undefined = undefined;
const getNextPage = async () => {
while (loadMax > view.dataFrame.length) {
const from = view.dataFrame.length;
const limit = stopIndex - from;
if (limit < 0) {
if (from >= meta.count) {
return;
}
const frame = (
@ -141,7 +138,7 @@ async function doSearchQuery(query: SearchQuery): Promise<QueryResponse> {
search: {
...(target?.search ?? {}),
from,
limit: Math.max(limit, nextPageSizes),
limit: nextPageSizes,
},
refId: 'Page',
facet: undefined,
@ -175,7 +172,20 @@ async function doSearchQuery(query: SearchQuery): Promise<QueryResponse> {
meta.locationInfo[key] = value;
}
}
return;
}
pending = undefined;
};
const view = new DataFrameView<DashboardQueryResult>(first);
return {
totalRows: meta.count ?? first.length,
view,
loadMoreItems: async (startIndex: number, stopIndex: number): Promise<void> => {
loadMax = Math.max(loadMax, stopIndex);
if (!pending) {
pending = getNextPage();
}
return pending;
},
isItemLoaded: (index: number): boolean => {
return index < view.dataFrame.length;

View File

@ -33,6 +33,10 @@ export interface DashboardQueryResult {
tags: string[];
location: string; // url that can be split
ds_uid: string[];
// debugging fields
score: number;
explain: {};
}
export interface LocationInfo {

View File

@ -61,9 +61,7 @@ export interface DashboardQuery {
query: string;
tag: string[];
starred: boolean;
skipRecent: boolean;
skipStarred: boolean;
folderIds: number[];
explain?: boolean; // adds debug info
datasource?: string;
sort: SelectableValue | null;
// Save sorting data between layouts