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:
Alex Khomenko 2020-04-20 18:04:51 +03:00 committed by GitHub
parent 55c306eb6d
commit 8709c9a8a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 68 additions and 42 deletions

View File

@ -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;

View File

@ -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() {

View File

@ -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>

View File

@ -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();

View File

@ -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 {

View File

@ -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,
});
} }
}; };

View File

@ -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);

View File

@ -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';

View File

@ -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(