mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 05:29:42 -06:00
Search: show dashboards form query (#47085)
This commit is contained in:
parent
00ec99a8a1
commit
38dc34359b
@ -11,6 +11,7 @@ import {
|
||||
SET_TAGS,
|
||||
TOGGLE_SORT,
|
||||
TOGGLE_STARRED,
|
||||
DATASOURCE_CHANGE,
|
||||
} from '../reducers/actionTypes';
|
||||
import { DashboardQuery, SearchLayout } from '../types';
|
||||
import { hasFilters, parseRouteParams } from '../utils';
|
||||
@ -32,6 +33,11 @@ export const useSearchQuery = (defaults: Partial<DashboardQuery>) => {
|
||||
updateLocation({ tag: tags });
|
||||
};
|
||||
|
||||
const onDatasourceChange = (datasource?: string) => {
|
||||
dispatch({ type: DATASOURCE_CHANGE, payload: datasource });
|
||||
updateLocation({ datasource });
|
||||
};
|
||||
|
||||
const onTagAdd = useCallback(
|
||||
(tag: string) => {
|
||||
dispatch({ type: ADD_TAG, payload: tag });
|
||||
@ -75,5 +81,6 @@ export const useSearchQuery = (defaults: Partial<DashboardQuery>) => {
|
||||
onTagAdd,
|
||||
onSortChange,
|
||||
onLayoutChange,
|
||||
onDatasourceChange,
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||
import { Input, useStyles2, Spinner } from '@grafana/ui';
|
||||
import { Input, useStyles2, Spinner, Button } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { css } from '@emotion/css';
|
||||
@ -22,15 +22,16 @@ const node: NavModelItem = {
|
||||
|
||||
export default function SearchPage() {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { query, onQueryChange, onTagFilterChange } = useSearchQuery({});
|
||||
const { query, onQueryChange, onTagFilterChange, onDatasourceChange } = useSearchQuery({});
|
||||
|
||||
const results = useAsync(() => {
|
||||
const { query: searchQuery, tag: tags } = query;
|
||||
const { query: searchQuery, tag: tags, datasource } = query;
|
||||
|
||||
const filters: QueryFilters = {
|
||||
tags,
|
||||
datasource,
|
||||
};
|
||||
return getGrafanaSearcher().search(searchQuery, tags.length ? filters : undefined);
|
||||
return getGrafanaSearcher().search(searchQuery, tags.length || datasource ? filters : undefined);
|
||||
}, [query]);
|
||||
|
||||
if (!config.featureToggles.panelTitleSearch) {
|
||||
@ -63,11 +64,27 @@ export default function SearchPage() {
|
||||
{results.value?.body && (
|
||||
<div>
|
||||
<TagFilter isClearable tags={query.tag} tagOptions={getTagOptions} onChange={onTagChange} /> <br />
|
||||
{query.datasource && (
|
||||
<Button
|
||||
icon="times"
|
||||
variant="secondary"
|
||||
onClick={() => onDatasourceChange(undefined)}
|
||||
className={styles.clearClick}
|
||||
>
|
||||
Datasource: {query.datasource}
|
||||
</Button>
|
||||
)}
|
||||
<AutoSizer style={{ width: '100%', height: '2000px' }}>
|
||||
{({ width }) => {
|
||||
return (
|
||||
<>
|
||||
<Table data={results.value!.body} width={width} tags={query.tag} onTagFilterChange={onTagChange} />
|
||||
<Table
|
||||
data={results.value!.body}
|
||||
width={width}
|
||||
tags={query.tag}
|
||||
onTagFilterChange={onTagChange}
|
||||
onDatasourceChange={onDatasourceChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
@ -88,4 +105,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
`,
|
||||
|
||||
clearClick: css`
|
||||
&:hover {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
margin-bottom: 20px;
|
||||
`,
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ type Props = {
|
||||
width: number;
|
||||
tags: string[];
|
||||
onTagFilterChange: (tags: string[]) => void;
|
||||
onDatasourceChange: (datasource?: string) => void;
|
||||
};
|
||||
|
||||
export type TableColumn = Column & {
|
||||
@ -36,7 +37,7 @@ export interface FieldAccess {
|
||||
datasource: DataSourceRef[];
|
||||
}
|
||||
|
||||
export const Table = ({ data, width, tags, onTagFilterChange }: Props) => {
|
||||
export const Table = ({ data, width, tags, onTagFilterChange, onDatasourceChange }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
|
||||
@ -54,8 +55,8 @@ export const Table = ({ data, width, tags, onTagFilterChange }: Props) => {
|
||||
const access = useMemo(() => new DataFrameView<FieldAccess>(data), [data]);
|
||||
const memoizedColumns = useMemo(() => {
|
||||
const isDashboardList = data.meta?.type === DataFrameType.DirectoryListing;
|
||||
return generateColumns(access, isDashboardList, width, styles, tags, onTagFilterChange);
|
||||
}, [data.meta?.type, access, width, styles, tags, onTagFilterChange]);
|
||||
return generateColumns(access, isDashboardList, width, styles, tags, onTagFilterChange, onDatasourceChange);
|
||||
}, [data.meta?.type, access, width, styles, tags, onTagFilterChange, onDatasourceChange]);
|
||||
|
||||
const options: TableOptions<{}> = useMemo(
|
||||
() => ({
|
||||
@ -77,7 +78,11 @@ export const Table = ({ data, width, tags, onTagFilterChange }: Props) => {
|
||||
return (
|
||||
<div {...row.getRowProps({ style })} className={styles.rowContainer}>
|
||||
{row.cells.map((cell: Cell, index: number) => {
|
||||
if (cell.column.id === 'column-checkbox' || cell.column.id === 'column-tags') {
|
||||
if (
|
||||
cell.column.id === 'column-checkbox' ||
|
||||
cell.column.id === 'column-tags' ||
|
||||
cell.column.id === 'column-datasource'
|
||||
) {
|
||||
return (
|
||||
<div key={index} className={styles.cellWrapper}>
|
||||
<TableCell
|
||||
@ -174,7 +179,6 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
`,
|
||||
cellWrapper: css`
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
`,
|
||||
headerCell: css`
|
||||
padding-top: 2px;
|
||||
|
@ -14,7 +14,8 @@ export const generateColumns = (
|
||||
availableWidth: number,
|
||||
styles: { [key: string]: string },
|
||||
tags: string[],
|
||||
onTagFilterChange: (tags: string[]) => void
|
||||
onTagFilterChange: (tags: string[]) => void,
|
||||
onDatasourceChange: (datasource?: string) => void
|
||||
): TableColumn[] => {
|
||||
const columns: TableColumn[] = [];
|
||||
const urlField = data.fields.url!;
|
||||
@ -91,7 +92,7 @@ export const generateColumns = (
|
||||
// Show datasources if we have any
|
||||
if (access.datasource && hasFieldValue(access.datasource)) {
|
||||
width = DATASOURCE_COLUMN_WIDTH;
|
||||
columns.push(makeDataSourceColumn(access.datasource, width, styles.typeIcon));
|
||||
columns.push(makeDataSourceColumn(access.datasource, width, styles.typeIcon, onDatasourceChange));
|
||||
availableWidth -= width;
|
||||
}
|
||||
|
||||
@ -171,7 +172,12 @@ function getIconForKind(v: string): IconName {
|
||||
return 'question-circle';
|
||||
}
|
||||
|
||||
function makeDataSourceColumn(field: Field<DataSourceRef[]>, width: number, iconClass: string): TableColumn {
|
||||
function makeDataSourceColumn(
|
||||
field: Field<DataSourceRef[]>,
|
||||
width: number,
|
||||
iconClass: string,
|
||||
onDatasourceChange: (datasource?: string) => void
|
||||
): TableColumn {
|
||||
return {
|
||||
Cell: DefaultCell,
|
||||
id: `column-datasource`,
|
||||
@ -188,10 +194,18 @@ function makeDataSourceColumn(field: Field<DataSourceRef[]>, width: number, icon
|
||||
const icon = settings?.meta?.info?.logos?.small;
|
||||
if (icon) {
|
||||
return (
|
||||
<span key={i}>
|
||||
<a
|
||||
key={i}
|
||||
href={`datasources/edit/${settings.uid}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onDatasourceChange(settings.uid);
|
||||
}}
|
||||
>
|
||||
<SVG src={icon} width={14} height={14} title={settings.type} className={iconClass} />
|
||||
{settings.name}
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <span key={i}>{v.type}</span>;
|
||||
|
@ -16,6 +16,7 @@ export const DELETE_ITEMS = 'DELETE_ITEMS';
|
||||
export const TOGGLE_STARRED = 'TOGGLE_STARRED';
|
||||
export const REMOVE_STARRED = 'REMOVE_STARRED';
|
||||
export const QUERY_CHANGE = 'QUERY_CHANGE';
|
||||
export const DATASOURCE_CHANGE = 'DATASOURCE_CHANGE';
|
||||
export const REMOVE_TAG = 'REMOVE_TAG';
|
||||
export const CLEAR_FILTERS = 'CLEAR_FILTERS';
|
||||
export const SET_TAGS = 'SET_TAGS';
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
REMOVE_STARRED,
|
||||
REMOVE_TAG,
|
||||
SET_TAGS,
|
||||
DATASOURCE_CHANGE,
|
||||
TOGGLE_SORT,
|
||||
TOGGLE_STARRED,
|
||||
} from './actionTypes';
|
||||
@ -43,6 +44,8 @@ export const queryReducer = (state: DashboardQuery, action: SearchAction) => {
|
||||
const tag = action.payload;
|
||||
return tag && !state.tag.includes(tag) ? { ...state, tag: [...state.tag, tag] } : state;
|
||||
}
|
||||
case DATASOURCE_CHANGE:
|
||||
return { ...state, datasource: action.payload };
|
||||
case TOGGLE_STARRED:
|
||||
return { ...state, starred: action.payload };
|
||||
case REMOVE_STARRED:
|
||||
|
@ -154,23 +154,40 @@ export function filterFrame(frame: DataFrame, filter?: QueryFilters): DataFrame
|
||||
const view = new DataFrameView<QueryResult>(frame);
|
||||
const keep: number[] = [];
|
||||
|
||||
const ds = filter.datasource ? view.fields.datasource : undefined;
|
||||
const tags = filter.tags?.length ? view.fields.tags : undefined;
|
||||
|
||||
let ok = true;
|
||||
for (let i = 0; i < view.length; i++) {
|
||||
ok = true;
|
||||
const row = view.get(i);
|
||||
if (filter.tags) {
|
||||
const tags = row.tags;
|
||||
if (!tags) {
|
||||
|
||||
if (tags) {
|
||||
const v = tags.values.get(i);
|
||||
if (!v) {
|
||||
ok = false;
|
||||
continue;
|
||||
}
|
||||
for (const t of filter.tags) {
|
||||
if (!tags.includes(t)) {
|
||||
ok = false;
|
||||
break;
|
||||
} else {
|
||||
for (const t of filter.tags!) {
|
||||
if (!v.includes(t)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ok && ds && filter.datasource) {
|
||||
ok = false;
|
||||
const v = ds.values.get(i);
|
||||
if (v) {
|
||||
for (const d of v) {
|
||||
if (d.uid === filter.datasource) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
keep.push(i);
|
||||
}
|
||||
|
@ -243,7 +243,22 @@ function shouldKeep(filter: QueryFilters, doc: InputDoc, index: number): boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
let keep = true;
|
||||
// Any is OK
|
||||
if (filter.datasource) {
|
||||
keep = false;
|
||||
const dss = doc.datasource?.get(index);
|
||||
if (dss) {
|
||||
for (const ds of dss) {
|
||||
if (ds.uid === filter.datasource) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return keep;
|
||||
}
|
||||
|
||||
function getInputDoc(kind: SearchResultKind, frame: DataFrame): InputDoc {
|
||||
|
@ -68,6 +68,7 @@ export interface DashboardQuery {
|
||||
skipRecent: boolean;
|
||||
skipStarred: boolean;
|
||||
folderIds: number[];
|
||||
datasource?: string;
|
||||
sort: SelectableValue | null;
|
||||
// Save sorting data between layouts
|
||||
prevSort: SelectableValue | null;
|
||||
|
Loading…
Reference in New Issue
Block a user