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';
export { initNav, updateLocation };
export { updateLocation };

View File

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

View File

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

View File

@ -1,62 +1,29 @@
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';
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 buildInitialState(): NavIndex {
const navIndex: NavIndex = {};
const rootNodes = config.bootData.navTree as NavModelItem[];
buildNavIndex(navIndex, rootNodes);
return navIndex;
}
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 => {
switch (action.type) {
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,
};
if (node.children) {
buildNavIndex(navIndex, node.children, node);
}
}
}
export const initialState: NavIndex = buildInitialState();
export const navIndexReducer = (state = initialState, action: Action): NavIndex => {
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 {
name: string;
value: string;
value: number;
}
export const getServerStats = async (): Promise<ServerStat[]> => {

View File

@ -1,18 +1,19 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { ServerStats } from './ServerStats';
import { initNav } from 'test/mocks/common';
import { createNavModel } from 'test/mocks/common';
import { ServerStat } from '../apis';
describe('ServerStats', () => {
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 = () => {
return Promise.resolve(stats);
};
const page = renderer.create(<ServerStats initNav={initNav} getServerStats={getServerStats} />);
const page = renderer.create(<ServerStats navModel={navModel} getServerStats={getServerStats} />);
setTimeout(() => {
expect(page.toJSON()).toMatchSnapshot();

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { describe, it, expect } from 'test/lib/common';
import { ThresholdMapper } from './threshold_mapper';
import { ThresholdMapper } from './ThresholdMapper';
describe('ThresholdMapper', () => {
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';
import { ContainerProps } from './container';
import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location';
export interface LocationUpdate {
path?: string;
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;
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;
}
export function getNavModel(title: string, tabs: string[]): NavModel {
export function createNavModel(title: string, ...tabs: string[]): NavModel {
const node: NavModelItem = {
id: title,
text: title,
@ -38,9 +38,12 @@ export function getNavModel(title: string, tabs: string[]): NavModel {
subTitle: 'subTitle',
url: title,
text: title,
active: false,
});
}
node.children[0].active = true;
return {
node: node,
main: node,