diff --git a/public/app/core/services/impression_srv.ts b/public/app/core/services/impression_srv.ts
index f26b85dc542..114468fddc7 100644
--- a/public/app/core/services/impression_srv.ts
+++ b/public/app/core/services/impression_srv.ts
@@ -28,7 +28,8 @@ export class ImpressionSrv {
store.set(impressionsKey, JSON.stringify(impressions));
}
- getDashboardOpened() {
+ /** Returns an array of internal (numeric) dashboard IDs */
+ getDashboardOpened(): number[] {
let impressions = store.get(this.impressionKey()) || '[]';
impressions = JSON.parse(impressions);
diff --git a/public/app/features/search/components/SearchCard.tsx b/public/app/features/search/components/SearchCard.tsx
index 862c3deefc8..f9b77ac4302 100644
--- a/public/app/features/search/components/SearchCard.tsx
+++ b/public/app/features/search/components/SearchCard.tsx
@@ -1,5 +1,6 @@
import { css } from '@emotion/css';
import React, { useCallback, useRef, useState } from 'react';
+import SVG from 'react-inlinesvg';
import { usePopper } from 'react-popper';
import { GrafanaTheme2 } from '@grafana/data';
@@ -130,7 +131,11 @@ export function SearchCard({ editable, item, onTagSelected, onToggleChecked }: P
setHasImage(false)} />
) : (
-
+ {item.icon ? (
+
+ ) : (
+
+ )}
)}
diff --git a/public/app/features/search/components/SearchCardExpanded.tsx b/public/app/features/search/components/SearchCardExpanded.tsx
index 548cce70706..4700fdd916d 100644
--- a/public/app/features/search/components/SearchCardExpanded.tsx
+++ b/public/app/features/search/components/SearchCardExpanded.tsx
@@ -1,6 +1,7 @@
import { css } from '@emotion/css';
import classNames from 'classnames';
import React, { useState } from 'react';
+import SVG from 'react-inlinesvg';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, Spinner, TagList, useTheme2 } from '@grafana/ui';
@@ -38,7 +39,11 @@ export function SearchCardExpanded({ className, imageHeight, imageWidth, item, l
/>
) : (
-
+ {item.icon ? (
+
+ ) : (
+
+ )}
)}
diff --git a/public/app/features/search/page/SearchPage.tsx b/public/app/features/search/page/SearchPage.tsx
index 2a83b0b11a2..167e18afcbe 100644
--- a/public/app/features/search/page/SearchPage.tsx
+++ b/public/app/features/search/page/SearchPage.tsx
@@ -79,7 +79,7 @@ export default function SearchPage() {
// This gets the possible tags from within the query results
const getTagOptions = (): Promise => {
const q: SearchQuery = {
- query: query.query ?? '*',
+ query: query.query?.length ? query.query : '*',
tags: query.tag,
ds_uid: query.datasource,
};
diff --git a/public/app/features/search/page/components/FolderSection.tsx b/public/app/features/search/page/components/FolderSection.tsx
index 263cb7f320d..53a9dd2f272 100644
--- a/public/app/features/search/page/components/FolderSection.tsx
+++ b/public/app/features/search/page/components/FolderSection.tsx
@@ -3,12 +3,14 @@ import React, { FC } from 'react';
import { useAsync, useLocalStorage } from 'react-use';
import { GrafanaTheme } from '@grafana/data';
+import { getBackendSrv } from '@grafana/runtime';
import { Checkbox, CollapsableSection, Icon, stylesFactory, useTheme } from '@grafana/ui';
+import impressionSrv from 'app/core/services/impression_srv';
import { getSectionStorageKey } from 'app/features/search/utils';
import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId';
import { SearchItem } from '../..';
-import { getGrafanaSearcher } from '../../service';
+import { getGrafanaSearcher, SearchQuery } from '../../service';
import { DashboardSearchItemType, DashboardSectionItem } from '../../types';
import { SelectionChecker, SelectionToggle } from '../selection';
@@ -38,15 +40,32 @@ export const FolderSection: FC = ({ section, selectionToggle
if (!sectionExpanded) {
return Promise.resolve([] as DashboardSectionItem[]);
}
- let query = {
+ let folderUid: string | undefined = section.uid;
+ let folderTitle: string | undefined = section.title;
+ let query: SearchQuery = {
query: '*',
kind: ['dashboard'],
location: section.uid,
};
if (section.title === 'Starred') {
- // TODO
+ const stars = await getBackendSrv().get('api/user/stars');
+ if (stars.length > 0) {
+ query = {
+ uid: stars, // array of UIDs
+ };
+ }
+ folderUid = undefined;
+ folderTitle = undefined;
} else if (section.title === 'Recent') {
- // TODO
+ const ids = impressionSrv.getDashboardOpened();
+ const uids = await getBackendSrv().get(`/api/dashboards/ids/${ids.slice(0, 30).join(',')}`);
+ if (uids?.length) {
+ query = {
+ uid: uids,
+ };
+ }
+ folderUid = undefined;
+ folderTitle = undefined;
}
const raw = await getGrafanaSearcher().search(query);
const v = raw.view.map(
@@ -60,16 +79,36 @@ export const FolderSection: FC = ({ section, selectionToggle
id: 666, // do not use me!
isStarred: false,
tags: item.tags ?? [],
- checked: selection ? selection(item.kind, item.uid) : false,
+ folderUid,
+ folderTitle,
} as DashboardSectionItem)
);
- console.log('HERE!');
return v;
}, [sectionExpanded, section]);
const onSectionExpand = () => {
setSectionExpanded(!sectionExpanded);
- console.log('TODO!! section', section.title, section);
+ };
+
+ const onToggleFolder = (evt: React.FormEvent) => {
+ evt.preventDefault();
+ evt.stopPropagation();
+ if (selectionToggle && selection) {
+ const checked = !selection(section.kind, section.uid);
+ selectionToggle(section.kind, section.uid);
+ const sub = results.value ?? [];
+ for (const item of sub) {
+ if (selection('dashboard', item.uid!) !== checked) {
+ selectionToggle('dashboard', item.uid!);
+ }
+ }
+ }
+ };
+
+ const onToggleChecked = (item: DashboardSectionItem) => {
+ if (selectionToggle) {
+ selectionToggle('dashboard', item.uid!);
+ }
};
const id = useUniqueId();
@@ -80,6 +119,31 @@ export const FolderSection: FC = ({ section, selectionToggle
icon = sectionExpanded ? 'folder-open' : 'folder';
}
+ const renderResults = () => {
+ if (!results.value?.length) {
+ return No items found
;
+ }
+
+ return results.value.map((v) => {
+ if (selection && selectionToggle) {
+ const type = v.type === DashboardSearchItemType.DashFolder ? 'folder' : 'dashboard';
+ v = {
+ ...v,
+ checked: selection(type, v.uid!),
+ };
+ }
+ return (
+
+ );
+ });
+ };
+
return (
= ({ section, selectionToggle
label={
<>
{selectionToggle && selection && (
- console.log(v)} className={styles.checkbox}>
+
)}
@@ -111,13 +175,7 @@ export const FolderSection: FC
= ({ section, selectionToggle
>
}
>
- {results.value && (
-
- {results.value.map((v) => (
-
- ))}
-
- )}
+ {results.value && }
);
};
@@ -150,6 +208,9 @@ const getSectionHeaderStyles = stylesFactory((theme: GrafanaTheme, selected = fa
'pointer',
{ selected }
),
+ sectionItems: css`
+ margin: 0 24px 0 32px;
+ `,
checkbox: css`
padding: 0 ${sm} 0 0;
`,
diff --git a/public/app/features/search/page/components/SearchResultsGrid.tsx b/public/app/features/search/page/components/SearchResultsGrid.tsx
index 296e105ba35..5b4c7e24d54 100644
--- a/public/app/features/search/page/components/SearchResultsGrid.tsx
+++ b/public/app/features/search/page/components/SearchResultsGrid.tsx
@@ -4,6 +4,7 @@ import { FixedSizeGrid } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import { GrafanaTheme2 } from '@grafana/data';
+import { config } from '@grafana/runtime';
import { useStyles2 } from '@grafana/ui';
import { SearchCard } from '../../components/SearchCard';
@@ -42,10 +43,20 @@ export const SearchResultsGrid = ({
const cellWidth = width / numColumns;
const cellHeight = (cellWidth - 64) * 0.75 + 56 + 8;
const numRows = Math.ceil(itemCount / numColumns);
+
return (
{({ onItemsRendered, ref }) => (
{
+ onItemsRendered({
+ visibleStartIndex: v.visibleRowStartIndex * numColumns,
+ visibleStopIndex: v.visibleRowStopIndex * numColumns,
+ overscanStartIndex: v.overscanRowStartIndex * numColumns,
+ overscanStopIndex: v.overscanColumnStopIndex * numColumns,
+ });
+ }}
columnCount={numColumns}
columnWidth={cellWidth}
rowCount={numRows}
@@ -60,9 +71,9 @@ export const SearchResultsGrid = ({
if (index >= view.length) {
return null;
}
-
const item = view.get(index);
const kind = item.kind ?? 'dashboard';
+
const facade: DashboardSectionItem = {
uid: item.uid,
title: item.name,
@@ -75,6 +86,20 @@ export const SearchResultsGrid = ({
checked: selection ? selection(kind, item.uid) : false,
};
+ if (kind === 'panel') {
+ const type = item.panel_type;
+ facade.icon = 'public/img/icons/unicons/graph-bar.svg';
+ if (type) {
+ const info = config.panels[type];
+ if (info?.name) {
+ const v = info.info?.logos.small;
+ if (v && v.endsWith('.svg')) {
+ facade.icon = v;
+ }
+ }
+ }
+ }
+
// The wrapper div is needed as the inner SearchItem has margin-bottom spacing
// And without this wrapper there is no room for that margin
return item ? (
diff --git a/public/app/features/search/page/components/columns.tsx b/public/app/features/search/page/components/columns.tsx
index 36666951f36..5df00f6cc75 100644
--- a/public/app/features/search/page/components/columns.tsx
+++ b/public/app/features/search/page/components/columns.tsx
@@ -5,7 +5,6 @@ import SVG from 'react-inlinesvg';
import { Field } from '@grafana/data';
import { config, getDataSourceSrv } from '@grafana/runtime';
import { Checkbox, Icon, IconName, TagList } from '@grafana/ui';
-import { DefaultCell } from '@grafana/ui/src/components/Table/DefaultCell';
import { QueryResponse, SearchResultMeta } from '../../service';
import { SelectionChecker, SelectionToggle } from '../selection';
@@ -221,11 +220,11 @@ function makeTypeColumn(
styles: Record
): TableColumn {
return {
- Cell: DefaultCell,
id: `column-type`,
field: kindField ?? typeField,
Header: 'Type',
- accessor: (row: any, i: number) => {
+ Cell: (p) => {
+ const i = p.row.index;
const kind = kindField?.values.get(i) ?? 'dashboard';
let icon = 'public/img/icons/unicons/apps.svg';
let txt = 'Dashboard';
@@ -253,13 +252,15 @@ function makeTypeColumn(
icon = v;
}
txt = info.name;
+ } else {
+ icon = `public/img/icons/unicons/question.svg`; // plugin not found
}
}
break;
}
}
return (
-
+
{txt}
diff --git a/public/app/features/search/service/bluge.ts b/public/app/features/search/service/bluge.ts
index 0104d163e5f..59984d5314c 100644
--- a/public/app/features/search/service/bluge.ts
+++ b/public/app/features/search/service/bluge.ts
@@ -70,7 +70,21 @@ export async function doSearchQuery(query: SearchQuery): Promise
field.display = getDisplayProcessor({ field, theme: config.theme2 });
}
- const meta = first.meta?.custom as SearchResultMeta;
+ // Make sure the object exists
+ if (!first.meta?.custom) {
+ first.meta = {
+ ...first.meta,
+ custom: {
+ count: first.length,
+ max_score: 1,
+ },
+ };
+ }
+
+ const meta = first.meta.custom as SearchResultMeta;
+ if (!meta.locationInfo) {
+ meta.locationInfo = {};
+ }
const view = new DataFrameView(first);
return {
totalRows: meta.count ?? first.length,
diff --git a/public/app/features/search/types.ts b/public/app/features/search/types.ts
index ba0caf00c5d..4b1d16d9dc8 100644
--- a/public/app/features/search/types.ts
+++ b/public/app/features/search/types.ts
@@ -40,6 +40,7 @@ export interface DashboardSectionItem {
tags: string[];
title: string;
type: DashboardSearchItemType;
+ icon?: string; // used for grid view
uid?: string;
uri: string;
url: string;