refactor: changed nav store to use nav index and selector instead of initNav action

This commit is contained in:
Torkel Ödegaard 2018-09-02 10:36:36 -07:00
parent 2ac202b22f
commit 7b06800295
17 changed files with 174 additions and 202 deletions

View File

@ -1,4 +1,3 @@
import { initNav } from './navModel';
import { updateLocation } from './location'; import { updateLocation } from './location';
export { initNav, updateLocation }; export { updateLocation };

View File

@ -1,11 +1,13 @@
export type Action = InitNavModelAction; export type Action = UpdateNavIndexAction;
export interface InitNavModelAction { // this action is not used yet
type: 'INIT_NAV_MODEL'; // kind of just a placeholder, will be need for dynamic pages
args: string[]; // like datasource edit, teams edit page
export interface UpdateNavIndexAction {
type: 'UPDATE_NAV_INDEX';
} }
export const initNav = (...args: string[]): InitNavModelAction => ({ export const updateNavIndex = (): UpdateNavIndexAction => ({
type: 'INIT_NAV_MODEL', type: 'UPDATE_NAV_INDEX',
args: args,
}); });

View File

@ -1,7 +1,7 @@
import navModel from './navModel'; import { navIndexReducer as navIndex } from './navModel';
import location from './location'; import location from './location';
export default { export default {
navModel, navIndex,
location, location,
}; };

View File

@ -1,62 +1,29 @@
import { Action } from 'app/core/actions/navModel'; import { Action } from 'app/core/actions/navModel';
import { NavModel, NavModelItem } from 'app/types'; import { NavModelItem, NavIndex } from 'app/types';
import config from 'app/core/config'; import config from 'app/core/config';
function getNotFoundModel(): NavModel { export function buildInitialState(): NavIndex {
var node: NavModelItem = { const navIndex: NavIndex = {};
id: 'not-found', const rootNodes = config.bootData.navTree as NavModelItem[];
text: 'Page not found', buildNavIndex(navIndex, rootNodes);
icon: 'fa fa-fw fa-warning', return navIndex;
subTitle: '404 Error',
url: 'not-found',
};
return {
node: node,
main: node,
};
} }
export const initialState: NavModel = getNotFoundModel(); function buildNavIndex(navIndex: NavIndex, children: NavModelItem[], parentItem?: NavModelItem) {
for (const node of children) {
navIndex[node.id] = {
...node,
parentItem: parentItem,
};
const navModelReducer = (state = initialState, action: Action): NavModel => { if (node.children) {
switch (action.type) { buildNavIndex(navIndex, node.children, node);
case 'INIT_NAV_MODEL': {
let children = config.bootData.navTree as NavModelItem[];
let main, node;
const parents = [];
for (const id of action.args) {
node = children.find(el => el.id === id);
if (!node) {
throw new Error(`NavItem with id ${id} not found`);
}
children = node.children;
parents.push(node);
}
main = parents[parents.length - 2];
if (main.children) {
for (const item of main.children) {
item.active = false;
if (item.url === node.url) {
item.active = true;
}
}
}
return {
main: main,
node: node,
};
} }
} }
}
export const initialState: NavIndex = buildInitialState();
export const navIndexReducer = (state = initialState, action: Action): NavIndex => {
return state; return state;
}; };
export default navModelReducer;

View File

@ -0,0 +1,39 @@
import { NavModel, NavModelItem, NavIndex } from 'app/types';
function getNotFoundModel(): NavModel {
var node: NavModelItem = {
id: 'not-found',
text: 'Page not found',
icon: 'fa fa-fw fa-warning',
subTitle: '404 Error',
url: 'not-found',
};
return {
node: node,
main: node,
};
}
export function selectNavNode(navIndex: NavIndex, id: string): NavModel {
if (navIndex[id]) {
const node = navIndex[id];
const main = {
...node.parentItem,
};
main.children = main.children.map(item => {
return {
...item,
active: item.url === node.url,
};
});
return {
node: node,
main: main,
};
} else {
return getNotFoundModel();
}
}

View File

