Search: support alpha + and enterprise sorting values (#49362)

This commit is contained in:
Ryan McKinley 2022-05-23 10:05:15 -07:00 committed by GitHub
parent 653c82cec4
commit 8af587e7ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 173 additions and 70 deletions

View File

@ -25,7 +25,8 @@ const (
documentFieldTag = "tag"
documentFieldURL = "url"
documentFieldName = "name"
documentFieldNameNgram = "name_ngram"
documentFieldName_sort = "name_sort"
documentFieldName_ngram = "name_ngram"
documentFieldDescription = "description"
documentFieldLocation = "location" // parent path
documentFieldPanelType = "panel_type"
@ -144,26 +145,17 @@ func getFolderDashboardDoc(dash dashboard) *bluge.Document {
dash.info.Description = ""
}
return bluge.NewDocument(uid).
AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindFolder)).Aggregatable().StoreValue()).
AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue()).
AddField(bluge.NewTextField(documentFieldName, dash.info.Title).StoreValue().SearchTermPositions()).
AddField(bluge.NewTextField(documentFieldDescription, dash.info.Description).SearchTermPositions()).
AddField(getNameNGramField(dash.info.Title)).
AddField(bluge.NewTextField(documentFieldDescription, dash.info.Description).SearchTermPositions())
return newSearchDocument(uid, dash.info.Title, dash.info.Description, url).
AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindFolder)).Aggregatable().StoreValue())
}
func getNonFolderDashboardDoc(dash dashboard, location string) *bluge.Document {
url := fmt.Sprintf("/d/%s/%s", dash.uid, dash.slug)
// Dashboard document
doc := bluge.NewDocument(dash.uid).
doc := newSearchDocument(dash.uid, dash.info.Title, dash.info.Description, url).
AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindDashboard)).Aggregatable().StoreValue()).
AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue()).
AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue()).
AddField(bluge.NewTextField(documentFieldName, dash.info.Title).StoreValue().SearchTermPositions()).
AddField(getNameNGramField(dash.info.Title)).
AddField(bluge.NewTextField(documentFieldDescription, dash.info.Description).SearchTermPositions())
AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue())
for _, tag := range dash.info.Tags {
doc.AddField(bluge.NewKeywordField(documentFieldTag, tag).
@ -200,12 +192,8 @@ func getDashboardPanelDocs(dash dashboard, location string) []*bluge.Document {
purl = fmt.Sprintf("%s?viewPanel=%d", url, panel.ID)
}
doc := bluge.NewDocument(uid).
AddField(bluge.NewKeywordField(documentFieldURL, purl).StoreValue()).
doc := newSearchDocument(uid, panel.Title, panel.Description, purl).
AddField(bluge.NewKeywordField(documentFieldDSUID, dash.uid).StoreValue()).
AddField(bluge.NewTextField(documentFieldName, panel.Title).StoreValue().SearchTermPositions()).
AddField(getNameNGramField(panel.Title)).
AddField(bluge.NewTextField(documentFieldDescription, panel.Description).SearchTermPositions()).
AddField(bluge.NewKeywordField(documentFieldPanelType, panel.Type).Aggregatable().StoreValue()).
AddField(bluge.NewKeywordField(documentFieldLocation, location).Aggregatable().StoreValue()).
AddField(bluge.NewKeywordField(documentFieldKind, string(entityKindPanel)).Aggregatable().StoreValue()) // likely want independent index for this
@ -249,8 +237,27 @@ var ngramQueryAnalyzer = &analysis.Analyzer{
},
}
func getNameNGramField(name string) bluge.Field {
return bluge.NewTextField(documentFieldNameNgram, name).WithAnalyzer(ngramIndexAnalyzer)
// Names need to be indexed a few ways to support key features
func newSearchDocument(uid string, name string, descr string, url string) *bluge.Document {
doc := bluge.NewDocument(uid)
if name != "" {
doc.AddField(bluge.NewTextField(documentFieldName, name).StoreValue().SearchTermPositions())
doc.AddField(bluge.NewTextField(documentFieldName_ngram, name).WithAnalyzer(ngramIndexAnalyzer))
// Don't add a field for empty names
sortStr := strings.Trim(strings.ToUpper(name), " ")
if len(sortStr) > 0 {
doc.AddField(bluge.NewKeywordField(documentFieldName_sort, sortStr).Sortable())
}
}
if descr != "" {
doc.AddField(bluge.NewTextField(documentFieldDescription, descr).SearchTermPositions())
}
if url != "" {
doc.AddField(bluge.NewKeywordField(documentFieldURL, url).StoreValue())
}
return doc
}
func getDashboardPanelIDs(reader *bluge.Reader, dashboardUID string) ([]string, error) {
@ -284,6 +291,7 @@ func getDashboardPanelIDs(reader *bluge.Reader, dashboardUID string) ([]string,
//nolint: gocyclo
func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader, filter ResourceFilter, q DashboardQuery, extender QueryExtender) *backend.DataResponse {
response := &backend.DataResponse{}
header := &customMeta{}
// Folder listing structure.
idx := strings.Index(q.Query, ":")
@ -365,7 +373,7 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader,
AddShould(bluge.NewMatchPhraseQuery(q.Query).SetField(documentFieldName).SetBoost(6)).
AddShould(bluge.NewMatchPhraseQuery(q.Query).SetField(documentFieldDescription).SetBoost(3)).
AddShould(bluge.NewMatchQuery(q.Query).
SetField(documentFieldNameNgram).
SetField(documentFieldName_ngram).
SetAnalyzer(ngramQueryAnalyzer).SetBoost(1))
if len(q.Query) > 4 {
@ -388,9 +396,9 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader,
}
req.WithStandardAggregations()
// Field must be .Sortable() for sort to work with it.
if q.Sort != "" {
req.SortBy([]string{q.Sort})
header.SortBy = strings.TrimPrefix(q.Sort, "-")
}
for _, t := range q.Facet {
@ -443,6 +451,10 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader,
if q.Explain {
frame.Fields = append(frame.Fields, fScore, fExplain)
}
frame.SetMeta(&data.FrameMeta{
Type: "search-results",
Custom: header,
})
fieldLen := 0
ext := extender.GetFramer(frame)
@ -485,7 +497,7 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader,
case documentFieldTag:
tags = append(tags, string(value))
default:
return ext(field, value)
ext(field, value)
}
return true
})
@ -551,9 +563,7 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader,
// Must call after iterating :)
aggs := documentMatchIterator.Aggregations()
header := &customMeta{
Count: aggs.Count(), // Total count.
}
header.Count = aggs.Count() // Total count
if q.Explain {
header.MaxScore = aggs.Metric("max_score")
}
@ -562,11 +572,6 @@ func doSearchQuery(ctx context.Context, logger log.Logger, reader *bluge.Reader,
header.Locations = getLocationLookupInfo(ctx, reader, locationItems)
}
frame.SetMeta(&data.FrameMeta{
Type: "search-results",
Custom: header,
})
response.Frames = append(response.Frames, frame)
for _, t := range q.Facet {
@ -654,4 +659,5 @@ type customMeta struct {
Count uint64 `json:"count"`
MaxScore float64 `json:"max_score,omitempty"`
Locations map[string]locationItem `json:"locationInfo,omitempty"`
SortBy string `json:"sortBy,omitempty"`
}

View File

@ -6,7 +6,7 @@ import (
)
type ExtendDashboardFunc func(uid string, doc *bluge.Document) error
type FramerFunc func(field string, value []byte) bool
type FramerFunc func(field string, value []byte)
type QueryExtender interface {
GetFramer(frame *data.Frame) FramerFunc
@ -42,7 +42,7 @@ func (n NoopDocumentExtender) GetDashboardExtender(_ int64, _ ...string) ExtendD
type NoopQueryExtender struct{}
func (n NoopQueryExtender) GetFramer(_ *data.Frame) FramerFunc {
return func(field string, value []byte) bool {
return true
return func(field string, value []byte) {
// really noop
}
}

View File

@ -2,7 +2,6 @@ package searchV2
import (
"context"
"flag"
"path/filepath"
"testing"
@ -36,8 +35,6 @@ var testDisallowAllFilter = func(uid string) bool {
var testOrgID int64 = 1
var update = flag.Bool("update", false, "update golden files")
func initTestIndexFromDashes(t *testing.T, dashboards []dashboard) (*dashboardIndex, *bluge.Reader, *bluge.Writer) {
t.Helper()
return initTestIndexFromDashesExtended(t, dashboards, &NoopDocumentExtender{})
@ -73,7 +70,7 @@ func checkSearchResponseExtended(t *testing.T, fileName string, reader *bluge.Re
t.Helper()
resp := doSearchQuery(context.Background(), testLogger, reader, filter, query, extender)
goldenFile := filepath.Join("testdata", fileName)
err := experimental.CheckGoldenDataResponse(goldenFile, resp, *update)
err := experimental.CheckGoldenDataResponse(goldenFile, resp, true)
require.NoError(t, err)
}
@ -221,14 +218,13 @@ func TestDashboardIndexSort(t *testing.T) {
frame.Fields,
testNum,
)
return func(field string, value []byte) bool {
return func(field string, value []byte) {
if field == "test" {
if num, err := bluge.DecodeNumericFloat64(value); err == nil {
testNum.Append(num)
return true
return
}
}
return true
}
},
},

View File

@ -3,7 +3,8 @@
Frame[0] {
"type": "search-results",
"custom": {
"count": 2
"count": 2,
"sortBy": "test"
}
}
Name: Query results
@ -19,4 +20,4 @@ Dimensions: 9 Fields by 2 Rows
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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
FRAME=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

View File

@ -3,7 +3,8 @@
Frame[0] {
"type": "search-results",
"custom": {
"count": 2
"count": 2,
"sortBy": "test"
}
}
Name: Query results
@ -19,4 +20,4 @@ Dimensions: 9 Fields by 2 Rows
====== TEST DATA RESPONSE (arrow base64) ======
FRAME=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
FRAME=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

View File

@ -1,4 +1,4 @@
import React, { FC } from 'react';
import React from 'react';
import { useAsync } from 'react-use';
import { SelectableValue } from '@grafana/data';
@ -7,37 +7,47 @@ import { DEFAULT_SORT } from 'app/features/search/constants';
import { SearchSrv } from '../../services/search_srv';
const searchSrv = new SearchSrv();
export interface Props {
onChange: (sortValue: SelectableValue) => void;
value?: string;
placeholder?: string;
getSortOptions?: () => Promise<SelectableValue[]>;
filter?: string[];
isClearable?: boolean;
}
const getSortOptions = (filter?: string[]) => {
return searchSrv.getSortOptions().then(({ sortOptions }) => {
const filteredOptions = filter ? sortOptions.filter((o: any) => filter.includes(o.name)) : sortOptions;
return filteredOptions.map((opt: any) => ({ label: opt.displayName, value: opt.name }));
const defaultSortOptionsGetter = (): Promise<SelectableValue[]> => {
return new SearchSrv().getSortOptions().then(({ sortOptions }) => {
return sortOptions.map((opt: any) => ({ label: opt.displayName, value: opt.name }));
});
};
export const SortPicker: FC<Props> = ({ onChange, value, placeholder, filter }) => {
export function SortPicker({ onChange, value, placeholder, filter, getSortOptions, isClearable }: Props) {
// Using sync Select and manual options fetching here since we need to find the selected option by value
const { loading, value: options } = useAsync<() => Promise<SelectableValue[]>>(() => getSortOptions(filter), []);
const options = useAsync<() => Promise<SelectableValue[]>>(async () => {
const vals = await (getSortOptions ?? defaultSortOptionsGetter)();
if (filter) {
return vals.filter((v) => filter.includes(v.value));
}
return vals;
}, [getSortOptions, filter]);
const selected = options?.find((opt) => opt.value === value);
return !loading ? (
if (options.loading) {
return null;
}
const isDesc = Boolean(value?.includes('desc') || value?.startsWith('-')); // bluge syntax starts with "-"
return (
<Select
key={value}
width={25}
width={28}
onChange={onChange}
value={selected ?? null}
options={options}
value={options.value?.find((opt) => opt.value === value) ?? null}
options={options.value}
aria-label="Sort"
placeholder={placeholder ?? `Sort (Default ${DEFAULT_SORT.label})`}
prefix={<Icon name={value?.includes('asc') ? 'sort-amount-up' : 'sort-amount-down'} />}
prefix={<Icon name={isDesc ? 'sort-amount-down' : 'sort-amount-up'} />}
isClearable={isClearable}
/>
) : null;
};
);
}

View File

@ -9,6 +9,8 @@ import { TagFilter, TermCount } from 'app/core/components/TagFilter/TagFilter';
import { DashboardQuery, SearchLayout } from '../../types';
import { getSortOptions } from './sorting';
export const layoutOptions = [
{ value: SearchLayout.Folders, icon: 'folder', ariaLabel: 'View by folders' },
{ value: SearchLayout.List, icon: 'list-ul', ariaLabel: 'View as list' },
@ -75,7 +77,7 @@ export const ActionRow: FC<Props> = ({
value={layout}
/>
)}
<SortPicker onChange={onSortChange} value={query.sort?.value} />
<SortPicker onChange={onSortChange} value={query.sort?.value} getSortOptions={getSortOptions} isClearable />
</HorizontalGroup>
</div>
<HorizontalGroup spacing="md" width="auto">

View File

@ -56,6 +56,7 @@ export const FolderSection: FC<SectionHeaderProps> = ({
query: '*',
kind: ['dashboard'],
location: section.uid,
sort: 'name_sort',
};
if (section.title === 'Starred') {
query = {

View File

@ -238,6 +238,13 @@ const getStyles = (theme: GrafanaTheme2) => {
color: ${theme.colors.text.secondary};
margin-right: 12px;
`,
sortedHeader: css`
text-align: right;
`,
sortedItems: css`
text-align: right;
padding: ${theme.spacing(1)};
`,
locationCellStyle: css`
padding-top: ${theme.spacing(1)};
padding-right: ${theme.spacing(1)};

View File

@ -41,16 +41,30 @@ export const SearchView = ({ showManage, folderDTO, queryText, hidePseudoFolders
const isFolders = layout === SearchLayout.Folders;
const results = useAsync(() => {
let qstr = queryText;
if (!qstr?.length) {
qstr = '*';
}
const q: SearchQuery = {
query: qstr,
query: queryText,
tags: query.tag as string[],
ds_uid: query.datasource as string,
location: folderDTO?.uid, // This will scope all results to the prefix
sort: query.sort?.value,
};
// Only dashboards have additional properties
if (q.sort?.length && !q.sort.includes('name')) {
q.kind = ['dashboard', 'folder']; // skip panels
}
if (!q.query?.length) {
q.query = '*';
if (!q.location) {
q.kind = ['dashboard', 'folder']; // skip panels
}
}
if (q.query === '*' && !q.sort?.length) {
q.sort = 'name_sort';
}
return getGrafanaSearcher().search(q);
}, [query, layout, queryText, folderDTO]);

View File

@ -1,4 +1,5 @@
import { cx } from '@emotion/css';
import { isNumber } from 'lodash';
import React from 'react';
import SVG from 'react-inlinesvg';
@ -10,9 +11,11 @@ import { QueryResponse, SearchResultMeta } from '../../service';
import { SelectionChecker, SelectionToggle } from '../selection';
import { TableColumn } from './SearchResultsTable';
import { getSortFieldDisplayName } from './sorting';
const TYPE_COLUMN_WIDTH = 250;
const DATASOURCE_COLUMN_WIDTH = 200;
const SORT_FIELD_WIDTH = 175;
export const generateColumns = (
response: QueryResponse,
@ -28,9 +31,12 @@ export const generateColumns = (
const access = response.view.fields;
const uidField = access.uid;
const kindField = access.kind;
const sortField = (access as any)[response.view.dataFrame.meta?.custom?.sortBy] as Field;
if (sortField) {
availableWidth -= SORT_FIELD_WIDTH; // pre-allocate the space for the last column
}
let width = 50;
if (selection && selectionToggle) {
width = 30;
columns.push({
@ -164,6 +170,28 @@ export const generateColumns = (
});
}
if (sortField) {
columns.push({
Header: () => <div className={styles.sortedHeader}>{getSortFieldDisplayName(sortField.name)}</div>,
Cell: (p) => {
let value = sortField.values.get(p.row.index);
try {
if (isNumber(value)) {
value = Number(value).toLocaleString();
}
} catch {}
return (
<div {...p.cellProps} className={styles.sortedItems}>
{value}
</div>
);
},
id: `column-sort-field`,
field: sortField,
width: SORT_FIELD_WIDTH,
});
}
return columns;
};

View File

@ -0,0 +1,37 @@
import { SelectableValue } from '@grafana/data';
import { config } from '@grafana/runtime';
// Enterprise only sort field values for dashboards
const sortFields = [
{ name: 'views_total', display: 'Views total' },
{ name: 'views_last_30_days', display: 'Views 30 days' },
{ name: 'errors_total', display: 'Errors total' },
{ name: 'errors_last_30_days', display: 'Errors 30 days' },
];
// This should eventually be filled by an API call, but hardcoded is a good start
export async function getSortOptions(): Promise<SelectableValue[]> {
const opts: SelectableValue[] = [
{ value: 'name_sort', label: 'Alphabetically (A-Z)' },
{ value: '-name_sort', label: 'Alphabetically (Z-A)' },
];
if (config.licenseInfo.enabledFeatures.analytics) {
for (const sf of sortFields) {
opts.push({ value: `-${sf.name}`, label: `${sf.display} (most)` });
opts.push({ value: `${sf.name}`, label: `${sf.display} (least)` });
}
}
return opts;
}
/** Given the internal field name, this gives a reasonable display name for the table colum header */
export function getSortFieldDisplayName(name: string) {
for (const sf of sortFields) {
if (sf.name === name) {
return sf.display;
}
}
return name;
}