Search: Adds search to main nav and removes open search click on dashboard name (#23943)

* Search: Adds search to main nav and removes open search click on dashboard name

* Updated snapshots

* Fixed strict null errors

* Fixed type issue
This commit is contained in:
Torkel Ödegaard
2020-04-27 16:38:04 +02:00
committed by GitHub
parent 1b9a926afc
commit 2c0a1d8428
19 changed files with 152 additions and 53 deletions
@@ -40,7 +40,7 @@ export interface OptionsEditorItem<TOptions, TSettings, TEditorProps, TValue> ex
*
* @param currentConfig Current options values
*/
showIf?: (currentConfig: TOptions) => boolean;
showIf?: (currentConfig: TOptions) => boolean | undefined;
/**
* Function that returns number of items if given option represents a collection, i.e. array of items.
* @param value
@@ -428,7 +428,7 @@ export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
intervalMs?: number;
maxDataPoints?: number;
panelId: number;
range?: TimeRange;
range: TimeRange;
reverse?: boolean;
scopedVars: ScopedVars;
targets: TQuery[];
+1 -1
View File
@@ -156,7 +156,7 @@ export interface PanelOptionsEditorConfig<TOptions, TSettings = any, TValue = an
*
* @param currentConfig Current panel options
*/
showIf?: (currentConfig: TOptions) => boolean;
showIf?: (currentConfig: TOptions) => boolean | undefined;
}
/**
@@ -88,7 +88,7 @@ export const getSvgSize = (size: IconSize) => {
case 'lg':
return 18;
case 'xl':
return 28;
return 24;
case 'xxl':
return 36;
case 'xxxl':
@@ -42,6 +42,7 @@ export const SelectMenuOptions = React.forwardRef<HTMLDivElement, React.PropsWit
const theme = useTheme();
const styles = getSelectStyles(theme);
const { children, innerProps, data, renderOptionLabel, isSelected, isFocused } = props;
return (
<div
ref={ref}
+1
View File
@@ -112,6 +112,7 @@ export type IconName =
| 'heart-break'
| 'ellipsis-v'
| 'favorite'
| 'line-alt'
| 'sort-amount-down';
export const getAvailableIcons = (): IconName[] => [
@@ -4,7 +4,7 @@ import { css } from 'emotion';
// @ts-ignore
import { components } from '@torkelo/react-select';
import { AsyncSelect, stylesFactory } from '@grafana/ui';
import { resetSelectStyles, Icon } from '@grafana/ui';
import { Icon } from '@grafana/ui';
import { escapeStringForRegex } from '@grafana/data';
// Components
import { TagOption } from './TagOption';
@@ -74,7 +74,6 @@ export class TagFilter extends React.Component<Props, any> {
noOptionsMessage: 'No tags found',
onChange: this.onChange,
placeholder,
styles: resetSelectStyles(),
value: tags,
width,
components: {
@@ -2,13 +2,23 @@ import React, { FC } from 'react';
import _ from 'lodash';
import TopSectionItem from './TopSectionItem';
import config from '../../config';
import { getLocationSrv } from '@grafana/runtime';
const TopSection: FC<any> = () => {
const navTree = _.cloneDeep(config.bootData.navTree);
const mainLinks = _.filter(navTree, item => !item.hideFromMenu);
const searchLink = {
text: 'Search',
icon: 'search',
};
const onOpenSearch = () => {
getLocationSrv().update({ query: { search: 'open' }, partial: true });
};
return (
<div className="sidemenu__top">
<TopSectionItem link={searchLink} onClick={onOpenSearch} />
{mainLinks.map((link, index) => {
return <TopSectionItem link={link} key={`${link.id}-${index}`} />;
})}
@@ -1,22 +1,24 @@
import React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import TopSectionItem from './TopSectionItem';
const setup = (propOverrides?: object) => {
const props = Object.assign(
{
link: {},
link: {
text: 'Hello',
url: '/asd',
},
},
propOverrides
);
return shallow(<TopSectionItem {...props} />);
return mount(<TopSectionItem {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
});
@@ -1,18 +1,21 @@
import React, { FC } from 'react';
import SideMenuDropDown from './SideMenuDropDown';
import { Icon } from '@grafana/ui';
import { NavModelItem } from '@grafana/data';
export interface Props {
link: any;
link: NavModelItem;
onClick?: () => void;
}
const TopSectionItem: FC<Props> = props => {
const { link } = props;
const { link, onClick } = props;
return (
<div className="sidemenu-item dropdown">
<a className="sidemenu-link" href={link.url} target={link.target}>
<a className="sidemenu-link" href={link.url} target={link.target} onClick={onClick}>
<span className="icon-circle sidemenu-icon">
<Icon name={link.icon} size="xl" />
<Icon name={link.icon as any} size="xl" />
{link.img && <img src={link.img} />}
</span>
</a>
@@ -4,6 +4,15 @@ exports[`Render should render component 1`] = `
<div
className="sidemenu__top"
>
<TopSectionItem
link={
Object {
"icon": "search",
"text": "Search",
}
}
onClick={[Function]}
/>
<TopSectionItem
key="3-0"
link={
@@ -20,6 +29,15 @@ exports[`Render should render items 1`] = `
<div
className="sidemenu__top"
>
<TopSectionItem
link={
Object {
"icon": "search",
"text": "Search",
}
}
onClick={[Function]}
/>
<TopSectionItem
key="3-0"
link={
@@ -1,22 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div
className="sidemenu-item dropdown"
<TopSectionItem
link={
Object {
"text": "Hello",
"url": "/asd",
}
}
>
<a
className="sidemenu-link"
<div
className="sidemenu-item dropdown"
>
<span
className="icon-circle sidemenu-icon"
<a
className="sidemenu-link"
href="/asd"
>
<Icon
size="xl"
/>
</span>
</a>
<SideMenuDropDown
link={Object {}}
/>
</div>
<span
className="icon-circle sidemenu-icon"
>
<Icon
size="xl"
>
<div />
</Icon>
</span>
</a>
<SideMenuDropDown
link={
Object {
"text": "Hello",
"url": "/asd",
}
}
>
<ul
className="dropdown-menu dropdown-menu--sidemenu"
role="menu"
>
<li
className="side-menu-header"
>
<a
className="side-menu-header-link"
href="/asd"
>
<span
className="sidemenu-item-text"
>
Hello
</span>
</a>
</li>
</ul>
</SideMenuDropDown>
</div>
</TopSectionItem>
`;
@@ -58,13 +58,6 @@ class DashNav extends PureComponent<Props> {
this.playlistSrv = this.props.$injector.get('playlistSrv');
}
onDashboardNameClick = () => {
this.props.updateLocation({
query: { search: 'open' },
partial: true,
});
};
onFolderNameClick = () => {
this.props.updateLocation({
query: { search: 'open', folder: 'current' },
@@ -169,13 +162,12 @@ class DashNav extends PureComponent<Props> {
renderDashboardTitleSearchButton() {
const { dashboard, isFullscreen } = this.props;
/* Hard-coded value so we don't have to wrap whole component in withTheme because of 1 variable */
const iconClassName = css`
margin-right: 4px;
margin-bottom: -1px;
const folderSymbol = css`
margin-right: 0 4px;
`;
const mainIconClassName = css`
margin-right: 4px;
margin-right: 8px;
margin-bottom: 3px;
`;
@@ -190,14 +182,11 @@ class DashNav extends PureComponent<Props> {
{haveFolder && (
<>
<a className="navbar-page-btn__folder" onClick={this.onFolderNameClick}>
{folderTitle}
{folderTitle} <span className={folderSymbol}>/</span>
</a>
<Icon name="angle-right" className={iconClassName} />
</>
)}
<a onClick={this.onDashboardNameClick}>
{dashboard.title} <Icon name="angle-down" className={iconClassName} />
</a>
<span>{dashboard.title}</span>
</div>
</div>
<div className="navbar-buttons navbar-buttons--actions">{this.renderLeftActionsButton()}</div>
@@ -1,5 +1,5 @@
import { JaegerDatasource, JaegerQuery } from './datasource';
import { DataQueryRequest, DataSourceInstanceSettings, FieldType, PluginType } from '@grafana/data';
import { DataQueryRequest, DataSourceInstanceSettings, FieldType, PluginType, dateTime } from '@grafana/data';
import { BackendSrv, BackendSrvRequest, getBackendSrv, setBackendSrv } from '@grafana/runtime';
describe('JaegerDatasource', () => {
@@ -77,7 +77,12 @@ const defaultQuery: DataQueryRequest<JaegerQuery> = {
interval: '0',
panelId: 0,
scopedVars: {},
timezone: '',
range: {
from: dateTime().subtract(1, 'h'),
to: dateTime(),
raw: { from: '1h', to: 'now' },
},
timezone: 'browser',
app: 'explore',
startTime: 0,
targets: [
+1 -1
View File
@@ -17,7 +17,7 @@ export const plugin = new PanelPlugin<NewsOptions>(NewsPanel).setPanelOptions(bu
path: 'useProxy',
name: 'Use Proxy',
description: 'If the feed is unable to connect, consider a CORS proxy',
showIf: currentConfig => {
showIf: (currentConfig: NewsOptions) => {
return currentConfig.feedUrl && !currentConfig.feedUrl.startsWith(PROXY_PREFIX);
},
});
+1 -1
View File
@@ -51,7 +51,7 @@ export function addStandardDataReduceOptions(
min: 1,
max: 5000,
},
showIf: options => options.reduceOptions.values,
showIf: options => options.reduceOptions.values === true,
});
builder.addCustomEditor({
+35 -1
View File
@@ -108,6 +108,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/dashboard/import', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -117,6 +118,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/datasources', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -138,6 +140,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/datasources/edit/:id/dashboards', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -147,6 +150,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/datasources/new', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -166,6 +170,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/dashboards/folder/new', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -175,6 +180,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/dashboards/f/:uid/:slug/permissions', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -184,6 +190,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/dashboards/f/:uid/:slug/settings', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -231,6 +238,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "OrgDetailsPage" */ '../features/org/OrgDetailsPage')),
@@ -238,12 +246,14 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org/new', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () => SafeDynamicImport(import(/* webpackChunkName: "NewOrgPage" */ 'app/features/org/NewOrgPage')),
},
})
.when('/org/users', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "UsersListPage" */ 'app/features/users/UsersListPage')),
@@ -251,6 +261,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org/users/invite', {
template: '<react-container/>',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "UserInvitePage" */ 'app/features/org/UserInvitePage')),
@@ -258,6 +269,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org/apikeys', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
roles: () => ['Editor', 'Admin'],
component: () =>
@@ -266,6 +278,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org/teams', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
roles: () => (config.editorsCanAdmin ? [] : ['Editor', 'Admin']),
component: () => SafeDynamicImport(import(/* webpackChunkName: "TeamList" */ 'app/features/teams/TeamList')),
@@ -273,6 +286,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org/teams/new', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
roles: () => (config.editorsCanAdmin ? [] : ['Admin']),
component: () =>
@@ -281,6 +295,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/org/teams/edit/:id/:page?', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
roles: () => (config.editorsCanAdmin ? [] : ['Admin']),
component: () => SafeDynamicImport(import(/* webpackChunkName: "TeamPages" */ 'app/features/teams/TeamPages')),
@@ -290,9 +305,11 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
templateUrl: 'public/app/features/profile/partials/profile.html',
controller: 'ProfileCtrl',
controllerAs: 'ctrl',
reloadOnSearch: false,
})
.when('/profile/password', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -303,15 +320,18 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
.when('/profile/select-org', {
templateUrl: 'public/app/features/org/partials/select_org.html',
controller: 'SelectOrgCtrl',
reloadOnSearch: false,
})
// ADMIN
.when('/admin', {
templateUrl: 'public/app/features/admin/partials/admin_home.html',
controller: 'AdminHomeCtrl',
controllerAs: 'ctrl',
reloadOnSearch: false,
})
.when('/admin/settings', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "AdminSettings" */ 'app/features/admin/AdminSettings')),
@@ -319,12 +339,14 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/admin/upgrading', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () => SafeDynamicImport(import('app/features/admin/UpgradePage')),
},
})
.when('/admin/users', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "UserListAdminPage" */ 'app/features/admin/UserListAdminPage')),
@@ -332,6 +354,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/admin/users/create', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "UserCreatePage" */ 'app/features/admin/UserCreatePage')),
@@ -339,12 +362,14 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/admin/users/edit/:id', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () => UserAdminPage,
},
})
.when('/admin/orgs', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "AdminListOrgsPage" */ 'app/features/admin/AdminListOrgsPage')),
@@ -352,6 +377,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/admin/orgs/edit/:id', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "AdminEditOrgPage" */ 'app/features/admin/AdminEditOrgPage')),
@@ -359,6 +385,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/admin/stats', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "ServerStats" */ 'app/features/admin/ServerStats')),
@@ -366,6 +393,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/admin/ldap', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () => LdapPage,
},
@@ -410,6 +438,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/plugins', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "PluginListPage" */ 'app/features/plugins/PluginListPage')),
@@ -417,7 +446,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/plugins/:pluginId/', {
template: '<react-container />',
reloadOnSearch: false, // tabs from query parameters
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(import(/* webpackChunkName: "PluginPage" */ '../features/plugins/PluginPage')),
@@ -427,6 +456,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
templateUrl: 'public/app/features/plugins/partials/plugin_page.html',
controller: 'AppPageCtrl',
controllerAs: 'ctrl',
reloadOnSearch: false,
})
.when('/alerting', {
redirectTo: '/alerting/list',
@@ -441,6 +471,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
})
.when('/alerting/notifications', {
template: '<react-container />',
reloadOnSearch: false,
resolve: {
component: () =>
SafeDynamicImport(
@@ -452,15 +483,18 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
templateUrl: 'public/app/features/alerting/partials/notification_edit.html',
controller: 'AlertNotificationEditCtrl',
controllerAs: 'ctrl',
reloadOnSearch: false,
})
.when('/alerting/notification/:id/edit', {
templateUrl: 'public/app/features/alerting/partials/notification_edit.html',
controller: 'AlertNotificationEditCtrl',
controllerAs: 'ctrl',
reloadOnSearch: false,
})
.otherwise({
templateUrl: 'public/app/partials/error.html',
controller: 'ErrorCtrl',
reloadOnSearch: false,
});
applyRouteRegistrationHandlers($routeProvider);
+1 -1
View File
@@ -76,7 +76,7 @@
.navbar-page-btn__folder {
display: none;
padding-right: 8px;
padding-right: 4px;
@include media-breakpoint-up(lg) {
display: inline-block;
+1 -1
View File
@@ -4,7 +4,7 @@ echo -e "Collecting code stats (typescript errors & more)"
ERROR_COUNT_LIMIT=788
ERROR_COUNT_LIMIT=771
DIRECTIVES_LIMIT=172
CONTROLLERS_LIMIT=139