@ -2,7 +2,7 @@ import { getBackendSrv } from 'app/core/services/backend_srv';
export interface ServerStat { export interface ServerStat {
name: string; name: string;
value: string; value: number;
} }
export const getServerStats = async (): Promise<ServerStat[]> => { export const getServerStats = async (): Promise<ServerStat[]> => {

View File

@ -1,18 +1,19 @@
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { ServerStats } from './ServerStats'; import { ServerStats } from './ServerStats';
import { initNav } from 'test/mocks/common'; import { createNavModel } from 'test/mocks/common';
import { ServerStat } from '../apis'; import { ServerStat } from '../apis';
describe('ServerStats', () => { describe('ServerStats', () => {
it('Should render table with stats', done => { it('Should render table with stats', done => {
const stats: ServerStat[] = [{ name: 'test', value: 'asd' }]; const navModel = createNavModel('Admin', 'stats');
const stats: ServerStat[] = [{ name: 'Total dashboards', value: 10 }, { name: 'Total Users', value: 1 }];
let getServerStats = () => { let getServerStats = () => {
return Promise.resolve(stats); return Promise.resolve(stats);
}; };
const page = renderer.create(<ServerStats initNav={initNav} getServerStats={getServerStats} />); const page = renderer.create(<ServerStats navModel={navModel} getServerStats={getServerStats} />);
setTimeout(() => { setTimeout(() => {
expect(page.toJSON()).toMatchSnapshot(); expect(page.toJSON()).toMatchSnapshot();

View File

@ -1,12 +1,13 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { initNav } from 'app/core/actions'; import { NavModel, StoreState } from 'app/types';
import { ContainerProps } from 'app/types'; import { selectNavNode } from 'app/core/selectors/navModel';
import { getServerStats, ServerStat } from '../apis'; import { getServerStats, ServerStat } from '../apis';
import PageHeader from 'app/core/components/PageHeader/PageHeader'; import PageHeader from 'app/core/components/PageHeader/PageHeader';
interface Props extends ContainerProps { interface Props {
navModel: NavModel;
getServerStats: () => Promise<ServerStat[]>; getServerStats: () => Promise<ServerStat[]>;
} }
@ -21,8 +22,6 @@ export class ServerStats extends PureComponent<Props, State> {
this.state = { this.state = {
stats: [], stats: [],
}; };
this.props.initNav('cfg', 'admin', 'server-stats');
} }
async componentDidMount() { async componentDidMount() {
@ -66,13 +65,9 @@ function StatItem(stat: ServerStat) {
); );
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: StoreState) => ({
navModel: state.navModel, navModel: selectNavNode(state.navIndex, 'server-stats'),
getServerStats: getServerStats, getServerStats: getServerStats,
}); });
const mapDispatchToProps = { export default hot(module)(connect(mapStateToProps)(ServerStats));
initNav,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ServerStats));

View File

@ -17,8 +17,9 @@ exports[`ServerStats Should render table with stats 1`] = `
<span <span
className="page-header__logo" className="page-header__logo"
> >
<i
className="page-header__icon fa fa-fw fa-warning"
/>
</span> </span>
<div <div
className="page-header__info-block" className="page-header__info-block"
@ -26,9 +27,13 @@ exports[`ServerStats Should render table with stats 1`] = `
<h1 <h1
className="page-header__title" className="page-header__title"
> >
admin-Text Admin
</h1> </h1>
<div
className="page-header__sub-title"
>
subTitle
</div>
</div> </div>
</div> </div>
<nav> <nav>
@ -36,19 +41,19 @@ exports[`ServerStats Should render table with stats 1`] = `
className="gf-form-select-wrapper width-20 page-header__select-nav" className="gf-form-select-wrapper width-20 page-header__select-nav"
> >
<label <label
className="gf-form-select-icon " className="gf-form-select-icon icon"
htmlFor="page-header-select-nav" htmlFor="page-header-select-nav"
/> />
<select <select
className="gf-select-nav gf-form-input" className="gf-select-nav gf-form-input"
id="page-header-select-nav" id="page-header-select-nav"
onChange={[Function]} onChange={[Function]}
value="/url/server-stats" value="Admin"
> >
<option <option
value="/url/server-stats" value="Admin"
> >
server-stats-Text Admin
</option> </option>
</select> </select>
</div> </div>
@ -60,13 +65,13 @@ exports[`ServerStats Should render table with stats 1`] = `
> >
<a <a
className="gf-tabs-link active" className="gf-tabs-link active"
href="/url/server-stats" href="Admin"
target={undefined} target={undefined}
> >
<i <i
className="" className="icon"
/> />
server-stats-Text Admin
</a> </a>
</li> </li>
</ul> </ul>
@ -101,66 +106,10 @@ exports[`ServerStats Should render table with stats 1`] = `
</tr> </tr>
<tr> <tr>
<td> <td>
Total users Total Users
</td> </td>
<td> <td>
0 1
</td>
</tr>
<tr>
<td>
Active users (seen last 30 days)
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Total orgs
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Total playlists
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Total snapshots
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Total dashboard tags
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Total starred dashboards
</td>
<td>
0
</td>
</tr>
<tr>
<td>
Total alerts
</td>
<td>
0
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@ -5,11 +5,13 @@ import classNames from 'classnames';
import PageHeader from 'app/core/components/PageHeader/PageHeader'; import PageHeader from 'app/core/components/PageHeader/PageHeader';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
import { initNav, updateLocation } from 'app/core/actions'; import { updateLocation } from 'app/core/actions';
import { ContainerProps } from 'app/types'; import { selectNavNode } from 'app/core/selectors/navModel';
import { NavModel, StoreState } from 'app/types';
import { getAlertRules, AlertRule } from './state/apis'; import { getAlertRules, AlertRule } from './state/apis';
interface Props extends ContainerProps { interface Props {
navModel: NavModel;
updateLocation: typeof updateLocation; updateLocation: typeof updateLocation;
} }
@ -37,8 +39,6 @@ export class AlertRuleList extends PureComponent<Props, State> {
search: '', search: '',
stateFilter: '', stateFilter: '',
}; };
this.props.initNav('alerting', 'alert-list');
} }
componentDidMount() { componentDidMount() {
@ -200,12 +200,11 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: StoreState) => ({
navModel: state.navModel, navModel: selectNavNode(state.navIndex, 'alert-list'),
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
initNav,
updateLocation, updateLocation,
}; };

View File

@ -1,6 +1,6 @@
import { describe, it, expect } from 'test/lib/common'; import { describe, it, expect } from 'test/lib/common';
import { ThresholdMapper } from './threshold_mapper'; import { ThresholdMapper } from './ThresholdMapper';
describe('ThresholdMapper', () => { describe('ThresholdMapper', () => {
describe('with greater than evaluator', () => { describe('with greater than evaluator', () => {

View File

@ -1,7 +0,0 @@
import { NavModel } from './navModel';
import { initNav } from 'app/core/actions';
export interface ContainerProps {
navModel: NavModel;
initNav: typeof initNav;
}

View File

@ -1,5 +1,43 @@
import { NavModel, NavModelItem } from './navModel'; export interface LocationUpdate {
import { ContainerProps } from './container'; path?: string;
import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location'; query?: UrlQueryMap;
routeParams?: UrlQueryMap;
}
export { NavModel, NavModelItem, ContainerProps, LocationState, LocationUpdate, UrlQueryValue, UrlQueryMap }; export interface LocationState {
url: string;
path: string;
query: UrlQueryMap;
routeParams: UrlQueryMap;
}
export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[];
export type UrlQueryMap = { [s: string]: UrlQueryValue };
export interface NavModelItem {
text: string;
url: string;
subTitle?: string;
icon?: string;
img?: string;
id: string;
active?: boolean;
hideFromTabs?: boolean;
divider?: boolean;
children?: NavModelItem[];
breadcrumbs?: NavModelItem[];
target?: string;
parentItem?: NavModelItem;
}
export interface NavModel {
main: NavModelItem;
node: NavModelItem;
}
export type NavIndex = { [s: string]: NavModelItem };
export interface StoreState {
navIndex: NavIndex;
location: LocationState;
}

View File

@ -1,15 +0,0 @@
export interface LocationUpdate {
path?: string;
query?: UrlQueryMap;
routeParams?: UrlQueryMap;
}
export interface LocationState {
url: string;
path: string;
query: UrlQueryMap;
routeParams: UrlQueryMap;
}
export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[];
export type UrlQueryMap = { [s: string]: UrlQueryValue };

View File

@ -1,19 +0,0 @@
export interface NavModelItem {
text: string;
url: string;
subTitle?: string;
icon?: string;
img?: string;
id: string;
active?: boolean;
hideFromTabs?: boolean;
divider?: boolean;
children?: NavModelItem[];
breadcrumbs?: NavModelItem[];
target?: string;
}
export interface NavModel {
main: NavModelItem;
node: NavModelItem;
}

View File

@ -20,3 +20,24 @@ configure({ adapter: new Adapter() });
const global = <any>window; const global = <any>window;
global.$ = global.jQuery = $; global.$ = global.jQuery = $;
const localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key];
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
},
removeItem: function(key) {
delete store[key];
},
};
})();
global.localStorage = localStorageMock;
// Object.defineProperty(window, 'localStorage', { value: localStorageMock });

View File

@ -20,7 +20,7 @@ export function createNavTree(...args) {
return root; return root;
} }
export function getNavModel(title: string, tabs: string[]): NavModel { export function createNavModel(title: string, ...tabs: string[]): NavModel {
const node: NavModelItem = { const node: NavModelItem = {
id: title, id: title,
text: title, text: title,
@ -38,9 +38,12 @@ export function getNavModel(title: string, tabs: string[]): NavModel {
subTitle: 'subTitle', subTitle: 'subTitle',
url: title, url: title,
text: title, text: title,
active: false,
}); });
} }
node.children[0].active = true;
return { return {
node: node, node: node,
main: node, main: node,