mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
Search: Toggle Search based on search query (#23648)
* Search: Toggle Search based on search query * Search: Fix types and closed search param * Search: Remove appEvents from SearchWrapper * Search: Reset folder on close Co-Authored-By: Alexander Zobnin <alexanderzobnin@gmail.com> * Search: Disable reloadOnSearch for manage dashboards urls Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
This commit is contained in:
parent
55c306eb6d
commit
8709c9a8a5
@ -3,3 +3,4 @@ import { LocationState } from 'app/types';
|
|||||||
export const getRouteParamsId = (state: LocationState) => state.routeParams.id;
|
export const getRouteParamsId = (state: LocationState) => state.routeParams.id;
|
||||||
export const getRouteParamsPage = (state: LocationState) => state.routeParams.page;
|
export const getRouteParamsPage = (state: LocationState) => state.routeParams.page;
|
||||||
export const getRouteParams = (state: LocationState) => state.routeParams;
|
export const getRouteParams = (state: LocationState) => state.routeParams;
|
||||||
|
export const getLocationQuery = (state: LocationState) => state.query;
|
||||||
|
@ -83,7 +83,8 @@ export class KeybindingSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openSearch() {
|
openSearch() {
|
||||||
appEvents.emit(CoreEvents.showDashSearch);
|
const search = _.extend(this.$location.search(), { search: 'open' });
|
||||||
|
this.$location.search(search);
|
||||||
}
|
}
|
||||||
|
|
||||||
openAlerting() {
|
openAlerting() {
|
||||||
|
@ -47,13 +47,17 @@ class DashNav extends PureComponent<Props> {
|
|||||||
this.playlistSrv = this.props.$injector.get('playlistSrv');
|
this.playlistSrv = this.props.$injector.get('playlistSrv');
|
||||||
}
|
}
|
||||||
|
|
||||||
onDahboardNameClick = () => {
|
onDashboardNameClick = () => {
|
||||||
appEvents.emit(CoreEvents.showDashSearch);
|
this.props.updateLocation({
|
||||||
|
query: { search: 'open' },
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onFolderNameClick = () => {
|
onFolderNameClick = () => {
|
||||||
appEvents.emit(CoreEvents.showDashSearch, {
|
this.props.updateLocation({
|
||||||
query: 'folder:current',
|
query: { search: 'open', folder: 'current' },
|
||||||
|
partial: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,7 +130,7 @@ class DashNav extends PureComponent<Props> {
|
|||||||
<Icon name="angle-right" className={iconClassName} />
|
<Icon name="angle-right" className={iconClassName} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<a onClick={this.onDahboardNameClick}>
|
<a onClick={this.onDashboardNameClick}>
|
||||||
{dashboard.title} <Icon name="angle-down" className={iconClassName} />
|
{dashboard.title} <Icon name="angle-down" className={iconClassName} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,6 @@ import { GrafanaTheme } from '@grafana/data';
|
|||||||
import { SearchSrv } from 'app/core/services/search_srv';
|
import { SearchSrv } from 'app/core/services/search_srv';
|
||||||
import { TagFilter } from 'app/core/components/TagFilter/TagFilter';
|
import { TagFilter } from 'app/core/components/TagFilter/TagFilter';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { OpenSearchParams } from '../types';
|
|
||||||
import { useSearchQuery } from '../hooks/useSearchQuery';
|
import { useSearchQuery } from '../hooks/useSearchQuery';
|
||||||
import { useDashboardSearch } from '../hooks/useDashboardSearch';
|
import { useDashboardSearch } from '../hooks/useDashboardSearch';
|
||||||
import { SearchField } from './SearchField';
|
import { SearchField } from './SearchField';
|
||||||
@ -18,10 +17,11 @@ const canEdit = isEditor || hasEditPermissionInFolders;
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
onCloseSearch: () => void;
|
onCloseSearch: () => void;
|
||||||
payload?: OpenSearchParams;
|
folder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DashboardSearch: FC<Props> = ({ onCloseSearch, payload = {} }) => {
|
export const DashboardSearch: FC<Props> = ({ onCloseSearch, folder }) => {
|
||||||
|
const payload = folder ? { query: `folder:${folder}` } : {};
|
||||||
const { query, onQueryChange, onClearFilters, onTagFilterChange, onTagAdd } = useSearchQuery(payload);
|
const { query, onQueryChange, onClearFilters, onTagFilterChange, onTagAdd } = useSearchQuery(payload);
|
||||||
const { results, loading, onToggleSection, onKeyDown } = useDashboardSearch(query, onCloseSearch);
|
const { results, loading, onToggleSection, onKeyDown } = useDashboardSearch(query, onCloseSearch);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState, memo } from 'react';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { Icon, TagList, HorizontalGroup, stylesFactory, useTheme } from '@grafana/ui';
|
import { Icon, TagList, HorizontalGroup, stylesFactory, useTheme } from '@grafana/ui';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
@ -20,7 +20,7 @@ export interface Props {
|
|||||||
|
|
||||||
const { isEditor } = contextSrv;
|
const { isEditor } = contextSrv;
|
||||||
|
|
||||||
export const ManageDashboards: FC<Props> = ({ folderId, folderUid }) => {
|
export const ManageDashboards: FC<Props> = memo(({ folderId, folderUid }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
@ -152,7 +152,7 @@ export const ManageDashboards: FC<Props> = ({ folderId, folderUid }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
return {
|
return {
|
||||||
|
@ -3,8 +3,7 @@ import { css, cx } from 'emotion';
|
|||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { e2e } from '@grafana/e2e';
|
import { e2e } from '@grafana/e2e';
|
||||||
import { Icon, useTheme, TagList, styleMixins, stylesFactory } from '@grafana/ui';
|
import { Icon, useTheme, TagList, styleMixins, stylesFactory } from '@grafana/ui';
|
||||||
import appEvents from 'app/core/app_events';
|
import { updateLocation } from 'app/core/reducers/location';
|
||||||
import { CoreEvents } from 'app/types';
|
|
||||||
import { DashboardSectionItem, OnToggleChecked } from '../types';
|
import { DashboardSectionItem, OnToggleChecked } from '../types';
|
||||||
import { SearchCheckbox } from './SearchCheckbox';
|
import { SearchCheckbox } from './SearchCheckbox';
|
||||||
|
|
||||||
@ -38,7 +37,10 @@ export const SearchItem: FC<Props> = ({ item, editable, onToggleChecked, onTagSe
|
|||||||
const onItemClick = () => {
|
const onItemClick = () => {
|
||||||
//Check if one string can be found in the other
|
//Check if one string can be found in the other
|
||||||
if (window.location.pathname.includes(item.url) || item.url.includes(window.location.pathname)) {
|
if (window.location.pathname.includes(item.url) || item.url.includes(window.location.pathname)) {
|
||||||
appEvents.emit(CoreEvents.hideDashSearch, { target: 'search-item' });
|
updateLocation({
|
||||||
|
query: { search: null },
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,34 +1,49 @@
|
|||||||
import React, { FC, useState, useEffect } from 'react';
|
import React, { FC, memo } from 'react';
|
||||||
import { appEvents } from 'app/core/core';
|
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||||
import { CoreEvents } from 'app/types';
|
import { getLocationQuery } from 'app/core/selectors/location';
|
||||||
|
import { updateLocation } from 'app/core/reducers/location';
|
||||||
|
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||||
|
import { StoreState } from 'app/types';
|
||||||
import { DashboardSearch } from './DashboardSearch';
|
import { DashboardSearch } from './DashboardSearch';
|
||||||
import { OpenSearchParams } from '../types';
|
|
||||||
|
|
||||||
export const SearchWrapper: FC = () => {
|
interface OwnProps {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
search?: string | null;
|
||||||
const [payload, setPayload] = useState({});
|
folder?: string;
|
||||||
|
queryText?: string;
|
||||||
|
filter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
interface DispatchProps {
|
||||||
const openSearch = (payload: OpenSearchParams) => {
|
updateLocation: typeof updateLocation;
|
||||||
setIsOpen(true);
|
}
|
||||||
setPayload(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeOnItemClick = (payload: any) => {
|
export type Props = OwnProps & DispatchProps;
|
||||||
// Detect if the event was emitted by clicking on search item
|
|
||||||
if (payload?.target === 'search-item' && isOpen) {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
appEvents.on(CoreEvents.showDashSearch, openSearch);
|
export const SearchWrapper: FC<Props> = memo(({ search, folder, updateLocation }) => {
|
||||||
appEvents.on(CoreEvents.hideDashSearch, closeOnItemClick);
|
const isOpen = search === 'open';
|
||||||
|
|
||||||
return () => {
|
const closeSearch = () => {
|
||||||
appEvents.off(CoreEvents.showDashSearch, openSearch);
|
if (search === 'open') {
|
||||||
appEvents.off(CoreEvents.hideDashSearch, closeOnItemClick);
|
updateLocation({
|
||||||
};
|
query: {
|
||||||
}, [isOpen]);
|
search: null,
|
||||||
|
folder: null,
|
||||||
|
},
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return isOpen ? <DashboardSearch onCloseSearch={() => setIsOpen(false)} payload={payload} /> : null;
|
return isOpen ? <DashboardSearch onCloseSearch={closeSearch} folder={folder} /> : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps: MapStateToProps<{}, OwnProps, StoreState> = (state: StoreState) => {
|
||||||
|
const { search, folder } = getLocationQuery(state.location);
|
||||||
|
return { search, folder };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||||
|
updateLocation,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connectWithStore(SearchWrapper, mapStateToProps, mapDispatchToProps);
|
||||||
|
@ -2,7 +2,7 @@ export { SearchResults } from './components/SearchResults';
|
|||||||
export { SearchField } from './components/SearchField';
|
export { SearchField } from './components/SearchField';
|
||||||
export { SearchItem } from './components/SearchItem';
|
export { SearchItem } from './components/SearchItem';
|
||||||
export { SearchCheckbox } from './components/SearchCheckbox';
|
export { SearchCheckbox } from './components/SearchCheckbox';
|
||||||
export { SearchWrapper } from './components/SearchWrapper';
|
export { default as SearchWrapper } from './components/SearchWrapper';
|
||||||
export { SearchResultsFilter } from './components/SearchResultsFilter';
|
export { SearchResultsFilter } from './components/SearchResultsFilter';
|
||||||
export { ManageDashboards } from './components/ManageDashboards';
|
export { ManageDashboards } from './components/ManageDashboards';
|
||||||
export { ConfirmDeleteModal } from './components/ConfirmDeleteModal';
|
export { ConfirmDeleteModal } from './components/ConfirmDeleteModal';
|
||||||
|
@ -156,6 +156,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
|||||||
})
|
})
|
||||||
.when('/dashboards', {
|
.when('/dashboards', {
|
||||||
template: '<react-container />',
|
template: '<react-container />',
|
||||||
|
reloadOnSearch: false,
|
||||||
resolve: {
|
resolve: {
|
||||||
component: () =>
|
component: () =>
|
||||||
SafeDynamicImport(
|
SafeDynamicImport(
|
||||||
@ -192,6 +193,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
|||||||
})
|
})
|
||||||
.when('/dashboards/f/:uid/:slug', {
|
.when('/dashboards/f/:uid/:slug', {
|
||||||
template: '<react-container />',
|
template: '<react-container />',
|
||||||
|
reloadOnSearch: false,
|
||||||
resolve: {
|
resolve: {
|
||||||
component: () =>
|
component: () =>
|
||||||
SafeDynamicImport(
|
SafeDynamicImport(
|
||||||
@ -201,6 +203,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
|||||||
})
|
})
|
||||||
.when('/dashboards/f/:uid', {
|
.when('/dashboards/f/:uid', {
|
||||||
template: '<react-container />',
|
template: '<react-container />',
|
||||||
|
reloadOnSearch: false,
|
||||||
resolve: {
|
resolve: {
|
||||||
component: () =>
|
component: () =>
|
||||||
SafeDynamicImport(
|
SafeDynamicImport(
|
||||||
|
Loading…
Reference in New Issue
Block a user