瀏覽代碼

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

Torkel Ödegaard 7 年之前
父節點
當前提交
7b06800295

+ 1 - 2
public/app/core/actions/index.ts

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

+ 9 - 7
public/app/core/actions/navModel.ts

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

+ 2 - 2
public/app/core/reducers/index.ts

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

+ 18 - 51
public/app/core/reducers/navModel.ts

@@ -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();
-
-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];
+function buildNavIndex(navIndex: NavIndex, children: NavModelItem[], parentItem?: NavModelItem) {
+  for (const node of children) {
+    navIndex[node.id] = {
+      ...node,
+      parentItem: parentItem,
+    };
 
-      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;

+ 39 - 0
public/app/core/selectors/navModel.ts

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

+ 1 - 1
public/app/features/admin/apis/index.ts

@@ -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[]> => {

+ 4 - 3
public/app/features/admin/containers/ServerStats.test.tsx

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

+ 7 - 12
public/app/features/admin/containers/ServerStats.tsx

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

+ 18 - 69
public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap

@@ -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
-          </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
+            Total Users
           </td>
           <td>
-            0
+            1
           </td>
         </tr>
       </tbody>

+ 7 - 8
public/app/features/alerting/AlertRuleList.tsx

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

+ 1 - 1
public/app/features/alerting/state/ThresholdMapper.test.ts

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

+ 0 - 7
public/app/types/container.ts

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

+ 42 - 4
public/app/types/index.ts

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

+ 0 - 15
public/app/types/location.ts

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

+ 0 - 19
public/app/types/navModel.ts

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

+ 21 - 0
public/test/jest-setup.ts

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

+ 4 - 1
public/test/mocks/common.ts

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