From 3740d564913ac5fe9f9d1b4e5e80ba3881457622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Jul 2018 09:17:38 +0200 Subject: [PATCH 01/27] wip: redux poc --- package.json | 9 ++++-- public/app/store/configureStore.dev.ts | 11 +++++++ public/app/store/configureStore.prod.ts | 9 ++++++ public/app/store/configureStore.ts | 5 +++ public/app/store/nav/nav.ts | 0 public/app/store/rootReducer.ts | 23 ++++++++++++++ yarn.lock | 42 +++++++++++++++++++++++-- 7 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 public/app/store/configureStore.dev.ts create mode 100644 public/app/store/configureStore.prod.ts create mode 100644 public/app/store/configureStore.ts create mode 100644 public/app/store/nav/nav.ts create mode 100644 public/app/store/rootReducer.ts diff --git a/package.json b/package.json index a43b2adc5be..3523b9eac6d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "expose-loader": "^0.7.3", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "^1.1.11", - "fork-ts-checker-webpack-plugin": "^0.4.1", + "fork-ts-checker-webpack-plugin": "^0.4.2", "gaze": "^1.1.2", "glob": "~7.0.0", "grunt": "1.0.1", @@ -90,15 +90,14 @@ "style-loader": "^0.21.0", "systemjs": "0.20.19", "systemjs-plugin-css": "^0.1.36", - "ts-loader": "^4.3.0", "ts-jest": "^22.4.6", + "ts-loader": "^4.3.0", "tslint": "^5.8.0", "tslint-loader": "^3.5.3", "typescript": "^2.6.2", "webpack": "^4.8.0", "webpack-bundle-analyzer": "^2.9.0", "webpack-cleanup-plugin": "^0.5.1", - "fork-ts-checker-webpack-plugin": "^0.4.2", "webpack-cli": "^2.1.4", "webpack-dev-server": "^3.1.0", "webpack-merge": "^4.1.0", @@ -170,9 +169,13 @@ "react-grid-layout": "0.16.6", "react-highlight-words": "^0.10.0", "react-popper": "^0.7.5", + "react-redux": "^5.0.7", "react-select": "^1.1.0", "react-sizeme": "^2.3.6", "react-transition-group": "^2.2.1", + "redux": "^4.0.0", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.3.0", "remarkable": "^1.7.1", "rst2html": "github:thoward/rst2html#990cb89", "rxjs": "^5.4.3", diff --git a/public/app/store/configureStore.dev.ts b/public/app/store/configureStore.dev.ts new file mode 100644 index 00000000000..98b1ca19634 --- /dev/null +++ b/public/app/store/configureStore.dev.ts @@ -0,0 +1,11 @@ +import { createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; +import { createLogger } from 'redux-logger'; +import rootReducer from './reducers'; + +export let store; + +export function configureStore() { + const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + store = createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger()))); +} diff --git a/public/app/store/configureStore.prod.ts b/public/app/store/configureStore.prod.ts new file mode 100644 index 00000000000..3c75e5b850b --- /dev/null +++ b/public/app/store/configureStore.prod.ts @@ -0,0 +1,9 @@ +import { createStore, applyMiddleware, compose } from 'redux'; +import thunk from 'redux-thunk'; +import rootReducer from './reducers'; + +export let store; + +export function configureStore() { + store = createStore(rootReducer, {}, compose(applyMiddleware(thunk))); +} diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts new file mode 100644 index 00000000000..78c9ea1fdc0 --- /dev/null +++ b/public/app/store/configureStore.ts @@ -0,0 +1,5 @@ +if (process.env.NODE_ENV === 'production') { + module.exports = require('./configureStore.prod'); +} else { + module.exports = require('./configureStore.dev'); +} diff --git a/public/app/store/nav/nav.ts b/public/app/store/nav/nav.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/public/app/store/rootReducer.ts b/public/app/store/rootReducer.ts new file mode 100644 index 00000000000..2e6d4cb53cd --- /dev/null +++ b/public/app/store/rootReducer.ts @@ -0,0 +1,23 @@ +import * as ActionTypes from '../actions'; +import { combineReducers } from 'redux'; +import { nav } from './nav'; + +// Updates error message to notify about the failed fetches. +const errorMessage = (state = null, action) => { + const { type, error } = action; + + if (type === ActionTypes.RESET_ERROR_MESSAGE) { + return null; + } else if (error) { + return error; + } + + return state; +}; + +const rootReducer = combineReducers({ + nav, + errorMessage, +}); + +export default rootReducer; diff --git a/yarn.lock b/yarn.lock index 6772d7c14a4..09fc26c2742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3293,6 +3293,10 @@ dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + deep-equal@*, deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -5885,7 +5889,7 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -7343,6 +7347,10 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" +lodash-es@^4.17.5: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" + lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -7974,7 +7982,7 @@ mocha@^4.0.1: mkdirp "0.5.1" supports-color "4.4.0" -moment@^2.18.1: +moment@^2.22.2: version "2.22.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" @@ -10074,6 +10082,17 @@ react-reconciler@^0.7.0: object-assign "^4.1.1" prop-types "^15.6.0" +react-redux@^5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8" + dependencies: + hoist-non-react-statics "^2.5.0" + invariant "^2.0.0" + lodash "^4.17.5" + lodash-es "^4.17.5" + loose-envify "^1.1.0" + prop-types "^15.6.0" + react-resizable@1.x: version "1.7.5" resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e" @@ -10337,6 +10356,23 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + dependencies: + deep-diff "^0.3.5" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + +redux@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03" + dependencies: + loose-envify "^1.1.0" + symbol-observable "^1.2.0" + regenerate@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" @@ -11723,7 +11759,7 @@ symbol-observable@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" -symbol-observable@^1.1.0: +symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" From d85fa66fb475ab96682fdd988a80e46584c3e363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Jul 2018 10:28:20 +0200 Subject: [PATCH 02/27] redid redux poc, old branch was to old and caused to many conflicts --- .../containers/ServerStats/ServerStats.tsx | 4 +++ public/app/core/components/grafana_app.ts | 2 ++ public/app/store/configureStore.dev.ts | 11 ------- public/app/store/configureStore.prod.ts | 9 ------ public/app/store/configureStore.ts | 18 ++++++++--- public/app/store/nav/actions.ts | 30 +++++++++++++++++++ public/app/store/nav/nav.ts | 0 public/app/store/nav/reducers.ts | 30 +++++++++++++++++++ public/app/store/rootReducer.ts | 23 -------------- 9 files changed, 80 insertions(+), 47 deletions(-) delete mode 100644 public/app/store/configureStore.dev.ts delete mode 100644 public/app/store/configureStore.prod.ts create mode 100644 public/app/store/nav/actions.ts delete mode 100644 public/app/store/nav/nav.ts create mode 100644 public/app/store/nav/reducers.ts delete mode 100644 public/app/store/rootReducer.ts diff --git a/public/app/containers/ServerStats/ServerStats.tsx b/public/app/containers/ServerStats/ServerStats.tsx index 761b296855f..bed86b43160 100644 --- a/public/app/containers/ServerStats/ServerStats.tsx +++ b/public/app/containers/ServerStats/ServerStats.tsx @@ -3,6 +3,8 @@ import { hot } from 'react-hot-loader'; import { inject, observer } from 'mobx-react'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; import IContainerProps from 'app/containers/IContainerProps'; +import { store } from 'app/store/configureStore'; +import { setNav } from 'app/store/nav/actions'; @inject('nav', 'serverStats') @observer @@ -13,6 +15,8 @@ export class ServerStats extends React.Component { nav.load('cfg', 'admin', 'server-stats'); serverStats.load(); + + store.dispatch(setNav('new', { asd: 'tasd' })); } render() { diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index fd2e32db3a7..fa2c96ade32 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -10,6 +10,7 @@ import { createStore } from 'app/stores/store'; import colors from 'app/core/utils/colors'; import { BackendSrv } from 'app/core/services/backend_srv'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { configureStore } from 'app/store/configureStore'; export class GrafanaCtrl { /** @ngInject */ @@ -24,6 +25,7 @@ export class GrafanaCtrl { backendSrv: BackendSrv, datasourceSrv: DatasourceSrv ) { + configureStore(); createStore({ backendSrv, datasourceSrv }); $scope.init = function() { diff --git a/public/app/store/configureStore.dev.ts b/public/app/store/configureStore.dev.ts deleted file mode 100644 index 98b1ca19634..00000000000 --- a/public/app/store/configureStore.dev.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createStore, applyMiddleware, compose } from 'redux'; -import thunk from 'redux-thunk'; -import { createLogger } from 'redux-logger'; -import rootReducer from './reducers'; - -export let store; - -export function configureStore() { - const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - store = createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger()))); -} diff --git a/public/app/store/configureStore.prod.ts b/public/app/store/configureStore.prod.ts deleted file mode 100644 index 3c75e5b850b..00000000000 --- a/public/app/store/configureStore.prod.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createStore, applyMiddleware, compose } from 'redux'; -import thunk from 'redux-thunk'; -import rootReducer from './reducers'; - -export let store; - -export function configureStore() { - store = createStore(rootReducer, {}, compose(applyMiddleware(thunk))); -} diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 78c9ea1fdc0..a0dfe576ed6 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,5 +1,15 @@ -if (process.env.NODE_ENV === 'production') { - module.exports = require('./configureStore.prod'); -} else { - module.exports = require('./configureStore.dev'); +import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; +import thunk from 'redux-thunk'; +import { createLogger } from 'redux-logger'; +import { navReducer } from './nav/reducers'; + +const rootReducer = combineReducers({ + nav: navReducer, +}); + +export let store; + +export function configureStore() { + const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + store = createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger()))); } diff --git a/public/app/store/nav/actions.ts b/public/app/store/nav/actions.ts new file mode 100644 index 00000000000..eca99cc2b90 --- /dev/null +++ b/public/app/store/nav/actions.ts @@ -0,0 +1,30 @@ +// +// Only test actions to test redux & typescript +// + +export enum ActionTypes { + SET_NAV = 'SET_NAV', + SET_QUERY = 'SET_QUERY', +} + +export interface SetNavAction { + type: ActionTypes.SET_NAV; + payload: { + path: string; + query: object; + }; +} + +export interface SetQueryAction { + type: ActionTypes.SET_QUERY; + payload: { + query: object; + }; +} + +export type Action = SetNavAction | SetQueryAction; + +export const setNav = (path: string, query: object): SetNavAction => ({ + type: ActionTypes.SET_NAV, + payload: { path: path, query: query }, +}); diff --git a/public/app/store/nav/nav.ts b/public/app/store/nav/nav.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/public/app/store/nav/reducers.ts b/public/app/store/nav/reducers.ts new file mode 100644 index 00000000000..6e9d6e713a0 --- /dev/null +++ b/public/app/store/nav/reducers.ts @@ -0,0 +1,30 @@ +import { Action, ActionTypes } from './actions'; + +export interface NavState { + path: string; + query: object; +} + +const initialState: NavState = { + path: '/test', + query: {}, +}; + +export const navReducer = (state: NavState = initialState, action: Action): NavState => { + switch (action.type) { + case ActionTypes.SET_NAV: { + return { ...state, path: action.payload.path, query: action.payload.query }; + } + + case ActionTypes.SET_QUERY: { + return { + ...state, + query: action.payload.query, + }; + } + + default: { + return state; + } + } +}; diff --git a/public/app/store/rootReducer.ts b/public/app/store/rootReducer.ts deleted file mode 100644 index 2e6d4cb53cd..00000000000 --- a/public/app/store/rootReducer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as ActionTypes from '../actions'; -import { combineReducers } from 'redux'; -import { nav } from './nav'; - -// Updates error message to notify about the failed fetches. -const errorMessage = (state = null, action) => { - const { type, error } = action; - - if (type === ActionTypes.RESET_ERROR_MESSAGE) { - return null; - } else if (error) { - return error; - } - - return state; -}; - -const rootReducer = combineReducers({ - nav, - errorMessage, -}); - -export default rootReducer; From cf58eea1dbbc40df3a2c5ca07a31e343e226c728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 31 Aug 2018 13:24:36 +0200 Subject: [PATCH 03/27] redux: wip progress for using redux --- docker/blocks/openldap/ldap_dev.toml | 1 + .../containers/ServerStats/ServerStats.tsx | 52 -------------- public/app/core/actions/index.ts | 3 + public/app/core/actions/navModel.ts | 11 +++ .../core/components/PageHeader/PageHeader.tsx | 2 +- public/app/core/components/grafana_app.ts | 2 +- public/app/core/reducers/index.ts | 5 ++ public/app/core/reducers/navModel.ts | 64 +++++++++++++++++ .../server-stats}/ServerStats.test.tsx | 0 .../app/features/server-stats/ServerStats.tsx | 69 +++++++++++++++++++ .../__snapshots__/ServerStats.test.tsx.snap | 0 public/app/routes/ReactContainer.tsx | 10 ++- public/app/routes/routes.ts | 2 +- public/app/store/nav/actions.ts | 30 -------- public/app/store/nav/reducers.ts | 30 -------- .../app/{store => stores}/configureStore.ts | 4 +- public/app/types/container.ts | 6 ++ public/app/types/index.ts | 4 ++ public/app/types/navModel.ts | 19 +++++ 19 files changed, 194 insertions(+), 120 deletions(-) delete mode 100644 public/app/containers/ServerStats/ServerStats.tsx create mode 100644 public/app/core/actions/index.ts create mode 100644 public/app/core/actions/navModel.ts create mode 100644 public/app/core/reducers/index.ts create mode 100644 public/app/core/reducers/navModel.ts rename public/app/{containers/ServerStats => features/server-stats}/ServerStats.test.tsx (100%) create mode 100644 public/app/features/server-stats/ServerStats.tsx rename public/app/{containers/ServerStats => features/server-stats}/__snapshots__/ServerStats.test.tsx.snap (100%) delete mode 100644 public/app/store/nav/actions.ts delete mode 100644 public/app/store/nav/reducers.ts rename public/app/{store => stores}/configureStore.ts (86%) create mode 100644 public/app/types/container.ts create mode 100644 public/app/types/index.ts create mode 100644 public/app/types/navModel.ts diff --git a/docker/blocks/openldap/ldap_dev.toml b/docker/blocks/openldap/ldap_dev.toml index e79771b57de..8767ff3c64a 100644 --- a/docker/blocks/openldap/ldap_dev.toml +++ b/docker/blocks/openldap/ldap_dev.toml @@ -72,6 +72,7 @@ email = "email" [[servers.group_mappings]] group_dn = "cn=admins,ou=groups,dc=grafana,dc=org" org_role = "Admin" +grafana_admin = true # The Grafana organization database id, optional, if left out the default org (id 1) will be used # org_id = 1 diff --git a/public/app/containers/ServerStats/ServerStats.tsx b/public/app/containers/ServerStats/ServerStats.tsx deleted file mode 100644 index fe3ef0ecfd1..00000000000 --- a/public/app/containers/ServerStats/ServerStats.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { hot } from 'react-hot-loader'; -import { inject, observer } from 'mobx-react'; -import PageHeader from 'app/core/components/PageHeader/PageHeader'; -import { store } from 'app/store/configureStore'; -import { setNav } from 'app/store/nav/actions'; -import ContainerProps from 'app/containers/ContainerProps'; - -@inject('nav', 'serverStats') -@observer -export class ServerStats extends React.Component { - constructor(props) { - super(props); - const { nav, serverStats } = this.props; - - nav.load('cfg', 'admin', 'server-stats'); - serverStats.load(); - - store.dispatch(setNav('new', { asd: 'tasd' })); - } - - render() { - const { nav, serverStats } = this.props; - return ( -
- -
- - - - - - - - {serverStats.stats.map(StatItem)} -
NameValue
-
-
- ); - } -} - -function StatItem(stat) { - return ( - - {stat.name} - {stat.value} - - ); -} - -export default hot(module)(ServerStats); diff --git a/public/app/core/actions/index.ts b/public/app/core/actions/index.ts new file mode 100644 index 00000000000..3c23dbbbe54 --- /dev/null +++ b/public/app/core/actions/index.ts @@ -0,0 +1,3 @@ +import { initNav } from './navModel'; + +export { initNav }; diff --git a/public/app/core/actions/navModel.ts b/public/app/core/actions/navModel.ts new file mode 100644 index 00000000000..048afd4f8ff --- /dev/null +++ b/public/app/core/actions/navModel.ts @@ -0,0 +1,11 @@ +export type Action = InitNavModelAction; + +export interface InitNavModelAction { + type: 'INIT_NAV_MODEL'; + args: string[]; +} + +export const initNav = (...args: string[]): InitNavModelAction => ({ + type: 'INIT_NAV_MODEL', + args: args, +}); diff --git a/public/app/core/components/PageHeader/PageHeader.tsx b/public/app/core/components/PageHeader/PageHeader.tsx index b7bef2495bb..9feddde68ce 100644 --- a/public/app/core/components/PageHeader/PageHeader.tsx +++ b/public/app/core/components/PageHeader/PageHeader.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { observer } from 'mobx-react'; -import { NavModel, NavModelItem } from '../../nav_model_srv'; +import { NavModel, NavModelItem } from 'app/types'; import classNames from 'classnames'; import appEvents from 'app/core/app_events'; import { toJS } from 'mobx'; diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index c1cd0e2b5f2..085f0db0a6d 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -10,7 +10,7 @@ import { createStore } from 'app/stores/store'; import colors from 'app/core/utils/colors'; import { BackendSrv, setBackendSrv } from 'app/core/services/backend_srv'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { configureStore } from 'app/store/configureStore'; +import { configureStore } from 'app/stores/configureStore'; export class GrafanaCtrl { /** @ngInject */ diff --git a/public/app/core/reducers/index.ts b/public/app/core/reducers/index.ts new file mode 100644 index 00000000000..0779111c16e --- /dev/null +++ b/public/app/core/reducers/index.ts @@ -0,0 +1,5 @@ +import navModel from './navModel'; + +export default { + navModel, +}; diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts new file mode 100644 index 00000000000..c00441c4881 --- /dev/null +++ b/public/app/core/reducers/navModel.ts @@ -0,0 +1,64 @@ +import { Action } from 'app/core/actions/navModel'; +import { NavModel, NavModelItem } 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 { + breadcrumbs: [node], + node: node, + main: node, + }; +} + +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]; + + 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, + breadcrumbs: [], + }; + } + } + + return state; +}; + +export default navModelReducer; diff --git a/public/app/containers/ServerStats/ServerStats.test.tsx b/public/app/features/server-stats/ServerStats.test.tsx similarity index 100% rename from public/app/containers/ServerStats/ServerStats.test.tsx rename to public/app/features/server-stats/ServerStats.test.tsx diff --git a/public/app/features/server-stats/ServerStats.tsx b/public/app/features/server-stats/ServerStats.tsx new file mode 100644 index 00000000000..b499fb725a8 --- /dev/null +++ b/public/app/features/server-stats/ServerStats.tsx @@ -0,0 +1,69 @@ +import React 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 PageHeader from 'app/core/components/PageHeader/PageHeader'; + +interface Props extends ContainerProps {} + +export class ServerStats extends React.Component { + constructor(props) { + super(props); + + this.props.initNav('cfg', 'admin', 'server-stats'); + // const { nav, serverStats } = this.props; + // + // nav.load('cfg', 'admin', 'server-stats'); + // serverStats.load(); + // + // store.dispatch(setNav('new', { asd: 'tasd' })); + } + + render() { + const { navModel } = this.props; + console.log('render', navModel); + return ( +
+ +

aasd

+
+ ); + // const { nav, serverStats } = this.props; + // return ( + //
+ // + //
+ // + // + // + // + // + // + // + // {serverStats.stats.map(StatItem)} + //
NameValue
+ //
+ //
+ // ); + } +} + +function StatItem(stat) { + return ( + + {stat.name} + {stat.value} + + ); +} + +const mapStateToProps = state => ({ + navModel: state.navModel, +}); + +const mapDispatchToProps = { + initNav, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ServerStats)); diff --git a/public/app/containers/ServerStats/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/server-stats/__snapshots__/ServerStats.test.tsx.snap similarity index 100% rename from public/app/containers/ServerStats/__snapshots__/ServerStats.test.tsx.snap rename to public/app/features/server-stats/__snapshots__/ServerStats.test.tsx.snap diff --git a/public/app/routes/ReactContainer.tsx b/public/app/routes/ReactContainer.tsx index b161a5e7a87..3ed534da587 100644 --- a/public/app/routes/ReactContainer.tsx +++ b/public/app/routes/ReactContainer.tsx @@ -1,18 +1,22 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'mobx-react'; +import { Provider as ReduxProvider } from 'react-redux'; import coreModule from 'app/core/core_module'; import { store } from 'app/stores/store'; +import { store as reduxStore } from 'app/stores/configureStore'; import { BackendSrv } from 'app/core/services/backend_srv'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { ContextSrv } from 'app/core/services/context_srv'; function WrapInProvider(store, Component, props) { return ( - - - + + + + + ); } diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index d12711aca5b..7fcab26645f 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -1,7 +1,7 @@ import './dashboard_loaders'; import './ReactContainer'; -import ServerStats from 'app/containers/ServerStats/ServerStats'; +import ServerStats from 'app/features/server-stats/ServerStats'; import AlertRuleList from 'app/containers/AlertRuleList/AlertRuleList'; import FolderSettings from 'app/containers/ManageDashboards/FolderSettings'; import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions'; diff --git a/public/app/store/nav/actions.ts b/public/app/store/nav/actions.ts deleted file mode 100644 index eca99cc2b90..00000000000 --- a/public/app/store/nav/actions.ts +++ /dev/null @@ -1,30 +0,0 @@ -// -// Only test actions to test redux & typescript -// - -export enum ActionTypes { - SET_NAV = 'SET_NAV', - SET_QUERY = 'SET_QUERY', -} - -export interface SetNavAction { - type: ActionTypes.SET_NAV; - payload: { - path: string; - query: object; - }; -} - -export interface SetQueryAction { - type: ActionTypes.SET_QUERY; - payload: { - query: object; - }; -} - -export type Action = SetNavAction | SetQueryAction; - -export const setNav = (path: string, query: object): SetNavAction => ({ - type: ActionTypes.SET_NAV, - payload: { path: path, query: query }, -}); diff --git a/public/app/store/nav/reducers.ts b/public/app/store/nav/reducers.ts deleted file mode 100644 index 6e9d6e713a0..00000000000 --- a/public/app/store/nav/reducers.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Action, ActionTypes } from './actions'; - -export interface NavState { - path: string; - query: object; -} - -const initialState: NavState = { - path: '/test', - query: {}, -}; - -export const navReducer = (state: NavState = initialState, action: Action): NavState => { - switch (action.type) { - case ActionTypes.SET_NAV: { - return { ...state, path: action.payload.path, query: action.payload.query }; - } - - case ActionTypes.SET_QUERY: { - return { - ...state, - query: action.payload.query, - }; - } - - default: { - return state; - } - } -}; diff --git a/public/app/store/configureStore.ts b/public/app/stores/configureStore.ts similarity index 86% rename from public/app/store/configureStore.ts rename to public/app/stores/configureStore.ts index a0dfe576ed6..3a7d16da76d 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/stores/configureStore.ts @@ -1,10 +1,10 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; import { createLogger } from 'redux-logger'; -import { navReducer } from './nav/reducers'; +import sharedReducers from 'app/core/reducers'; const rootReducer = combineReducers({ - nav: navReducer, + ...sharedReducers }); export let store; diff --git a/public/app/types/container.ts b/public/app/types/container.ts new file mode 100644 index 00000000000..174bc0c8460 --- /dev/null +++ b/public/app/types/container.ts @@ -0,0 +1,6 @@ +import { NavModel } from './navModel'; + +export interface ContainerProps { + navModel: NavModel; + initNav: (...args: string[]) => void; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts new file mode 100644 index 00000000000..43d921e3964 --- /dev/null +++ b/public/app/types/index.ts @@ -0,0 +1,4 @@ +import { NavModel, NavModelItem } from './navModel'; +import { ContainerProps } from './container'; + +export { NavModel, NavModelItem, ContainerProps }; diff --git a/public/app/types/navModel.ts b/public/app/types/navModel.ts new file mode 100644 index 00000000000..e1a4265847c --- /dev/null +++ b/public/app/types/navModel.ts @@ -0,0 +1,19 @@ +export interface NavModelItem { + text: string; + url: string; + subTitle?: string; + icon?: string; + img?: string; + id: string; + active?: boolean; + hideFromTabs?: boolean; + divider?: boolean; + children?: NavModelItem[]; + target?: string; +} + +export interface NavModel { + breadcrumbs: NavModelItem[]; + main: NavModelItem; + node: NavModelItem; +} From d68007fde37665ff847645bcf22191c5ec7c4fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 31 Aug 2018 14:38:23 +0200 Subject: [PATCH 04/27] wip: redux --- .../app/features/server-stats/ServerStats.tsx | 65 +++++++++++-------- public/app/features/server-stats/api.ts | 26 ++++++++ 2 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 public/app/features/server-stats/api.ts diff --git a/public/app/features/server-stats/ServerStats.tsx b/public/app/features/server-stats/ServerStats.tsx index b499fb725a8..da1fb6e76f7 100644 --- a/public/app/features/server-stats/ServerStats.tsx +++ b/public/app/features/server-stats/ServerStats.tsx @@ -3,53 +3,61 @@ import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import { initNav } from 'app/core/actions'; import { ContainerProps } from 'app/types'; +import { getServerStats, ServerStat } from './api'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; -interface Props extends ContainerProps {} +interface Props extends ContainerProps { + getServerStats: () => Promise; +} + +interface State { + stats: ServerStat[]; +} -export class ServerStats extends React.Component { +export class ServerStats extends React.Component { constructor(props) { super(props); + this.state = { + stats: [], + }; + this.props.initNav('cfg', 'admin', 'server-stats'); - // const { nav, serverStats } = this.props; - // - // nav.load('cfg', 'admin', 'server-stats'); - // serverStats.load(); - // - // store.dispatch(setNav('new', { asd: 'tasd' })); + } + + async componentDidMount() { + try { + const stats = await this.props.getServerStats(); + this.setState({ stats }); + } catch (error) { + console.error(error); + } } render() { const { navModel } = this.props; - console.log('render', navModel); + const { stats } = this.state; + return (
-

aasd

+
+ + + + + + + + {stats.map(StatItem)} +
NameValue
+
); - // const { nav, serverStats } = this.props; - // return ( - //
- // - //
- // - // - // - // - // - // - // - // {serverStats.stats.map(StatItem)} - //
NameValue
- //
- //
- // ); } } -function StatItem(stat) { +function StatItem(stat: ServerStat) { return ( {stat.name} @@ -60,6 +68,7 @@ function StatItem(stat) { const mapStateToProps = state => ({ navModel: state.navModel, + getServerStats: getServerStats, }); const mapDispatchToProps = { diff --git a/public/app/features/server-stats/api.ts b/public/app/features/server-stats/api.ts new file mode 100644 index 00000000000..888cfd4f58f --- /dev/null +++ b/public/app/features/server-stats/api.ts @@ -0,0 +1,26 @@ +import { getBackendSrv } from 'app/core/services/backend_srv'; + +export interface ServerStat { + name: string; + value: string; +} + +export const getServerStats = async (): Promise => { + try { + const res = await getBackendSrv().get('api/admin/stats'); + return [ + { name: 'Total users', value: res.users }, + { name: 'Total dashboards', value: res.dashboards }, + { name: 'Active users (seen last 30 days)', value: res.activeUsers }, + { name: 'Total orgs', value: res.orgs }, + { name: 'Total playlists', value: res.playlists }, + { name: 'Total snapshots', value: res.snapshots }, + { name: 'Total dashboard tags', value: res.tags }, + { name: 'Total starred dashboards', value: res.stars }, + { name: 'Total alerts', value: res.alerts }, + ]; + } catch (error) { + console.error(error); + throw error; + } +}; From 593cc5380f6ac4fe14c8fc1c45daccfb5ffe3dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 31 Aug 2018 09:42:32 -0700 Subject: [PATCH 05/27] wip: redux refactor --- public/app/features/alerting/apis/index.ts | 52 ++++++++++++ .../containers}/AlertRuleList.test.tsx | 0 .../alerting/containers}/AlertRuleList.tsx | 83 +++++++++++++------ .../__snapshots__/AlertRuleList.test.tsx.snap | 0 .../ServerStats.test.tsx | 0 .../ServerStats.tsx | 0 .../__snapshots__/ServerStats.test.tsx.snap | 0 .../{server-stats => serverStats}/api.ts | 0 public/app/routes/routes.ts | 4 +- 9 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 public/app/features/alerting/apis/index.ts rename public/app/{containers/AlertRuleList => features/alerting/containers}/AlertRuleList.test.tsx (100%) rename public/app/{containers/AlertRuleList => features/alerting/containers}/AlertRuleList.tsx (73%) rename public/app/{containers/AlertRuleList => features/alerting/containers}/__snapshots__/AlertRuleList.test.tsx.snap (100%) rename public/app/features/{server-stats => serverStats}/ServerStats.test.tsx (100%) rename public/app/features/{server-stats => serverStats}/ServerStats.tsx (100%) rename public/app/features/{server-stats => serverStats}/__snapshots__/ServerStats.test.tsx.snap (100%) rename public/app/features/{server-stats => serverStats}/api.ts (100%) diff --git a/public/app/features/alerting/apis/index.ts b/public/app/features/alerting/apis/index.ts new file mode 100644 index 00000000000..ebfdbd34024 --- /dev/null +++ b/public/app/features/alerting/apis/index.ts @@ -0,0 +1,52 @@ +import { getBackendSrv } from 'app/core/services/backend_srv'; +import alertDef from '../alert_def'; +import moment from 'moment'; + +export interface AlertRule { + id: number; + dashboardId: number; + panelId: number; + name: string; + state: string; + stateText: string; + stateIcon: string; + stateClass: string; + stateAge: string; + info?: string; + url: string; +} + +export function setStateFields(rule, state) { + const stateModel = alertDef.getStateDisplayModel(state); + rule.state = state; + rule.stateText = stateModel.text; + rule.stateIcon = stateModel.iconClass; + rule.stateClass = stateModel.stateClass; + rule.stateAge = moment(rule.newStateDate) + .fromNow() + .replace(' ago', ''); +} + +export const getAlertRules = async (): Promise => { + try { + const rules = await getBackendSrv().get('/api/alerts', {}); + + for (const rule of rules) { + setStateFields(rule, rule.state); + + if (rule.state !== 'paused') { + if (rule.executionError) { + rule.info = 'Execution Error: ' + rule.executionError; + } + if (rule.evalData && rule.evalData.noData) { + rule.info = 'Query returned no data'; + } + } + } + + return rules; + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/public/app/containers/AlertRuleList/AlertRuleList.test.tsx b/public/app/features/alerting/containers/AlertRuleList.test.tsx similarity index 100% rename from public/app/containers/AlertRuleList/AlertRuleList.test.tsx rename to public/app/features/alerting/containers/AlertRuleList.test.tsx diff --git a/public/app/containers/AlertRuleList/AlertRuleList.tsx b/public/app/features/alerting/containers/AlertRuleList.tsx similarity index 73% rename from public/app/containers/AlertRuleList/AlertRuleList.tsx rename to public/app/features/alerting/containers/AlertRuleList.tsx index 668136dee6f..665b1508e3e 100644 --- a/public/app/containers/AlertRuleList/AlertRuleList.tsx +++ b/public/app/features/alerting/containers/AlertRuleList.tsx @@ -1,16 +1,23 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; +import { connect } from 'react-redux'; import classNames from 'classnames'; -import { inject, observer } from 'mobx-react'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; -import { AlertRule } from 'app/stores/AlertListStore/AlertListStore'; import appEvents from 'app/core/app_events'; -import ContainerProps from 'app/containers/ContainerProps'; import Highlighter from 'react-highlight-words'; +import { initNav } from 'app/core/actions'; +import { ContainerProps } from 'app/types'; +import { getAlertRules, AlertRule } from '../apis'; -@inject('view', 'nav', 'alertList') -@observer -export class AlertRuleList extends React.Component { +interface Props extends ContainerProps {} + +interface State { + rules: AlertRule[]; + search: string; + stateFilter: string; +} + +export class AlertRuleList extends PureComponent { stateFilters = [ { text: 'All', value: 'all' }, { text: 'OK', value: 'ok' }, @@ -23,19 +30,35 @@ export class AlertRuleList extends React.Component { constructor(props) { super(props); - this.props.nav.load('alerting', 'alert-list'); + this.state = { + rules: [], + search: '', + stateFilter: '', + }; + + this.props.initNav('alerting', 'alert-list'); + } + + componentDidMount() { this.fetchRules(); } onStateFilterChanged = evt => { - this.props.view.updateQuery({ state: evt.target.value }); - this.fetchRules(); + // this.props.view.updateQuery({ state: evt.target.value }); + // this.fetchRules(); }; - fetchRules() { - this.props.alertList.loadRules({ - state: this.props.view.query.get('state') || 'all', - }); + async fetchRules() { + try { + const rules = await getAlertRules(); + this.setState({ rules }); + } catch (error) { + console.error(error); + } + + // this.props.alertList.loadRules({ + // state: this.props.view.query.get('state') || 'all', + // }); } onOpenHowTo = () => { @@ -47,15 +70,16 @@ export class AlertRuleList extends React.Component { }; onSearchQueryChange = evt => { - this.props.alertList.setSearchQuery(evt.target.value); + // this.props.alertList.setSearchQuery(evt.target.value); }; render() { - const { nav, alertList } = this.props; + const { navModel } = this.props; + const { rules, search, stateFilter } = this.state; return (
- +
@@ -64,7 +88,7 @@ export class AlertRuleList extends React.Component { type="text" className="gf-form-input" placeholder="Search alerts" - value={alertList.search} + value={search} onChange={this.onSearchQueryChange} /> @@ -74,7 +98,7 @@ export class AlertRuleList extends React.Component {
- {this.stateFilters.map(AlertStateFilterOption)}
@@ -89,8 +113,8 @@ export class AlertRuleList extends React.Component {
    - {alertList.filteredRules.map(rule => ( - + {rules.map(rule => ( + ))}
@@ -113,10 +137,9 @@ export interface AlertRuleItemProps { search: string; } -@observer export class AlertRuleItem extends React.Component { toggleState = () => { - this.props.rule.togglePaused(); + // this.props.rule.togglePaused(); }; renderText(text: string) { @@ -134,8 +157,8 @@ export class AlertRuleItem extends React.Component { const stateClass = classNames({ fa: true, - 'fa-play': rule.isPaused, - 'fa-pause': !rule.isPaused, + 'fa-play': rule.state === 'paused', + 'fa-pause': rule.state !== 'paused', }); const ruleUrl = `${rule.url}?panelId=${rule.panelId}&fullscreen=true&edit=true&tab=alert`; @@ -175,4 +198,12 @@ export class AlertRuleItem extends React.Component { } } -export default hot(module)(AlertRuleList); +const mapStateToProps = state => ({ + navModel: state.navModel, +}); + +const mapDispatchToProps = { + initNav, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(AlertRuleList)); diff --git a/public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.test.tsx.snap b/public/app/features/alerting/containers/__snapshots__/AlertRuleList.test.tsx.snap similarity index 100% rename from public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.test.tsx.snap rename to public/app/features/alerting/containers/__snapshots__/AlertRuleList.test.tsx.snap diff --git a/public/app/features/server-stats/ServerStats.test.tsx b/public/app/features/serverStats/ServerStats.test.tsx similarity index 100% rename from public/app/features/server-stats/ServerStats.test.tsx rename to public/app/features/serverStats/ServerStats.test.tsx diff --git a/public/app/features/server-stats/ServerStats.tsx b/public/app/features/serverStats/ServerStats.tsx similarity index 100% rename from public/app/features/server-stats/ServerStats.tsx rename to public/app/features/serverStats/ServerStats.tsx diff --git a/public/app/features/server-stats/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/serverStats/__snapshots__/ServerStats.test.tsx.snap similarity index 100% rename from public/app/features/server-stats/__snapshots__/ServerStats.test.tsx.snap rename to public/app/features/serverStats/__snapshots__/ServerStats.test.tsx.snap diff --git a/public/app/features/server-stats/api.ts b/public/app/features/serverStats/api.ts similarity index 100% rename from public/app/features/server-stats/api.ts rename to public/app/features/serverStats/api.ts diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 7fcab26645f..cf45176ecf7 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -1,8 +1,8 @@ import './dashboard_loaders'; import './ReactContainer'; -import ServerStats from 'app/features/server-stats/ServerStats'; -import AlertRuleList from 'app/containers/AlertRuleList/AlertRuleList'; +import ServerStats from 'app/features/serverStats/ServerStats'; +import AlertRuleList from 'app/features/alerting/containers/AlertRuleList'; import FolderSettings from 'app/containers/ManageDashboards/FolderSettings'; import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions'; import TeamPages from 'app/containers/Teams/TeamPages'; From 2c85e44ab785681e35bdb855ff650cb148de84f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 31 Aug 2018 09:49:02 -0700 Subject: [PATCH 06/27] wip: moveing things around --- .../features/serverStats/ServerStats.test.tsx | 30 ---- .../app/features/serverStats/ServerStats.tsx | 78 -------- .../__snapshots__/ServerStats.test.tsx.snap | 170 ------------------ public/app/features/serverStats/api.ts | 26 --- public/app/routes/routes.ts | 2 +- 5 files changed, 1 insertion(+), 305 deletions(-) delete mode 100644 public/app/features/serverStats/ServerStats.test.tsx delete mode 100644 public/app/features/serverStats/ServerStats.tsx delete mode 100644 public/app/features/serverStats/__snapshots__/ServerStats.test.tsx.snap delete mode 100644 public/app/features/serverStats/api.ts diff --git a/public/app/features/serverStats/ServerStats.test.tsx b/public/app/features/serverStats/ServerStats.test.tsx deleted file mode 100644 index a329a47527d..00000000000 --- a/public/app/features/serverStats/ServerStats.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { ServerStats } from './ServerStats'; -import { RootStore } from 'app/stores/RootStore/RootStore'; -import { backendSrv, createNavTree } from 'test/mocks/common'; - -describe('ServerStats', () => { - it('Should render table with stats', done => { - backendSrv.get.mockReturnValue( - Promise.resolve({ - dashboards: 10, - }) - ); - - const store = RootStore.create( - {}, - { - backendSrv: backendSrv, - navTree: createNavTree('cfg', 'admin', 'server-stats'), - } - ); - - const page = renderer.create(); - - setTimeout(() => { - expect(page.toJSON()).toMatchSnapshot(); - done(); - }); - }); -}); diff --git a/public/app/features/serverStats/ServerStats.tsx b/public/app/features/serverStats/ServerStats.tsx deleted file mode 100644 index da1fb6e76f7..00000000000 --- a/public/app/features/serverStats/ServerStats.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React 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 { getServerStats, ServerStat } from './api'; -import PageHeader from 'app/core/components/PageHeader/PageHeader'; - -interface Props extends ContainerProps { - getServerStats: () => Promise; -} - -interface State { - stats: ServerStat[]; -} - -export class ServerStats extends React.Component { - constructor(props) { - super(props); - - this.state = { - stats: [], - }; - - this.props.initNav('cfg', 'admin', 'server-stats'); - } - - async componentDidMount() { - try { - const stats = await this.props.getServerStats(); - this.setState({ stats }); - } catch (error) { - console.error(error); - } - } - - render() { - const { navModel } = this.props; - const { stats } = this.state; - - return ( -
- -
- - - - - - - - {stats.map(StatItem)} -
NameValue
-
-
- ); - } -} - -function StatItem(stat: ServerStat) { - return ( - - {stat.name} - {stat.value} - - ); -} - -const mapStateToProps = state => ({ - navModel: state.navModel, - getServerStats: getServerStats, -}); - -const mapDispatchToProps = { - initNav, -}; - -export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ServerStats)); diff --git a/public/app/features/serverStats/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/serverStats/__snapshots__/ServerStats.test.tsx.snap deleted file mode 100644 index eac793ca2ca..00000000000 --- a/public/app/features/serverStats/__snapshots__/ServerStats.test.tsx.snap +++ /dev/null @@ -1,170 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ServerStats Should render table with stats 1`] = ` -
-
-
-
-
- - - - -
-

- admin-Text -

- -
-
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Name - - Value -
- Total dashboards - - 10 -
- Total users - - 0 -
- Active users (seen last 30 days) - - 0 -
- Total orgs - - 0 -
- Total playlists - - 0 -
- Total snapshots - - 0 -
- Total dashboard tags - - 0 -
- Total starred dashboards - - 0 -
- Total alerts - - 0 -
-
-
-`; diff --git a/public/app/features/serverStats/api.ts b/public/app/features/serverStats/api.ts deleted file mode 100644 index 888cfd4f58f..00000000000 --- a/public/app/features/serverStats/api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getBackendSrv } from 'app/core/services/backend_srv'; - -export interface ServerStat { - name: string; - value: string; -} - -export const getServerStats = async (): Promise => { - try { - const res = await getBackendSrv().get('api/admin/stats'); - return [ - { name: 'Total users', value: res.users }, - { name: 'Total dashboards', value: res.dashboards }, - { name: 'Active users (seen last 30 days)', value: res.activeUsers }, - { name: 'Total orgs', value: res.orgs }, - { name: 'Total playlists', value: res.playlists }, - { name: 'Total snapshots', value: res.snapshots }, - { name: 'Total dashboard tags', value: res.tags }, - { name: 'Total starred dashboards', value: res.stars }, - { name: 'Total alerts', value: res.alerts }, - ]; - } catch (error) { - console.error(error); - throw error; - } -}; diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index cf45176ecf7..7b1e223afe5 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -1,7 +1,7 @@ import './dashboard_loaders'; import './ReactContainer'; -import ServerStats from 'app/features/serverStats/ServerStats'; +import ServerStats from 'app/features/admin/containers/ServerStats'; import AlertRuleList from 'app/features/alerting/containers/AlertRuleList'; import FolderSettings from 'app/containers/ManageDashboards/FolderSettings'; import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions'; From 6efe9da10f99448740609845550211c361117086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 31 Aug 2018 09:49:32 -0700 Subject: [PATCH 07/27] wip: moving things around --- public/app/features/admin/apis/index.ts | 26 +++ .../admin/containers/ServerStats.test.tsx | 30 ++++ .../features/admin/containers/ServerStats.tsx | 78 ++++++++ .../__snapshots__/ServerStats.test.tsx.snap | 170 ++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 public/app/features/admin/apis/index.ts create mode 100644 public/app/features/admin/containers/ServerStats.test.tsx create mode 100644 public/app/features/admin/containers/ServerStats.tsx create mode 100644 public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap diff --git a/public/app/features/admin/apis/index.ts b/public/app/features/admin/apis/index.ts new file mode 100644 index 00000000000..888cfd4f58f --- /dev/null +++ b/public/app/features/admin/apis/index.ts @@ -0,0 +1,26 @@ +import { getBackendSrv } from 'app/core/services/backend_srv'; + +export interface ServerStat { + name: string; + value: string; +} + +export const getServerStats = async (): Promise => { + try { + const res = await getBackendSrv().get('api/admin/stats'); + return [ + { name: 'Total users', value: res.users }, + { name: 'Total dashboards', value: res.dashboards }, + { name: 'Active users (seen last 30 days)', value: res.activeUsers }, + { name: 'Total orgs', value: res.orgs }, + { name: 'Total playlists', value: res.playlists }, + { name: 'Total snapshots', value: res.snapshots }, + { name: 'Total dashboard tags', value: res.tags }, + { name: 'Total starred dashboards', value: res.stars }, + { name: 'Total alerts', value: res.alerts }, + ]; + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/public/app/features/admin/containers/ServerStats.test.tsx b/public/app/features/admin/containers/ServerStats.test.tsx new file mode 100644 index 00000000000..a329a47527d --- /dev/null +++ b/public/app/features/admin/containers/ServerStats.test.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { ServerStats } from './ServerStats'; +import { RootStore } from 'app/stores/RootStore/RootStore'; +import { backendSrv, createNavTree } from 'test/mocks/common'; + +describe('ServerStats', () => { + it('Should render table with stats', done => { + backendSrv.get.mockReturnValue( + Promise.resolve({ + dashboards: 10, + }) + ); + + const store = RootStore.create( + {}, + { + backendSrv: backendSrv, + navTree: createNavTree('cfg', 'admin', 'server-stats'), + } + ); + + const page = renderer.create(); + + setTimeout(() => { + expect(page.toJSON()).toMatchSnapshot(); + done(); + }); + }); +}); diff --git a/public/app/features/admin/containers/ServerStats.tsx b/public/app/features/admin/containers/ServerStats.tsx new file mode 100644 index 00000000000..7e96dcf4e0e --- /dev/null +++ b/public/app/features/admin/containers/ServerStats.tsx @@ -0,0 +1,78 @@ +import React 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 { getServerStats, ServerStat } from '../apis'; +import PageHeader from 'app/core/components/PageHeader/PageHeader'; + +interface Props extends ContainerProps { + getServerStats: () => Promise; +} + +interface State { + stats: ServerStat[]; +} + +export class ServerStats extends React.Component { + constructor(props) { + super(props); + + this.state = { + stats: [], + }; + + this.props.initNav('cfg', 'admin', 'server-stats'); + } + + async componentDidMount() { + try { + const stats = await this.props.getServerStats(); + this.setState({ stats }); + } catch (error) { + console.error(error); + } + } + + render() { + const { navModel } = this.props; + const { stats } = this.state; + + return ( +
+ +
+ + + + + + + + {stats.map(StatItem)} +
NameValue
+
+
+ ); + } +} + +function StatItem(stat: ServerStat) { + return ( + + {stat.name} + {stat.value} + + ); +} + +const mapStateToProps = state => ({ + navModel: state.navModel, + getServerStats: getServerStats, +}); + +const mapDispatchToProps = { + initNav, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ServerStats)); diff --git a/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap new file mode 100644 index 00000000000..eac793ca2ca --- /dev/null +++ b/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap @@ -0,0 +1,170 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ServerStats Should render table with stats 1`] = ` +
+
+
+
+
+ + + + +
+

+ admin-Text +

+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Value +
+ Total dashboards + + 10 +
+ Total users + + 0 +
+ Active users (seen last 30 days) + + 0 +
+ Total orgs + + 0 +
+ Total playlists + + 0 +
+ Total snapshots + + 0 +
+ Total dashboard tags + + 0 +
+ Total starred dashboards + + 0 +
+ Total alerts + + 0 +
+
+
+`; From de456f8b7356fe0de98d1d8a86f25deccba6627f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 31 Aug 2018 13:16:20 -0700 Subject: [PATCH 08/27] wip: solid progress on redux -> angular location bridge update --- public/app/core/actions/index.ts | 3 +- public/app/core/actions/location.ts | 13 +++++++ public/app/core/reducers/index.ts | 2 ++ public/app/core/reducers/location.ts | 35 +++++++++++++++++++ public/app/core/services/bridge_srv.ts | 33 +++++++++++++++++ .../alerting/containers/AlertRuleList.tsx | 15 ++++---- public/app/types/index.ts | 3 +- public/app/types/location.ts | 15 ++++++++ 8 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 public/app/core/actions/location.ts create mode 100644 public/app/core/reducers/location.ts create mode 100644 public/app/types/location.ts diff --git a/public/app/core/actions/index.ts b/public/app/core/actions/index.ts index 3c23dbbbe54..7a965f82dd1 100644 --- a/public/app/core/actions/index.ts +++ b/public/app/core/actions/index.ts @@ -1,3 +1,4 @@ import { initNav } from './navModel'; +import { updateLocation } from './location'; -export { initNav }; +export { initNav, updateLocation }; diff --git a/public/app/core/actions/location.ts b/public/app/core/actions/location.ts new file mode 100644 index 00000000000..6f7ac67363e --- /dev/null +++ b/public/app/core/actions/location.ts @@ -0,0 +1,13 @@ +import { LocationUpdate } from 'app/types'; + +export type Action = UpdateLocationAction; + +export interface UpdateLocationAction { + type: 'UPDATE_LOCATION'; + payload: LocationUpdate; +} + +export const updateLocation = (location: LocationUpdate): UpdateLocationAction => ({ + type: 'UPDATE_LOCATION', + payload: location, +}); diff --git a/public/app/core/reducers/index.ts b/public/app/core/reducers/index.ts index 0779111c16e..98f796981e4 100644 --- a/public/app/core/reducers/index.ts +++ b/public/app/core/reducers/index.ts @@ -1,5 +1,7 @@ import navModel from './navModel'; +import location from './location'; export default { navModel, + location, }; diff --git a/public/app/core/reducers/location.ts b/public/app/core/reducers/location.ts new file mode 100644 index 00000000000..5676c82844a --- /dev/null +++ b/public/app/core/reducers/location.ts @@ -0,0 +1,35 @@ +import { Action } from 'app/core/actions/location'; +import { LocationState, UrlQueryMap } from 'app/types'; +import { toUrlParams } from 'app/core/utils/url'; + +export const initialState: LocationState = { + url: '', + path: '', + query: {}, + routeParams: {}, +}; + +function renderUrl(path: string, query: UrlQueryMap): string { + if (Object.keys(query).length > 0) { + path += '?' + toUrlParams(query); + } + return path; +} + +const routerReducer = (state = initialState, action: Action): LocationState => { + switch (action.type) { + case 'UPDATE_LOCATION': { + const { path, query, routeParams } = action.payload; + return { + url: renderUrl(path || state.path, query), + path: path || state.path, + query: query || state.query, + routeParams: routeParams || state.routeParams, + }; + } + } + + return state; +}; + +export default routerReducer; diff --git a/public/app/core/services/bridge_srv.ts b/public/app/core/services/bridge_srv.ts index bdc2976a94c..29326794ac6 100644 --- a/public/app/core/services/bridge_srv.ts +++ b/public/app/core/services/bridge_srv.ts @@ -1,8 +1,10 @@ import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; import { store } from 'app/stores/store'; +import { store as reduxStore } from 'app/stores/configureStore'; import { reaction } from 'mobx'; import locationUtil from 'app/core/utils/location_util'; +import { updateLocation } from 'app/core/actions'; // Services that handles angular -> mobx store sync & other react <-> angular sync export class BridgeSrv { @@ -19,12 +21,30 @@ export class BridgeSrv { if (store.view.currentUrl !== angularUrl) { store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params); } + const state = reduxStore.getState(); + if (state.location.url !== angularUrl) { + reduxStore.dispatch( + updateLocation({ + path: this.$location.path(), + query: this.$location.search(), + routeParams: this.$route.current.params, + }) + ); + } }); this.$rootScope.$on('$routeChangeSuccess', (evt, data) => { store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params); + reduxStore.dispatch( + updateLocation({ + path: this.$location.path(), + query: this.$location.search(), + routeParams: this.$route.current.params, + }) + ); }); + // listen for mobx store changes and update angular reaction( () => store.view.currentUrl, currentUrl => { @@ -39,6 +59,19 @@ export class BridgeSrv { } ); + // Listen for changes in redux location -> update angular location + reduxStore.subscribe(() => { + const state = reduxStore.getState(); + const angularUrl = this.$location.url(); + const url = locationUtil.stripBaseFromUrl(state.location.url); + if (angularUrl !== url) { + this.$timeout(() => { + this.$location.url(url); + }); + console.log('store updating angular $location.url', url); + } + }); + appEvents.on('location-change', payload => { const urlWithoutBase = locationUtil.stripBaseFromUrl(payload.href); if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) { diff --git a/public/app/features/alerting/containers/AlertRuleList.tsx b/public/app/features/alerting/containers/AlertRuleList.tsx index 665b1508e3e..3c64f490db4 100644 --- a/public/app/features/alerting/containers/AlertRuleList.tsx +++ b/public/app/features/alerting/containers/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 } from 'app/core/actions'; +import { initNav, updateLocation } from 'app/core/actions'; import { ContainerProps } from 'app/types'; import { getAlertRules, AlertRule } from '../apis'; -interface Props extends ContainerProps {} +interface Props extends ContainerProps { + updateLocation: typeof updateLocation; +} interface State { rules: AlertRule[]; @@ -44,7 +46,9 @@ export class AlertRuleList extends PureComponent { } onStateFilterChanged = evt => { - // this.props.view.updateQuery({ state: evt.target.value }); + this.props.updateLocation({ + query: { state: evt.target.value }, + }); // this.fetchRules(); }; @@ -113,9 +117,7 @@ export class AlertRuleList extends PureComponent {
    - {rules.map(rule => ( - - ))} + {rules.map(rule => )}
@@ -204,6 +206,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = { initNav, + updateLocation, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(AlertRuleList)); diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 43d921e3964..9cb5ee85c04 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -1,4 +1,5 @@ import { NavModel, NavModelItem } from './navModel'; import { ContainerProps } from './container'; +import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location'; -export { NavModel, NavModelItem, ContainerProps }; +export { NavModel, NavModelItem, ContainerProps, LocationState, LocationUpdate, UrlQueryValue, UrlQueryMap }; diff --git a/public/app/types/location.ts b/public/app/types/location.ts new file mode 100644 index 00000000000..4a7f51523a7 --- /dev/null +++ b/public/app/types/location.ts @@ -0,0 +1,15 @@ +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 }; From 2ac202b22f4d2c6f6e076b851f34be155f64c8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Sep 2018 07:11:21 -0700 Subject: [PATCH 09/27] moving things around --- public/app/core/reducers/navModel.ts | 2 -- .../admin/containers/ServerStats.test.tsx | 22 +++++--------- .../features/admin/containers/ServerStats.tsx | 4 +-- .../{containers => }/AlertRuleList.test.tsx | 0 .../{containers => }/AlertRuleList.tsx | 2 +- .../{alert_tab_ctrl.ts => AlertTabCtrl.ts} | 4 +-- ..._edit_ctrl.ts => NotificationsEditCtrl.ts} | 0 ..._list_ctrl.ts => NotificationsListCtrl.ts} | 0 .../__snapshots__/AlertRuleList.test.tsx.snap | 0 public/app/features/alerting/all.ts | 2 -- .../ThresholdMapper.test.ts} | 2 +- .../ThresholdMapper.ts} | 0 .../{alert_def.ts => state/alertDef.ts} | 0 .../alerting/{apis/index.ts => state/apis.ts} | 2 +- public/app/features/all.ts | 3 +- .../annotations/annotation_tooltip.ts | 2 +- public/app/plugins/panel/alertlist/module.ts | 2 +- public/app/plugins/sdk.ts | 2 +- public/app/routes/routes.ts | 2 +- public/app/stores/AlertListStore/helpers.ts | 2 +- public/app/types/container.ts | 3 +- public/app/types/navModel.ts | 2 +- public/test/mocks/common.ts | 29 +++++++++++++++++++ 23 files changed, 53 insertions(+), 34 deletions(-) rename public/app/features/alerting/{containers => }/AlertRuleList.test.tsx (100%) rename public/app/features/alerting/{containers => }/AlertRuleList.tsx (99%) rename public/app/features/alerting/{alert_tab_ctrl.ts => AlertTabCtrl.ts} (99%) rename public/app/features/alerting/{notification_edit_ctrl.ts => NotificationsEditCtrl.ts} (100%) rename public/app/features/alerting/{notifications_list_ctrl.ts => NotificationsListCtrl.ts} (100%) rename public/app/features/alerting/{containers => }/__snapshots__/AlertRuleList.test.tsx.snap (100%) delete mode 100644 public/app/features/alerting/all.ts rename public/app/features/alerting/{specs/threshold_mapper.test.ts => state/ThresholdMapper.test.ts} (97%) rename public/app/features/alerting/{threshold_mapper.ts => state/ThresholdMapper.ts} (100%) rename public/app/features/alerting/{alert_def.ts => state/alertDef.ts} (100%) rename public/app/features/alerting/{apis/index.ts => state/apis.ts} (97%) diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index c00441c4881..4e9a7f8e434 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -12,7 +12,6 @@ function getNotFoundModel(): NavModel { }; return { - breadcrumbs: [node], node: node, main: node, }; @@ -53,7 +52,6 @@ const navModelReducer = (state = initialState, action: Action): NavModel => { return { main: main, node: node, - breadcrumbs: [], }; } } diff --git a/public/app/features/admin/containers/ServerStats.test.tsx b/public/app/features/admin/containers/ServerStats.test.tsx index a329a47527d..a89e78cb4ba 100644 --- a/public/app/features/admin/containers/ServerStats.test.tsx +++ b/public/app/features/admin/containers/ServerStats.test.tsx @@ -1,26 +1,18 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { ServerStats } from './ServerStats'; -import { RootStore } from 'app/stores/RootStore/RootStore'; -import { backendSrv, createNavTree } from 'test/mocks/common'; +import { initNav } from 'test/mocks/common'; +import { ServerStat } from '../apis'; describe('ServerStats', () => { it('Should render table with stats', done => { - backendSrv.get.mockReturnValue( - Promise.resolve({ - dashboards: 10, - }) - ); + const stats: ServerStat[] = [{ name: 'test', value: 'asd' }]; - const store = RootStore.create( - {}, - { - backendSrv: backendSrv, - navTree: createNavTree('cfg', 'admin', 'server-stats'), - } - ); + let getServerStats = () => { + return Promise.resolve(stats); + }; - const page = renderer.create(); + const page = renderer.create(); setTimeout(() => { expect(page.toJSON()).toMatchSnapshot(); diff --git a/public/app/features/admin/containers/ServerStats.tsx b/public/app/features/admin/containers/ServerStats.tsx index 7e96dcf4e0e..29611696efa 100644 --- a/public/app/features/admin/containers/ServerStats.tsx +++ b/public/app/features/admin/containers/ServerStats.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import { initNav } from 'app/core/actions'; @@ -14,7 +14,7 @@ interface State { stats: ServerStat[]; } -export class ServerStats extends React.Component { +export class ServerStats extends PureComponent { constructor(props) { super(props); diff --git a/public/app/features/alerting/containers/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx similarity index 100% rename from public/app/features/alerting/containers/AlertRuleList.test.tsx rename to public/app/features/alerting/AlertRuleList.test.tsx diff --git a/public/app/features/alerting/containers/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx similarity index 99% rename from public/app/features/alerting/containers/AlertRuleList.tsx rename to public/app/features/alerting/AlertRuleList.tsx index 3c64f490db4..e2e6d1a719a 100644 --- a/public/app/features/alerting/containers/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -7,7 +7,7 @@ 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 { getAlertRules, AlertRule } from '../apis'; +import { getAlertRules, AlertRule } from './state/apis'; interface Props extends ContainerProps { updateLocation: typeof updateLocation; diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/AlertTabCtrl.ts similarity index 99% rename from public/app/features/alerting/alert_tab_ctrl.ts rename to public/app/features/alerting/AlertTabCtrl.ts index a25d37913d4..040b293b244 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; -import { ThresholdMapper } from './threshold_mapper'; +import { ThresholdMapper } from './state/ThresholdMapper'; import { QueryPart } from 'app/core/components/query_part/query_part'; -import alertDef from './alert_def'; +import alertDef from './state/alertDef'; import config from 'app/core/config'; import appEvents from 'app/core/app_events'; diff --git a/public/app/features/alerting/notification_edit_ctrl.ts b/public/app/features/alerting/NotificationsEditCtrl.ts similarity index 100% rename from public/app/features/alerting/notification_edit_ctrl.ts rename to public/app/features/alerting/NotificationsEditCtrl.ts diff --git a/public/app/features/alerting/notifications_list_ctrl.ts b/public/app/features/alerting/NotificationsListCtrl.ts similarity index 100% rename from public/app/features/alerting/notifications_list_ctrl.ts rename to public/app/features/alerting/NotificationsListCtrl.ts diff --git a/public/app/features/alerting/containers/__snapshots__/AlertRuleList.test.tsx.snap b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap similarity index 100% rename from public/app/features/alerting/containers/__snapshots__/AlertRuleList.test.tsx.snap rename to public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap diff --git a/public/app/features/alerting/all.ts b/public/app/features/alerting/all.ts deleted file mode 100644 index 91d3a4109e7..00000000000 --- a/public/app/features/alerting/all.ts +++ /dev/null @@ -1,2 +0,0 @@ -import './notifications_list_ctrl'; -import './notification_edit_ctrl'; diff --git a/public/app/features/alerting/specs/threshold_mapper.test.ts b/public/app/features/alerting/state/ThresholdMapper.test.ts similarity index 97% rename from public/app/features/alerting/specs/threshold_mapper.test.ts rename to public/app/features/alerting/state/ThresholdMapper.test.ts index 922d9c8787e..d8ab54234cd 100644 --- a/public/app/features/alerting/specs/threshold_mapper.test.ts +++ b/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 './threshold_mapper'; describe('ThresholdMapper', () => { describe('with greater than evaluator', () => { diff --git a/public/app/features/alerting/threshold_mapper.ts b/public/app/features/alerting/state/ThresholdMapper.ts similarity index 100% rename from public/app/features/alerting/threshold_mapper.ts rename to public/app/features/alerting/state/ThresholdMapper.ts diff --git a/public/app/features/alerting/alert_def.ts b/public/app/features/alerting/state/alertDef.ts similarity index 100% rename from public/app/features/alerting/alert_def.ts rename to public/app/features/alerting/state/alertDef.ts diff --git a/public/app/features/alerting/apis/index.ts b/public/app/features/alerting/state/apis.ts similarity index 97% rename from public/app/features/alerting/apis/index.ts rename to public/app/features/alerting/state/apis.ts index ebfdbd34024..44cadc05215 100644 --- a/public/app/features/alerting/apis/index.ts +++ b/public/app/features/alerting/state/apis.ts @@ -1,5 +1,5 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; -import alertDef from '../alert_def'; +import alertDef from './alertDef'; import moment from 'moment'; export interface AlertRule { diff --git a/public/app/features/all.ts b/public/app/features/all.ts index df987a8b59b..065f399cae3 100644 --- a/public/app/features/all.ts +++ b/public/app/features/all.ts @@ -9,5 +9,6 @@ import './snapshot/all'; import './panel/all'; import './org/all'; import './admin/admin'; -import './alerting/all'; +import './alerting/NotificationsEditCtrl'; +import './alerting/NotificationsListCtrl'; import './styleguide/styleguide'; diff --git a/public/app/features/annotations/annotation_tooltip.ts b/public/app/features/annotations/annotation_tooltip.ts index ed2d797b7bf..0cb0c6a9419 100644 --- a/public/app/features/annotations/annotation_tooltip.ts +++ b/public/app/features/annotations/annotation_tooltip.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import $ from 'jquery'; import coreModule from 'app/core/core_module'; -import alertDef from '../alerting/alert_def'; +import alertDef from '../alerting/state/alertDef'; /** @ngInject **/ export function annotationTooltipDirective($sanitize, dashboardSrv, contextSrv, $compile) { diff --git a/public/app/plugins/panel/alertlist/module.ts b/public/app/plugins/panel/alertlist/module.ts index b171f590e94..f5a23f4748b 100644 --- a/public/app/plugins/panel/alertlist/module.ts +++ b/public/app/plugins/panel/alertlist/module.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import moment from 'moment'; -import alertDef from '../../../features/alerting/alert_def'; +import alertDef from '../../../features/alerting/state/alertDef'; import { PanelCtrl } from 'app/plugins/sdk'; import * as dateMath from 'app/core/utils/datemath'; diff --git a/public/app/plugins/sdk.ts b/public/app/plugins/sdk.ts index 2734426bd19..0f183271495 100644 --- a/public/app/plugins/sdk.ts +++ b/public/app/plugins/sdk.ts @@ -1,7 +1,7 @@ import { PanelCtrl } from 'app/features/panel/panel_ctrl'; import { MetricsPanelCtrl } from 'app/features/panel/metrics_panel_ctrl'; import { QueryCtrl } from 'app/features/panel/query_ctrl'; -import { alertTab } from 'app/features/alerting/alert_tab_ctrl'; +import { alertTab } from 'app/features/alerting/AlertTabCtrl'; import { loadPluginCss } from 'app/features/plugins/plugin_loader'; export { PanelCtrl, MetricsPanelCtrl, QueryCtrl, alertTab, loadPluginCss }; diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 7b1e223afe5..dfd215f7056 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -2,7 +2,7 @@ import './dashboard_loaders'; import './ReactContainer'; import ServerStats from 'app/features/admin/containers/ServerStats'; -import AlertRuleList from 'app/features/alerting/containers/AlertRuleList'; +import AlertRuleList from 'app/features/alerting/AlertRuleList'; import FolderSettings from 'app/containers/ManageDashboards/FolderSettings'; import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions'; import TeamPages from 'app/containers/Teams/TeamPages'; diff --git a/public/app/stores/AlertListStore/helpers.ts b/public/app/stores/AlertListStore/helpers.ts index c460a697967..4d1ddcc30e0 100644 --- a/public/app/stores/AlertListStore/helpers.ts +++ b/public/app/stores/AlertListStore/helpers.ts @@ -1,5 +1,5 @@ import moment from 'moment'; -import alertDef from 'app/features/alerting/alert_def'; +import alertDef from 'app/features/alerting/state/alertDef'; export function setStateFields(rule, state) { const stateModel = alertDef.getStateDisplayModel(state); diff --git a/public/app/types/container.ts b/public/app/types/container.ts index 174bc0c8460..98b5248fdd6 100644 --- a/public/app/types/container.ts +++ b/public/app/types/container.ts @@ -1,6 +1,7 @@ import { NavModel } from './navModel'; +import { initNav } from 'app/core/actions'; export interface ContainerProps { navModel: NavModel; - initNav: (...args: string[]) => void; + initNav: typeof initNav; } diff --git a/public/app/types/navModel.ts b/public/app/types/navModel.ts index e1a4265847c..9464858f967 100644 --- a/public/app/types/navModel.ts +++ b/public/app/types/navModel.ts @@ -9,11 +9,11 @@ export interface NavModelItem { hideFromTabs?: boolean; divider?: boolean; children?: NavModelItem[]; + breadcrumbs?: NavModelItem[]; target?: string; } export interface NavModel { - breadcrumbs: NavModelItem[]; main: NavModelItem; node: NavModelItem; } diff --git a/public/test/mocks/common.ts b/public/test/mocks/common.ts index 64d12fdf725..5350636573d 100644 --- a/public/test/mocks/common.ts +++ b/public/test/mocks/common.ts @@ -1,3 +1,5 @@ +import { NavModel, NavModelItem } from 'app/types'; + export const backendSrv = { get: jest.fn(), getDashboard: jest.fn(), @@ -17,3 +19,30 @@ export function createNavTree(...args) { return root; } + +export function getNavModel(title: string, tabs: string[]): NavModel { + const node: NavModelItem = { + id: title, + text: title, + icon: 'fa fa-fw fa-warning', + subTitle: 'subTitle', + url: title, + children: [], + breadcrumbs: [], + }; + + for (let tab of tabs) { + node.children.push({ + id: tab, + icon: 'icon', + subTitle: 'subTitle', + url: title, + text: title, + }); + } + + return { + node: node, + main: node, + }; +} From 7b06800295189343bb61e6ef56cebe70056cc23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Sep 2018 10:36:36 -0700 Subject: [PATCH 10/27] refactor: changed nav store to use nav index and selector instead of initNav action --- public/app/core/actions/index.ts | 3 +- public/app/core/actions/navModel.ts | 16 ++-- public/app/core/reducers/index.ts | 4 +- public/app/core/reducers/navModel.ts | 69 ++++----------- public/app/core/selectors/navModel.ts | 39 +++++++++ public/app/features/admin/apis/index.ts | 2 +- .../admin/containers/ServerStats.test.tsx | 7 +- .../features/admin/containers/ServerStats.tsx | 19 ++-- .../__snapshots__/ServerStats.test.tsx.snap | 87 ++++--------------- .../app/features/alerting/AlertRuleList.tsx | 15 ++-- .../alerting/state/ThresholdMapper.test.ts | 2 +- public/app/types/container.ts | 7 -- public/app/types/index.ts | 46 +++++++++- public/app/types/location.ts | 15 ---- public/app/types/navModel.ts | 19 ---- public/test/jest-setup.ts | 21 +++++ public/test/mocks/common.ts | 5 +- 17 files changed, 174 insertions(+), 202 deletions(-) create mode 100644 public/app/core/selectors/navModel.ts delete mode 100644 public/app/types/container.ts delete mode 100644 public/app/types/location.ts delete mode 100644 public/app/types/navModel.ts diff --git a/public/app/core/actions/index.ts b/public/app/core/actions/index.ts index 7a965f82dd1..74b61f845c0 100644 --- a/public/app/core/actions/index.ts +++ b/public/app/core/actions/index.ts @@ -1,4 +1,3 @@ -import { initNav } from './navModel'; import { updateLocation } from './location'; -export { initNav, updateLocation }; +export { updateLocation }; diff --git a/public/app/core/actions/navModel.ts b/public/app/core/actions/navModel.ts index 048afd4f8ff..56d129fd263 100644 --- a/public/app/core/actions/navModel.ts +++ b/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', }); diff --git a/public/app/core/reducers/index.ts b/public/app/core/reducers/index.ts index 98f796981e4..a3f9ca909c9 100644 --- a/public/app/core/reducers/index.ts +++ b/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, }; diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index 4e9a7f8e434..26acdb39a3d 100644 --- a/public/app/core/reducers/navModel.ts +++ b/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; diff --git a/public/app/core/selectors/navModel.ts b/public/app/core/selectors/navModel.ts new file mode 100644 index 00000000000..5f2d0318dff --- /dev/null +++ b/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(); + } +} diff --git a/public/app/features/admin/apis/index.ts b/public/app/features/admin/apis/index.ts index 888cfd4f58f..d81fd299493 100644 --- a/public/app/features/admin/apis/index.ts +++ b/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 => { diff --git a/public/app/features/admin/containers/ServerStats.test.tsx b/public/app/features/admin/containers/ServerStats.test.tsx index a89e78cb4ba..e12dfc3bed4 100644 --- a/public/app/features/admin/containers/ServerStats.test.tsx +++ b/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(); + const page = renderer.create(); setTimeout(() => { expect(page.toJSON()).toMatchSnapshot(); diff --git a/public/app/features/admin/containers/ServerStats.tsx b/public/app/features/admin/containers/ServerStats.tsx index 29611696efa..0b44a9af65e 100644 --- a/public/app/features/admin/containers/ServerStats.tsx +++ b/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; } @@ -21,8 +22,6 @@ export class ServerStats extends PureComponent { 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)); diff --git a/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap index eac793ca2ca..63de5bcd870 100644 --- a/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap +++ b/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap @@ -17,8 +17,9 @@ exports[`ServerStats Should render table with stats 1`] = ` - - +
- admin-Text + Admin - +
+ subTitle +
@@ -60,13 +65,13 @@ exports[`ServerStats Should render table with stats 1`] = ` > - server-stats-Text + Admin @@ -101,66 +106,10 @@ exports[`ServerStats Should render table with stats 1`] = ` - Total users - - - 0 - - - - - Active users (seen last 30 days) - - - 0 - - - - - Total orgs - - - 0 - - - - - Total playlists - - - 0 - - - - - Total snapshots - - - 0 - - - - - Total dashboard tags - - - 0 - - - - - Total starred dashboards - - - 0 - - - - - Total alerts + Total Users - 0 + 1 diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index e2e6d1a719a..84994555445 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/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 { search: '', stateFilter: '', }; - - this.props.initNav('alerting', 'alert-list'); } componentDidMount() { @@ -200,12 +200,11 @@ export class AlertRuleItem extends React.Component { } } -const mapStateToProps = state => ({ - navModel: state.navModel, +const mapStateToProps = (state: StoreState) => ({ + navModel: selectNavNode(state.navIndex, 'alert-list'), }); const mapDispatchToProps = { - initNav, updateLocation, }; diff --git a/public/app/features/alerting/state/ThresholdMapper.test.ts b/public/app/features/alerting/state/ThresholdMapper.test.ts index d8ab54234cd..8e91d0b6d0a 100644 --- a/public/app/features/alerting/state/ThresholdMapper.test.ts +++ b/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', () => { diff --git a/public/app/types/container.ts b/public/app/types/container.ts deleted file mode 100644 index 98b5248fdd6..00000000000 --- a/public/app/types/container.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NavModel } from './navModel'; -import { initNav } from 'app/core/actions'; - -export interface ContainerProps { - navModel: NavModel; - initNav: typeof initNav; -} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 9cb5ee85c04..930c08c9eb0 100644 --- a/public/app/types/index.ts +++ b/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; +} diff --git a/public/app/types/location.ts b/public/app/types/location.ts deleted file mode 100644 index 4a7f51523a7..00000000000 --- a/public/app/types/location.ts +++ /dev/null @@ -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 }; diff --git a/public/app/types/navModel.ts b/public/app/types/navModel.ts deleted file mode 100644 index 9464858f967..00000000000 --- a/public/app/types/navModel.ts +++ /dev/null @@ -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; -} diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index fed65097ac7..7b326a279b7 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -20,3 +20,24 @@ configure({ adapter: new Adapter() }); const global = 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 }); diff --git a/public/test/mocks/common.ts b/public/test/mocks/common.ts index 5350636573d..1c7bdb4f1e2 100644 --- a/public/test/mocks/common.ts +++ b/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, From 2a64d19f5b22ca44f6c3d5ecffec75f62b1fba7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Sep 2018 11:36:03 -0700 Subject: [PATCH 11/27] wip: load alert rules via redux --- public/app/core/reducers/index.ts | 2 +- public/app/core/reducers/location.ts | 4 +- public/app/core/selectors/navModel.ts | 2 +- .../features/admin/containers/ServerStats.tsx | 4 +- .../app/features/alerting/AlertRuleList.tsx | 27 +++++----- public/app/features/alerting/state/actions.ts | 26 ++++++++++ public/app/features/alerting/state/apis.ts | 52 ------------------- .../app/features/alerting/state/reducers.ts | 46 ++++++++++++++++ public/app/stores/configureStore.ts | 4 +- public/app/types/index.ts | 33 ++++++++++++ 10 files changed, 126 insertions(+), 74 deletions(-) create mode 100644 public/app/features/alerting/state/actions.ts delete mode 100644 public/app/features/alerting/state/apis.ts create mode 100644 public/app/features/alerting/state/reducers.ts diff --git a/public/app/core/reducers/index.ts b/public/app/core/reducers/index.ts index a3f9ca909c9..be13528c91c 100644 --- a/public/app/core/reducers/index.ts +++ b/public/app/core/reducers/index.ts @@ -1,5 +1,5 @@ import { navIndexReducer as navIndex } from './navModel'; -import location from './location'; +import { locationReducer as location } from './location'; export default { navIndex, diff --git a/public/app/core/reducers/location.ts b/public/app/core/reducers/location.ts index 5676c82844a..4591448d082 100644 --- a/public/app/core/reducers/location.ts +++ b/public/app/core/reducers/location.ts @@ -16,7 +16,7 @@ function renderUrl(path: string, query: UrlQueryMap): string { return path; } -const routerReducer = (state = initialState, action: Action): LocationState => { +export const locationReducer = (state = initialState, action: Action): LocationState => { switch (action.type) { case 'UPDATE_LOCATION': { const { path, query, routeParams } = action.payload; @@ -31,5 +31,3 @@ const routerReducer = (state = initialState, action: Action): LocationState => { return state; }; - -export default routerReducer; diff --git a/public/app/core/selectors/navModel.ts b/public/app/core/selectors/navModel.ts index 5f2d0318dff..a7e1c3330bd 100644 --- a/public/app/core/selectors/navModel.ts +++ b/public/app/core/selectors/navModel.ts @@ -15,7 +15,7 @@ function getNotFoundModel(): NavModel { }; } -export function selectNavNode(navIndex: NavIndex, id: string): NavModel { +export function getNavModel(navIndex: NavIndex, id: string): NavModel { if (navIndex[id]) { const node = navIndex[id]; const main = { diff --git a/public/app/features/admin/containers/ServerStats.tsx b/public/app/features/admin/containers/ServerStats.tsx index 0b44a9af65e..97419ec9301 100644 --- a/public/app/features/admin/containers/ServerStats.tsx +++ b/public/app/features/admin/containers/ServerStats.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import { NavModel, StoreState } from 'app/types'; -import { selectNavNode } from 'app/core/selectors/navModel'; +import { getNavModel } from 'app/core/selectors/navModel'; import { getServerStats, ServerStat } from '../apis'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; @@ -66,7 +66,7 @@ function StatItem(stat: ServerStat) { } const mapStateToProps = (state: StoreState) => ({ - navModel: selectNavNode(state.navIndex, 'server-stats'), + navModel: getNavModel(state.navIndex, 'server-stats'), getServerStats: getServerStats, }); diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 84994555445..faa46945536 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -6,13 +6,15 @@ import PageHeader from 'app/core/components/PageHeader/PageHeader'; import appEvents from 'app/core/app_events'; import Highlighter from 'react-highlight-words'; 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'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { NavModel, StoreState, AlertRule } from 'app/types'; +import { getAlertRulesAsync } from './state/actions'; interface Props { navModel: NavModel; + alertRules: AlertRule[]; updateLocation: typeof updateLocation; + getAlertRulesAsync: typeof getAlertRulesAsync; } interface State { @@ -49,16 +51,11 @@ export class AlertRuleList extends PureComponent { this.props.updateLocation({ query: { state: evt.target.value }, }); - // this.fetchRules(); + this.fetchRules(); }; async fetchRules() { - try { - const rules = await getAlertRules(); - this.setState({ rules }); - } catch (error) { - console.error(error); - } + await this.props.getAlertRulesAsync(); // this.props.alertList.loadRules({ // state: this.props.view.query.get('state') || 'all', @@ -78,8 +75,8 @@ export class AlertRuleList extends PureComponent { }; render() { - const { navModel } = this.props; - const { rules, search, stateFilter } = this.state; + const { navModel, alertRules } = this.props; + const { search, stateFilter } = this.state; return (
@@ -117,7 +114,7 @@ export class AlertRuleList extends PureComponent {
    - {rules.map(rule => )} + {alertRules.map(rule => )}
@@ -201,11 +198,13 @@ export class AlertRuleItem extends React.Component { } const mapStateToProps = (state: StoreState) => ({ - navModel: selectNavNode(state.navIndex, 'alert-list'), + navModel: getNavModel(state.navIndex, 'alert-list'), + alertRules: state.alertRules, }); const mapDispatchToProps = { updateLocation, + getAlertRulesAsync, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(AlertRuleList)); diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts new file mode 100644 index 00000000000..0f9caa9d47f --- /dev/null +++ b/public/app/features/alerting/state/actions.ts @@ -0,0 +1,26 @@ +import { Dispatch } from 'redux'; +import { getBackendSrv } from 'app/core/services/backend_srv'; +import { AlertRule } from 'app/types'; + +export interface LoadAlertRulesAction { + type: 'LOAD_ALERT_RULES'; + payload: AlertRule[]; +} + +export const loadAlertRules = (rules: AlertRule[]): LoadAlertRulesAction => ({ + type: 'LOAD_ALERT_RULES', + payload: rules, +}); + +export type Action = LoadAlertRulesAction; + +export const getAlertRulesAsync = () => async (dispatch: Dispatch): Promise => { + try { + const rules = await getBackendSrv().get('/api/alerts', {}); + dispatch(loadAlertRules(rules)); + return rules; + } catch (error) { + console.error(error); + throw error; + } +}; diff --git a/public/app/features/alerting/state/apis.ts b/public/app/features/alerting/state/apis.ts deleted file mode 100644 index 44cadc05215..00000000000 --- a/public/app/features/alerting/state/apis.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getBackendSrv } from 'app/core/services/backend_srv'; -import alertDef from './alertDef'; -import moment from 'moment'; - -export interface AlertRule { - id: number; - dashboardId: number; - panelId: number; - name: string; - state: string; - stateText: string; - stateIcon: string; - stateClass: string; - stateAge: string; - info?: string; - url: string; -} - -export function setStateFields(rule, state) { - const stateModel = alertDef.getStateDisplayModel(state); - rule.state = state; - rule.stateText = stateModel.text; - rule.stateIcon = stateModel.iconClass; - rule.stateClass = stateModel.stateClass; - rule.stateAge = moment(rule.newStateDate) - .fromNow() - .replace(' ago', ''); -} - -export const getAlertRules = async (): Promise => { - try { - const rules = await getBackendSrv().get('/api/alerts', {}); - - for (const rule of rules) { - setStateFields(rule, rule.state); - - if (rule.state !== 'paused') { - if (rule.executionError) { - rule.info = 'Execution Error: ' + rule.executionError; - } - if (rule.evalData && rule.evalData.noData) { - rule.info = 'Query returned no data'; - } - } - } - - return rules; - } catch (error) { - console.error(error); - throw error; - } -}; diff --git a/public/app/features/alerting/state/reducers.ts b/public/app/features/alerting/state/reducers.ts new file mode 100644 index 00000000000..0718c511106 --- /dev/null +++ b/public/app/features/alerting/state/reducers.ts @@ -0,0 +1,46 @@ +import { Action } from './actions'; +import { AlertRule } from 'app/types'; +import alertDef from './alertDef'; +import moment from 'moment'; + +export const initialState: AlertRule[] = []; + +export function setStateFields(rule, state) { + const stateModel = alertDef.getStateDisplayModel(state); + rule.state = state; + rule.stateText = stateModel.text; + rule.stateIcon = stateModel.iconClass; + rule.stateClass = stateModel.stateClass; + rule.stateAge = moment(rule.newStateDate) + .fromNow() + .replace(' ago', ''); +} + +export const alertRulesReducer = (state = initialState, action: Action): AlertRule[] => { + switch (action.type) { + case 'LOAD_ALERT_RULES': { + const alertRules = action.payload; + + for (const rule of alertRules) { + setStateFields(rule, rule.state); + + if (rule.state !== 'paused') { + if (rule.executionError) { + rule.info = 'Execution Error: ' + rule.executionError; + } + if (rule.evalData && rule.evalData.noData) { + rule.info = 'Query returned no data'; + } + } + } + + return alertRules; + } + } + + return state; +}; + +export default { + alertRules: alertRulesReducer, +}; diff --git a/public/app/stores/configureStore.ts b/public/app/stores/configureStore.ts index 3a7d16da76d..232f2e30cb8 100644 --- a/public/app/stores/configureStore.ts +++ b/public/app/stores/configureStore.ts @@ -2,9 +2,11 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import thunk from 'redux-thunk'; import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; +import alertingReducers from 'app/features/alerting/state/reducers'; const rootReducer = combineReducers({ - ...sharedReducers + ...sharedReducers, + ...alertingReducers, }); export let store; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 930c08c9eb0..a409f586f33 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -1,3 +1,7 @@ +// +// Location +// + export interface LocationUpdate { path?: string; query?: UrlQueryMap; @@ -14,6 +18,30 @@ export interface LocationState { export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[]; export type UrlQueryMap = { [s: string]: UrlQueryValue }; +// +// Alerting +// + +export interface AlertRule { + id: number; + dashboardId: number; + panelId: number; + name: string; + state: string; + stateText: string; + stateIcon: string; + stateClass: string; + stateAge: string; + info?: string; + url: string; + executionError?: string; + evalData?: { noData: boolean }; +} + +// +// NavModel +// + export interface NavModelItem { text: string; url: string; @@ -37,7 +65,12 @@ export interface NavModel { export type NavIndex = { [s: string]: NavModelItem }; +// +// Store +// + export interface StoreState { navIndex: NavIndex; location: LocationState; + alertRules: AlertRule[]; } From 3fd707f321a7c2fbb8081f87b6bc62122e9208da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Sep 2018 12:08:31 -0700 Subject: [PATCH 12/27] redux: progress --- .../app/features/alerting/AlertRuleList.tsx | 28 ++++++++++--------- public/app/features/alerting/state/actions.ts | 6 ++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index faa46945536..03bafe119b0 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -15,12 +15,11 @@ interface Props { alertRules: AlertRule[]; updateLocation: typeof updateLocation; getAlertRulesAsync: typeof getAlertRulesAsync; + stateFilter: string; } interface State { - rules: AlertRule[]; search: string; - stateFilter: string; } export class AlertRuleList extends PureComponent { @@ -37,29 +36,31 @@ export class AlertRuleList extends PureComponent { super(props); this.state = { - rules: [], search: '', - stateFilter: '', }; } componentDidMount() { - this.fetchRules(); + this.fetchRules(this.getStateFilter()); } onStateFilterChanged = evt => { this.props.updateLocation({ query: { state: evt.target.value }, }); - this.fetchRules(); + this.fetchRules(evt.target.value); }; - async fetchRules() { - await this.props.getAlertRulesAsync(); + getStateFilter(): string { + const { stateFilter } = this.props; + if (stateFilter) { + return stateFilter.toString(); + } + return 'all'; + } - // this.props.alertList.loadRules({ - // state: this.props.view.query.get('state') || 'all', - // }); + async fetchRules(stateFilter: string) { + await this.props.getAlertRulesAsync({ state: stateFilter }); } onOpenHowTo = () => { @@ -76,7 +77,7 @@ export class AlertRuleList extends PureComponent { render() { const { navModel, alertRules } = this.props; - const { search, stateFilter } = this.state; + const { search } = this.state; return (
@@ -99,7 +100,7 @@ export class AlertRuleList extends PureComponent {
- {this.stateFilters.map(AlertStateFilterOption)}
@@ -200,6 +201,7 @@ export class AlertRuleItem extends React.Component { const mapStateToProps = (state: StoreState) => ({ navModel: getNavModel(state.navIndex, 'alert-list'), alertRules: state.alertRules, + stateFilter: state.location.query.state, }); const mapDispatchToProps = { diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts index 0f9caa9d47f..9103b34e81d 100644 --- a/public/app/features/alerting/state/actions.ts +++ b/public/app/features/alerting/state/actions.ts @@ -14,9 +14,11 @@ export const loadAlertRules = (rules: AlertRule[]): LoadAlertRulesAction => ({ export type Action = LoadAlertRulesAction; -export const getAlertRulesAsync = () => async (dispatch: Dispatch): Promise => { +export const getAlertRulesAsync = (options: { state: string }) => async ( + dispatch: Dispatch +): Promise => { try { - const rules = await getBackendSrv().get('/api/alerts', {}); + const rules = await getBackendSrv().get('/api/alerts', options); dispatch(loadAlertRules(rules)); return rules; } catch (error) { From 42aaa2b90746eee050bab8495b1d007277e2863f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Sep 2018 12:14:41 -0700 Subject: [PATCH 13/27] redux: improved state handling --- public/app/features/alerting/AlertRuleList.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 03bafe119b0..77e5af520fc 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -41,14 +41,20 @@ export class AlertRuleList extends PureComponent { } componentDidMount() { - this.fetchRules(this.getStateFilter()); + console.log('did mount'); + this.fetchRules(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.stateFilter !== this.props.stateFilter) { + this.fetchRules(); + } } onStateFilterChanged = evt => { this.props.updateLocation({ query: { state: evt.target.value }, }); - this.fetchRules(evt.target.value); }; getStateFilter(): string { @@ -59,8 +65,8 @@ export class AlertRuleList extends PureComponent { return 'all'; } - async fetchRules(stateFilter: string) { - await this.props.getAlertRulesAsync({ state: stateFilter }); + async fetchRules() { + await this.props.getAlertRulesAsync({ state: this.getStateFilter() }); } onOpenHowTo = () => { From 50444c32e00b82c903bdf280499d1c4641cb43f1 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Sep 2018 13:46:39 +0200 Subject: [PATCH 14/27] actions and reducers for search filter --- .../features/alerting/AlertRuleItem.test.tsx | 32 +++++ .../app/features/alerting/AlertRuleItem.tsx | 70 +++++++++++ .../app/features/alerting/AlertRuleList.tsx | 112 ++++-------------- .../__snapshots__/AlertRuleItem.test.tsx.snap | 85 +++++++++++++ public/app/features/alerting/state/actions.ts | 21 +++- .../app/features/alerting/state/reducers.ts | 17 +-- .../app/features/alerting/state/selectors.ts | 9 ++ public/app/types/index.ts | 7 +- 8 files changed, 251 insertions(+), 102 deletions(-) create mode 100644 public/app/features/alerting/AlertRuleItem.test.tsx create mode 100644 public/app/features/alerting/AlertRuleItem.tsx create mode 100644 public/app/features/alerting/__snapshots__/AlertRuleItem.test.tsx.snap create mode 100644 public/app/features/alerting/state/selectors.ts diff --git a/public/app/features/alerting/AlertRuleItem.test.tsx b/public/app/features/alerting/AlertRuleItem.test.tsx new file mode 100644 index 00000000000..0a1c5cbe437 --- /dev/null +++ b/public/app/features/alerting/AlertRuleItem.test.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import AlertRuleItem, { Props } from './AlertRuleItem'; + +const setup = (propOverrides?: object) => { + const props: Props = { + rule: { + id: 1, + dashboardId: 1, + panelId: 1, + name: 'Some rule', + state: 'Open', + stateText: 'state text', + stateIcon: 'icon', + stateClass: 'state class', + stateAge: 'age', + url: 'https://something.something.darkside', + }, + search: '', + }; + Object.assign(props, propOverrides); + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx new file mode 100644 index 00000000000..4c8d74cd6e3 --- /dev/null +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import Highlighter from 'react-highlight-words'; +import classNames from 'classnames/bind'; +import { AlertRule } from '../../types'; + +export interface Props { + rule: AlertRule; + search: string; +} + +export default class AlertRuleItem extends React.Component { + toggleState = () => { + // this.props.rule.togglePaused(); + }; + + renderText(text: string) { + return ( + + ); + } + + render() { + const { rule } = this.props; + + const stateClass = classNames({ + fa: true, + 'fa-play': rule.state === 'paused', + 'fa-pause': rule.state !== 'paused', + }); + + const ruleUrl = `${rule.url}?panelId=${rule.panelId}&fullscreen=true&edit=true&tab=alert`; + + return ( +
  • + + + +
    +
    + +
    + {this.renderText(rule.stateText)} + for {rule.stateAge} +
    +
    + {rule.info &&
    {this.renderText(rule.info)}
    } +
    + +
    + + + + +
    +
  • + ); + } +} diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 77e5af520fc..0adadb0f6d0 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -1,21 +1,23 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; -import classNames from 'classnames'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; +import AlertRuleItem from './AlertRuleItem'; import appEvents from 'app/core/app_events'; -import Highlighter from 'react-highlight-words'; import { updateLocation } from 'app/core/actions'; import { getNavModel } from 'app/core/selectors/navModel'; import { NavModel, StoreState, AlertRule } from 'app/types'; -import { getAlertRulesAsync } from './state/actions'; +import { getAlertRulesAsync, setSearchQuery } from './state/actions'; +import { getAlertRuleItems, getSearchQuery } from './state/selectors'; interface Props { navModel: NavModel; alertRules: AlertRule[]; updateLocation: typeof updateLocation; getAlertRulesAsync: typeof getAlertRulesAsync; + setSearchQuery: typeof setSearchQuery; stateFilter: string; + search: string; } interface State { @@ -32,14 +34,6 @@ export class AlertRuleList extends PureComponent { { text: 'Paused', value: 'paused' }, ]; - constructor(props) { - super(props); - - this.state = { - search: '', - }; - } - componentDidMount() { console.log('did mount'); this.fetchRules(); @@ -77,13 +71,21 @@ export class AlertRuleList extends PureComponent { }); }; - onSearchQueryChange = evt => { - // this.props.alertList.setSearchQuery(evt.target.value); + onSearchQueryChange = event => { + const { value } = event.target; + this.props.setSearchQuery(value); }; + alertStateFilterOption({ text, value }) { + return ( + + ); + } + render() { - const { navModel, alertRules } = this.props; - const { search } = this.state; + const { navModel, alertRules, search } = this.props; return (
    @@ -107,7 +109,7 @@ export class AlertRuleList extends PureComponent {
    @@ -130,89 +132,17 @@ export class AlertRuleList extends PureComponent { } } -function AlertStateFilterOption({ text, value }) { - return ( - - ); -} - -export interface AlertRuleItemProps { - rule: AlertRule; - search: string; -} - -export class AlertRuleItem extends React.Component { - toggleState = () => { - // this.props.rule.togglePaused(); - }; - - renderText(text: string) { - return ( - - ); - } - - render() { - const { rule } = this.props; - - const stateClass = classNames({ - fa: true, - 'fa-play': rule.state === 'paused', - 'fa-pause': rule.state !== 'paused', - }); - - const ruleUrl = `${rule.url}?panelId=${rule.panelId}&fullscreen=true&edit=true&tab=alert`; - - return ( -
  • - - - -
    -
    - -
    - {this.renderText(rule.stateText)} - for {rule.stateAge} -
    -
    - {rule.info &&
    {this.renderText(rule.info)}
    } -
    - -
    - - - - -
    -
  • - ); - } -} - const mapStateToProps = (state: StoreState) => ({ navModel: getNavModel(state.navIndex, 'alert-list'), - alertRules: state.alertRules, + alertRules: getAlertRuleItems(state.alertRules), stateFilter: state.location.query.state, + search: getSearchQuery(state.alertRules), }); const mapDispatchToProps = { updateLocation, getAlertRulesAsync, + setSearchQuery, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(AlertRuleList)); diff --git a/public/app/features/alerting/__snapshots__/AlertRuleItem.test.tsx.snap b/public/app/features/alerting/__snapshots__/AlertRuleItem.test.tsx.snap new file mode 100644 index 00000000000..7d3c446fc55 --- /dev/null +++ b/public/app/features/alerting/__snapshots__/AlertRuleItem.test.tsx.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` +
  • + + + +
    +
    +
    + + + +
    +
    + + + + + for + age + +
    +
    +
    +
    + + + + +
    +
  • +`; diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts index 9103b34e81d..3b80cb19c39 100644 --- a/public/app/features/alerting/state/actions.ts +++ b/public/app/features/alerting/state/actions.ts @@ -2,17 +2,32 @@ import { Dispatch } from 'redux'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { AlertRule } from 'app/types'; +export enum ActionTypes { + LoadAlertRules = 'LOAD_ALERT_RULES', + SetSearchQuery = 'SET_SEARCH_QUERY', +} + export interface LoadAlertRulesAction { - type: 'LOAD_ALERT_RULES'; + type: ActionTypes.LoadAlertRules; payload: AlertRule[]; } +export interface SetSearchQueryAction { + type: ActionTypes.SetSearchQuery; + payload: string; +} + export const loadAlertRules = (rules: AlertRule[]): LoadAlertRulesAction => ({ - type: 'LOAD_ALERT_RULES', + type: ActionTypes.LoadAlertRules, payload: rules, }); -export type Action = LoadAlertRulesAction; +export const setSearchQuery = (query: string): SetSearchQueryAction => ({ + type: ActionTypes.SetSearchQuery, + payload: query, +}); + +export type Action = LoadAlertRulesAction | SetSearchQueryAction; export const getAlertRulesAsync = (options: { state: string }) => async ( dispatch: Dispatch diff --git a/public/app/features/alerting/state/reducers.ts b/public/app/features/alerting/state/reducers.ts index 0718c511106..a18d112dd94 100644 --- a/public/app/features/alerting/state/reducers.ts +++ b/public/app/features/alerting/state/reducers.ts @@ -1,9 +1,9 @@ -import { Action } from './actions'; -import { AlertRule } from 'app/types'; -import alertDef from './alertDef'; import moment from 'moment'; +import { AlertRulesState } from 'app/types'; +import { Action, ActionTypes } from './actions'; +import alertDef from './alertDef'; -export const initialState: AlertRule[] = []; +export const initialState: AlertRulesState = { items: [], searchQuery: '' }; export function setStateFields(rule, state) { const stateModel = alertDef.getStateDisplayModel(state); @@ -16,9 +16,9 @@ export function setStateFields(rule, state) { .replace(' ago', ''); } -export const alertRulesReducer = (state = initialState, action: Action): AlertRule[] => { +export const alertRulesReducer = (state = initialState, action: Action): AlertRulesState => { switch (action.type) { - case 'LOAD_ALERT_RULES': { + case ActionTypes.LoadAlertRules: { const alertRules = action.payload; for (const rule of alertRules) { @@ -34,8 +34,11 @@ export const alertRulesReducer = (state = initialState, action: Action): AlertRu } } - return alertRules; + return { items: alertRules, searchQuery: state.searchQuery }; } + + case ActionTypes.SetSearchQuery: + return { items: state.items, searchQuery: action.payload }; } return state; diff --git a/public/app/features/alerting/state/selectors.ts b/public/app/features/alerting/state/selectors.ts new file mode 100644 index 00000000000..7c72520d773 --- /dev/null +++ b/public/app/features/alerting/state/selectors.ts @@ -0,0 +1,9 @@ +export const getSearchQuery = state => state.searchQuery; + +export const getAlertRuleItems = state => { + const regex = new RegExp(state.searchQuery, 'i'); + + return state.items.filter(item => { + return regex.test(item.name) || regex.test(item.stateText) || regex.test(item.info); + }); +}; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index a409f586f33..6aa4dc23d97 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -69,8 +69,13 @@ export type NavIndex = { [s: string]: NavModelItem }; // Store // +export interface AlertRulesState { + items: AlertRule[]; + searchQuery: string; +} + export interface StoreState { navIndex: NavIndex; location: LocationState; - alertRules: AlertRule[]; + alertRules: AlertRulesState; } From 1994ca50167c0ec37f0b07fcd8a66d0f167decf3 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Sep 2018 14:04:44 +0200 Subject: [PATCH 15/27] remove log --- public/app/features/alerting/AlertRuleList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 0adadb0f6d0..6023a1bb142 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -35,7 +35,6 @@ export class AlertRuleList extends PureComponent { ]; componentDidMount() { - console.log('did mount'); this.fetchRules(); } From c958ebd10172964af4dde7f56b67cb994f4cbe7f Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Sep 2018 14:05:12 +0200 Subject: [PATCH 16/27] extend from purecomponent --- public/app/features/alerting/AlertRuleItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx index 4c8d74cd6e3..7d669771722 100644 --- a/public/app/features/alerting/AlertRuleItem.tsx +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; import Highlighter from 'react-highlight-words'; import classNames from 'classnames/bind'; import { AlertRule } from '../../types'; @@ -8,7 +8,7 @@ export interface Props { search: string; } -export default class AlertRuleItem extends React.Component { +export default class AlertRuleItem extends PureComponent { toggleState = () => { // this.props.rule.togglePaused(); }; From 638370e310155ef9e82e942b3f47e5ddb0d9c470 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Sep 2018 15:44:39 +0200 Subject: [PATCH 17/27] pausing alert need to fix return type on dispatch. Could not test correctly either. --- public/app/features/alerting/AlertRuleItem.tsx | 17 +++++++++++++---- public/app/features/alerting/state/actions.ts | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx index 7d669771722..95c6966ab88 100644 --- a/public/app/features/alerting/AlertRuleItem.tsx +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -1,16 +1,21 @@ import React, { PureComponent } from 'react'; +import { connect } from 'react-redux'; import Highlighter from 'react-highlight-words'; import classNames from 'classnames/bind'; +import { togglePauseAlertRule } from './state/actions'; import { AlertRule } from '../../types'; export interface Props { rule: AlertRule; search: string; + togglePauseAlertRule: typeof togglePauseAlertRule; } -export default class AlertRuleItem extends PureComponent { - toggleState = () => { - // this.props.rule.togglePaused(); +class AlertRuleItem extends PureComponent { + togglePaused = () => { + const { rule } = this.props; + + this.props.togglePauseAlertRule(rule.id, { paused: rule.state === 'paused' }); }; renderText(text: string) { @@ -56,7 +61,7 @@ export default class AlertRuleItem extends PureComponent { @@ -68,3 +73,7 @@ export default class AlertRuleItem extends PureComponent { ); } } + +export default connect(null, { + togglePauseAlertRule, +})(AlertRuleItem); diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts index 3b80cb19c39..87afbfff665 100644 --- a/public/app/features/alerting/state/actions.ts +++ b/public/app/features/alerting/state/actions.ts @@ -1,6 +1,6 @@ import { Dispatch } from 'redux'; import { getBackendSrv } from 'app/core/services/backend_srv'; -import { AlertRule } from 'app/types'; +import { AlertRule, StoreState } from 'app/types'; export enum ActionTypes { LoadAlertRules = 'LOAD_ALERT_RULES', @@ -41,3 +41,19 @@ export const getAlertRulesAsync = (options: { state: string }) => async ( throw error; } }; + +export const togglePauseAlertRule = (id: number, options: { paused: boolean }) => async ( + // Maybe fix dispatch type? + dispatch: Dispatch, + getState: () => StoreState +): Promise => { + try { + await getBackendSrv().post(`/api/alerts/${id}/pause`, options); + const stateFilter = getState().location.query.state || 'all'; + dispatch(getAlertRulesAsync({ state: stateFilter.toString() })); + return true; + } catch (error) { + console.log(error); + throw error; + } +}; From f4594c8320e67831c8d476db7a840b356f149c84 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Sep 2018 16:58:11 +0200 Subject: [PATCH 18/27] some basic selector tests --- .../features/alerting/AlertRuleItem.test.tsx | 5 +++ .../features/alerting/state/selectors.test.ts | 43 +++++++++++++++++++ public/app/types/index.ts | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 public/app/features/alerting/state/selectors.test.ts diff --git a/public/app/features/alerting/AlertRuleItem.test.tsx b/public/app/features/alerting/AlertRuleItem.test.tsx index 0a1c5cbe437..397c5c5ac0c 100644 --- a/public/app/features/alerting/AlertRuleItem.test.tsx +++ b/public/app/features/alerting/AlertRuleItem.test.tsx @@ -2,6 +2,10 @@ import React from 'react'; import { shallow } from 'enzyme'; import AlertRuleItem, { Props } from './AlertRuleItem'; +jest.mock('react-redux', () => ({ + connect: params => params, +})); + const setup = (propOverrides?: object) => { const props: Props = { rule: { @@ -17,6 +21,7 @@ const setup = (propOverrides?: object) => { url: 'https://something.something.darkside', }, search: '', + togglePauseAlertRule: jest.fn(), }; Object.assign(props, propOverrides); diff --git a/public/app/features/alerting/state/selectors.test.ts b/public/app/features/alerting/state/selectors.test.ts new file mode 100644 index 00000000000..2d6d48caa2d --- /dev/null +++ b/public/app/features/alerting/state/selectors.test.ts @@ -0,0 +1,43 @@ +import { getSearchQuery, getAlertRuleItems } from './selectors'; +import { AlertRulesState } from '../../../types'; + +const defaultState: AlertRulesState = { + items: [], + searchQuery: '', +}; + +const getState = (overrides?: object) => Object.assign(defaultState, overrides); + +describe('Get search query', () => { + it('should get search query', () => { + const state = getState({ searchQuery: 'dashboard' }); + const result = getSearchQuery(state); + + expect(result).toEqual(state.searchQuery); + }); +}); + +describe('Get alert rule items', () => { + it('should get alert rule items', () => { + const state = getState({ + items: [ + { + id: 1, + dashboardId: 1, + panelId: 1, + name: '', + state: '', + stateText: '', + stateIcon: '', + stateClass: '', + stateAge: '', + url: '', + }, + ], + }); + + const result = getAlertRuleItems(state); + + expect(result.length).toEqual(0); + }); +}); diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 6aa4dc23d97..1f17962a70b 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -32,8 +32,8 @@ export interface AlertRule { stateIcon: string; stateClass: string; stateAge: string; - info?: string; url: string; + info?: string; executionError?: string; evalData?: { noData: boolean }; } From 5ac5a08e9e9b21ac711bcb2095fa9d10afb80119 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 4 Sep 2018 09:53:24 +0200 Subject: [PATCH 19/27] Fixed a bug in the test and added test for filter alert rules --- .../features/alerting/state/selectors.test.ts | 75 ++++++++++++++++--- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/public/app/features/alerting/state/selectors.test.ts b/public/app/features/alerting/state/selectors.test.ts index 2d6d48caa2d..e853b146c03 100644 --- a/public/app/features/alerting/state/selectors.test.ts +++ b/public/app/features/alerting/state/selectors.test.ts @@ -1,16 +1,8 @@ import { getSearchQuery, getAlertRuleItems } from './selectors'; -import { AlertRulesState } from '../../../types'; - -const defaultState: AlertRulesState = { - items: [], - searchQuery: '', -}; - -const getState = (overrides?: object) => Object.assign(defaultState, overrides); describe('Get search query', () => { it('should get search query', () => { - const state = getState({ searchQuery: 'dashboard' }); + const state = { searchQuery: 'dashboard' }; const result = getSearchQuery(state); expect(result).toEqual(state.searchQuery); @@ -19,7 +11,7 @@ describe('Get search query', () => { describe('Get alert rule items', () => { it('should get alert rule items', () => { - const state = getState({ + const state = { items: [ { id: 1, @@ -34,10 +26,69 @@ describe('Get alert rule items', () => { url: '', }, ], - }); + searchQuery: '', + }; const result = getAlertRuleItems(state); + expect(result.length).toEqual(1); + }); + + it('should filter rule items based on search query', () => { + const state = { + items: [ + { + id: 1, + dashboardId: 1, + panelId: 1, + name: 'dashboard', + state: '', + stateText: '', + stateIcon: '', + stateClass: '', + stateAge: '', + url: '', + }, + { + id: 2, + dashboardId: 3, + panelId: 1, + name: 'dashboard2', + state: '', + stateText: '', + stateIcon: '', + stateClass: '', + stateAge: '', + url: '', + }, + { + id: 3, + dashboardId: 5, + panelId: 1, + name: 'hello', + state: '', + stateText: '', + stateIcon: '', + stateClass: '', + stateAge: '', + url: '', + }, + { + id: 4, + dashboardId: 7, + panelId: 1, + name: 'test', + state: '', + stateText: 'dashboard', + stateIcon: '', + stateClass: '', + stateAge: '', + url: '', + }, + ], + searchQuery: 'dashboard', + }; - expect(result.length).toEqual(0); + const result = getAlertRuleItems(state); + expect(result.length).toEqual(3); }); }); From 22510be450a7930a9a907c4f84385ac75fa79fcc Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 4 Sep 2018 15:00:04 +0200 Subject: [PATCH 20/27] tests --- .../features/alerting/AlertRuleItem.test.tsx | 3 +- .../app/features/alerting/AlertRuleItem.tsx | 2 +- .../features/alerting/AlertRuleList.test.tsx | 192 ++++++++--- .../app/features/alerting/AlertRuleList.tsx | 31 +- .../__snapshots__/AlertRuleList.test.tsx.snap | 309 +++++++++++++----- public/app/features/alerting/state/actions.ts | 8 +- .../features/alerting/state/reducers.test.ts | 91 ++++++ .../app/features/alerting/state/reducers.ts | 39 +-- public/app/types/index.ts | 15 + 9 files changed, 516 insertions(+), 174 deletions(-) create mode 100644 public/app/features/alerting/state/reducers.test.ts diff --git a/public/app/features/alerting/AlertRuleItem.test.tsx b/public/app/features/alerting/AlertRuleItem.test.tsx index 397c5c5ac0c..1b356fa5687 100644 --- a/public/app/features/alerting/AlertRuleItem.test.tsx +++ b/public/app/features/alerting/AlertRuleItem.test.tsx @@ -3,7 +3,7 @@ import { shallow } from 'enzyme'; import AlertRuleItem, { Props } from './AlertRuleItem'; jest.mock('react-redux', () => ({ - connect: params => params, + connect: () => params => params, })); const setup = (propOverrides?: object) => { @@ -23,6 +23,7 @@ const setup = (propOverrides?: object) => { search: '', togglePauseAlertRule: jest.fn(), }; + Object.assign(props, propOverrides); return shallow(); diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx index 95c6966ab88..0e6b1c5fb90 100644 --- a/public/app/features/alerting/AlertRuleItem.tsx +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -15,7 +15,7 @@ class AlertRuleItem extends PureComponent { togglePaused = () => { const { rule } = this.props; - this.props.togglePauseAlertRule(rule.id, { paused: rule.state === 'paused' }); + this.props.togglePauseAlertRule(rule.id, { paused: rule.state !== 'paused' }); }; renderText(text: string) { diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index f88ff4522d4..9bcdcd41a3b 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -1,69 +1,159 @@ import React from 'react'; -import moment from 'moment'; -import { AlertRuleList } from './AlertRuleList'; -import { RootStore } from 'app/stores/RootStore/RootStore'; -import { backendSrv, createNavTree } from 'test/mocks/common'; -import { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; - -describe('AlertRuleList', () => { - let page, store; - - beforeAll(() => { - backendSrv.get.mockReturnValue( - Promise.resolve([ +import { shallow } from 'enzyme'; +import AlertRuleList, { Props } from './AlertRuleList'; +import { AlertRule, NavModel } from '../../types'; +import appEvents from '../../core/app_events'; + +jest.mock('react-redux', () => ({ + connect: () => params => params, +})); + +jest.mock('../../core/app_events', () => ({ + emit: jest.fn(), +})); + +const setup = (propOverrides?: object) => { + const props: Props = { + navModel: {} as NavModel, + alertRules: [] as AlertRule[], + updateLocation: jest.fn(), + getAlertRulesAsync: jest.fn(), + setSearchQuery: jest.fn(), + stateFilter: '', + search: '', + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + + return { + wrapper, + instance: wrapper.instance() as AlertRuleList, + }; +}; + +describe('Render', () => { + it('should render component', () => { + const { wrapper } = setup(); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should render alert rules', () => { + const { wrapper } = setup({ + alertRules: [ { - id: 11, - dashboardId: 58, + id: 1, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', panelId: 3, - name: 'Panel Title alert', + name: 'TestData - Always OK', state: 'ok', - newStateDate: moment() - .subtract(5, 'minutes') - .format(), + newStateDate: '2018-09-04T10:01:01+02:00', + evalDate: '0001-01-01T00:00:00Z', evalData: {}, executionError: '', - url: 'd/ufkcofof/my-goal', - canEdit: true, + url: '/d/ggHbN42mk/alerting-with-testdata', + }, + { + id: 3, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', + panelId: 3, + name: 'TestData - ok', + state: 'ok', + newStateDate: '2018-09-04T10:01:01+02:00', + evalDate: '0001-01-01T00:00:00Z', + evalData: {}, + executionError: 'error', + url: '/d/ggHbN42mk/alerting-with-testdata', }, - ]) - ); - - store = RootStore.create( - {}, - { - backendSrv: backendSrv, - navTree: createNavTree('alerting', 'alert-list'), - } - ); - - page = mount(); + ], + }); + + expect(wrapper).toMatchSnapshot(); + }); +}); + +describe('Life cycle', () => { + describe('component did mount', () => { + it('should call fetchrules', () => { + const { instance } = setup(); + instance.fetchRules = jest.fn(); + instance.componentDidMount(); + expect(instance.fetchRules).toHaveBeenCalled(); + }); }); - it('should call api to get rules', () => { - expect(backendSrv.get.mock.calls[0][0]).toEqual('/api/alerts'); + describe('component did update', () => { + it('should call fetchrules if props differ', () => { + const { instance } = setup(); + instance.fetchRules = jest.fn(); + + instance.componentDidUpdate({ stateFilter: 'ok' }); + + expect(instance.fetchRules).toHaveBeenCalled(); + }); }); +}); + +describe('Functions', () => { + describe('Get state filter', () => { + it('should get all if prop is not set', () => { + const { instance } = setup(); + + const stateFilter = instance.getStateFilter(); + + expect(stateFilter).toEqual('all'); + }); + + it('should return state filter if set', () => { + const { instance } = setup({ + stateFilter: 'ok', + }); - it('should render 1 rule', () => { - page.update(); - const ruleNode = page.find('.alert-rule-item'); - expect(toJson(ruleNode)).toMatchSnapshot(); + const stateFilter = instance.getStateFilter(); + + expect(stateFilter).toEqual('ok'); + }); + }); + + describe('State filter changed', () => { + it('should update location', () => { + const { instance } = setup(); + const mockEvent = { target: { value: 'alerting' } }; + + instance.onStateFilterChanged(mockEvent); + + expect(instance.props.updateLocation).toHaveBeenCalledWith({ query: { state: 'alerting' } }); + }); }); - it('toggle state should change pause rule if not paused', async () => { - backendSrv.post.mockReturnValue( - Promise.resolve({ - state: 'paused', - }) - ); + describe('Open how to', () => { + it('should emit show-modal event', () => { + const { instance } = setup(); + + instance.onOpenHowTo(); + + expect(appEvents.emit).toHaveBeenCalledWith('show-modal', { + src: 'public/app/features/alerting/partials/alert_howto.html', + modalClass: 'confirm-modal', + model: {}, + }); + }); + }); - page.find('.fa-pause').simulate('click'); + describe('Search query change', () => { + it('should set search query', () => { + const { instance } = setup(); + const mockEvent = { target: { value: 'dashboard' } }; - // wait for api call to resolve - await Promise.resolve(); - page.update(); + instance.onSearchQueryChange(mockEvent); - expect(store.alertList.rules[0].state).toBe('paused'); - expect(page.find('.fa-play')).toHaveLength(1); + expect(instance.props.setSearchQuery).toHaveBeenCalledWith('dashboard'); + }); }); }); diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 6023a1bb142..e8458e72f11 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -10,7 +10,7 @@ import { NavModel, StoreState, AlertRule } from 'app/types'; import { getAlertRulesAsync, setSearchQuery } from './state/actions'; import { getAlertRuleItems, getSearchQuery } from './state/selectors'; -interface Props { +export interface Props { navModel: NavModel; alertRules: AlertRule[]; updateLocation: typeof updateLocation; @@ -20,11 +20,7 @@ interface Props { search: string; } -interface State { - search: string; -} - -export class AlertRuleList extends PureComponent { +class AlertRuleList extends PureComponent { stateFilters = [ { text: 'All', value: 'all' }, { text: 'OK', value: 'ok' }, @@ -44,11 +40,9 @@ export class AlertRuleList extends PureComponent { } } - onStateFilterChanged = evt => { - this.props.updateLocation({ - query: { state: evt.target.value }, - }); - }; + async fetchRules() { + await this.props.getAlertRulesAsync({ state: this.getStateFilter() }); + } getStateFilter(): string { const { stateFilter } = this.props; @@ -58,9 +52,11 @@ export class AlertRuleList extends PureComponent { return 'all'; } - async fetchRules() { - await this.props.getAlertRulesAsync({ state: this.getStateFilter() }); - } + onStateFilterChanged = event => { + this.props.updateLocation({ + query: { state: event.target.value }, + }); + }; onOpenHowTo = () => { appEvents.emit('show-modal', { @@ -75,13 +71,13 @@ export class AlertRuleList extends PureComponent { this.props.setSearchQuery(value); }; - alertStateFilterOption({ text, value }) { + alertStateFilterOption = ({ text, value }) => { return ( ); - } + }; render() { const { navModel, alertRules, search } = this.props; @@ -112,14 +108,11 @@ export class AlertRuleList extends PureComponent {
    - -
      {alertRules.map(rule => )} diff --git a/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap index f408f6409be..99869ba6126 100644 --- a/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap +++ b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap @@ -1,103 +1,254 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AlertRuleList should render 1 rule 1`] = ` -
    1. - - - +exports[`Render should render alert rules 1`] = ` +
      +
      - - - - - OK - - - - - +
      - for - 5 minutes - + +
      + +
      +
        + + +
      +
      +
      +`; + +exports[`Render should render component 1`] = ` +
      +
      -
      +
      + +
      + +
      +
      + +
      +
        - +
      -
    2. + `; diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts index 87afbfff665..2dff257685f 100644 --- a/public/app/features/alerting/state/actions.ts +++ b/public/app/features/alerting/state/actions.ts @@ -1,6 +1,6 @@ import { Dispatch } from 'redux'; import { getBackendSrv } from 'app/core/services/backend_srv'; -import { AlertRule, StoreState } from 'app/types'; +import { AlertRuleApi, StoreState } from 'app/types'; export enum ActionTypes { LoadAlertRules = 'LOAD_ALERT_RULES', @@ -9,7 +9,7 @@ export enum ActionTypes { export interface LoadAlertRulesAction { type: ActionTypes.LoadAlertRules; - payload: AlertRule[]; + payload: AlertRuleApi[]; } export interface SetSearchQueryAction { @@ -17,7 +17,7 @@ export interface SetSearchQueryAction { payload: string; } -export const loadAlertRules = (rules: AlertRule[]): LoadAlertRulesAction => ({ +export const loadAlertRules = (rules: AlertRuleApi[]): LoadAlertRulesAction => ({ type: ActionTypes.LoadAlertRules, payload: rules, }); @@ -31,7 +31,7 @@ export type Action = LoadAlertRulesAction | SetSearchQueryAction; export const getAlertRulesAsync = (options: { state: string }) => async ( dispatch: Dispatch -): Promise => { +): Promise => { try { const rules = await getBackendSrv().get('/api/alerts', options); dispatch(loadAlertRules(rules)); diff --git a/public/app/features/alerting/state/reducers.test.ts b/public/app/features/alerting/state/reducers.test.ts new file mode 100644 index 00000000000..96ca7bacf6c --- /dev/null +++ b/public/app/features/alerting/state/reducers.test.ts @@ -0,0 +1,91 @@ +import { ActionTypes, Action } from './actions'; +import { alertRulesReducer, initialState } from './reducers'; +import { AlertRuleApi } from '../../../types'; + +describe('Alert rules', () => { + const payload: AlertRuleApi[] = [ + { + id: 2, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', + panelId: 4, + name: 'TestData - Always Alerting', + state: 'alerting', + newStateDate: '2018-09-04T10:00:30+02:00', + evalDate: '0001-01-01T00:00:00Z', + evalData: { evalMatches: [{ metric: 'A-series', tags: null, value: 215 }] }, + executionError: '', + url: '/d/ggHbN42mk/alerting-with-testdata', + }, + { + id: 1, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', + panelId: 3, + name: 'TestData - Always OK', + state: 'ok', + newStateDate: '2018-09-04T10:01:01+02:00', + evalDate: '0001-01-01T00:00:00Z', + evalData: {}, + executionError: '', + url: '/d/ggHbN42mk/alerting-with-testdata', + }, + { + id: 3, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', + panelId: 3, + name: 'TestData - ok', + state: 'ok', + newStateDate: '2018-09-04T10:01:01+02:00', + evalDate: '0001-01-01T00:00:00Z', + evalData: {}, + executionError: 'error', + url: '/d/ggHbN42mk/alerting-with-testdata', + }, + { + id: 4, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', + panelId: 3, + name: 'TestData - Paused', + state: 'paused', + newStateDate: '2018-09-04T10:01:01+02:00', + evalDate: '0001-01-01T00:00:00Z', + evalData: {}, + executionError: 'error', + url: '/d/ggHbN42mk/alerting-with-testdata', + }, + { + id: 5, + dashboardId: 7, + dashboardUid: 'ggHbN42mk', + dashboardSlug: 'alerting-with-testdata', + panelId: 3, + name: 'TestData - Ok', + state: 'ok', + newStateDate: '2018-09-04T10:01:01+02:00', + evalDate: '0001-01-01T00:00:00Z', + evalData: { + noData: true, + }, + executionError: 'error', + url: '/d/ggHbN42mk/alerting-with-testdata', + }, + ]; + + it('should set alert rules', () => { + const action: Action = { + type: ActionTypes.LoadAlertRules, + payload: payload, + }; + + const result = alertRulesReducer(initialState, action); + + expect(result.items).toEqual(payload); + }); +}); diff --git a/public/app/features/alerting/state/reducers.ts b/public/app/features/alerting/state/reducers.ts index a18d112dd94..73feb3cb260 100644 --- a/public/app/features/alerting/state/reducers.ts +++ b/public/app/features/alerting/state/reducers.ts @@ -1,40 +1,41 @@ import moment from 'moment'; -import { AlertRulesState } from 'app/types'; +import { AlertRuleApi, AlertRule, AlertRulesState } from 'app/types'; import { Action, ActionTypes } from './actions'; import alertDef from './alertDef'; export const initialState: AlertRulesState = { items: [], searchQuery: '' }; -export function setStateFields(rule, state) { +function convertToAlertRule(rule, state): AlertRule { const stateModel = alertDef.getStateDisplayModel(state); - rule.state = state; rule.stateText = stateModel.text; rule.stateIcon = stateModel.iconClass; rule.stateClass = stateModel.stateClass; rule.stateAge = moment(rule.newStateDate) .fromNow() .replace(' ago', ''); + + if (rule.state !== 'paused') { + if (rule.executionError) { + rule.info = 'Execution Error: ' + rule.executionError; + } + if (rule.evalData && rule.evalData.noData) { + rule.info = 'Query returned no data'; + } + } + + return rule; } export const alertRulesReducer = (state = initialState, action: Action): AlertRulesState => { switch (action.type) { case ActionTypes.LoadAlertRules: { - const alertRules = action.payload; - - for (const rule of alertRules) { - setStateFields(rule, rule.state); - - if (rule.state !== 'paused') { - if (rule.executionError) { - rule.info = 'Execution Error: ' + rule.executionError; - } - if (rule.evalData && rule.evalData.noData) { - rule.info = 'Query returned no data'; - } - } - } - - return { items: alertRules, searchQuery: state.searchQuery }; + const alertRules: AlertRuleApi[] = action.payload; + + const alertRulesViewModel: AlertRule[] = alertRules.map(rule => { + return convertToAlertRule(rule, rule.state); + }); + + return { items: alertRulesViewModel, searchQuery: state.searchQuery }; } case ActionTypes.SetSearchQuery: diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 1f17962a70b..debfcf58ac8 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -22,6 +22,21 @@ export type UrlQueryMap = { [s: string]: UrlQueryValue }; // Alerting // +export interface AlertRuleApi { + id: number; + dashboardId: number; + dashboardUid: string; + dashboardSlug: string; + panelId: number; + name: string; + state: string; + newStateDate: string; + evalDate: string; + evalData?: object; + executionError: string; + url: string; +} + export interface AlertRule { id: number; dashboardId: number; From 41dcd7641b273903de4a1f0d1274cb4b151bf7e5 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 4 Sep 2018 16:13:51 +0200 Subject: [PATCH 21/27] removed unused mobx state --- public/app/containers/ContainerProps.ts | 4 -- .../AlertListStore/AlertListStore.test.ts | 66 ------------------- .../stores/AlertListStore/AlertListStore.ts | 47 ------------- public/app/stores/AlertListStore/AlertRule.ts | 34 ---------- public/app/stores/AlertListStore/helpers.ts | 13 ---- public/app/stores/RootStore/RootStore.ts | 8 --- .../app/stores/ServerStatsStore/ServerStat.ts | 6 -- .../ServerStatsStore/ServerStatsStore.ts | 24 ------- 8 files changed, 202 deletions(-) delete mode 100644 public/app/stores/AlertListStore/AlertListStore.test.ts delete mode 100644 public/app/stores/AlertListStore/AlertListStore.ts delete mode 100644 public/app/stores/AlertListStore/AlertRule.ts delete mode 100644 public/app/stores/AlertListStore/helpers.ts delete mode 100644 public/app/stores/ServerStatsStore/ServerStat.ts delete mode 100644 public/app/stores/ServerStatsStore/ServerStatsStore.ts diff --git a/public/app/containers/ContainerProps.ts b/public/app/containers/ContainerProps.ts index 97889278fdc..903edd83567 100644 --- a/public/app/containers/ContainerProps.ts +++ b/public/app/containers/ContainerProps.ts @@ -1,16 +1,12 @@ import { SearchStore } from './../stores/SearchStore/SearchStore'; -import { ServerStatsStore } from './../stores/ServerStatsStore/ServerStatsStore'; import { NavStore } from './../stores/NavStore/NavStore'; import { PermissionsStore } from './../stores/PermissionsStore/PermissionsStore'; -import { AlertListStore } from './../stores/AlertListStore/AlertListStore'; import { ViewStore } from './../stores/ViewStore/ViewStore'; import { FolderStore } from './../stores/FolderStore/FolderStore'; interface ContainerProps { search: typeof SearchStore.Type; - serverStats: typeof ServerStatsStore.Type; nav: typeof NavStore.Type; - alertList: typeof AlertListStore.Type; permissions: typeof PermissionsStore.Type; view: typeof ViewStore.Type; folder: typeof FolderStore.Type; diff --git a/public/app/stores/AlertListStore/AlertListStore.test.ts b/public/app/stores/AlertListStore/AlertListStore.test.ts deleted file mode 100644 index f0ab24b6cfc..00000000000 --- a/public/app/stores/AlertListStore/AlertListStore.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { AlertListStore } from './AlertListStore'; -import { backendSrv } from 'test/mocks/common'; -import moment from 'moment'; - -function getRule(name, state, info) { - return { - id: 11, - dashboardId: 58, - panelId: 3, - name: name, - state: state, - newStateDate: moment() - .subtract(5, 'minutes') - .format(), - evalData: {}, - executionError: '', - url: 'db/mygool', - stateText: state, - stateIcon: 'fa', - stateClass: 'asd', - stateAge: '10m', - info: info, - canEdit: true, - }; -} - -describe('AlertListStore', () => { - let store; - - beforeAll(() => { - store = AlertListStore.create( - { - rules: [ - getRule('Europe', 'OK', 'backend-01'), - getRule('Google', 'ALERTING', 'backend-02'), - getRule('Amazon', 'PAUSED', 'backend-03'), - getRule('West-Europe', 'PAUSED', 'backend-03'), - ], - search: '', - }, - { - backendSrv: backendSrv, - } - ); - }); - - it('search should filter list on name', () => { - store.setSearchQuery('urope'); - expect(store.filteredRules).toHaveLength(2); - }); - - it('search should filter list on state', () => { - store.setSearchQuery('ale'); - expect(store.filteredRules).toHaveLength(1); - }); - - it('search should filter list on info', () => { - store.setSearchQuery('-0'); - expect(store.filteredRules).toHaveLength(4); - }); - - it('search should be equal', () => { - store.setSearchQuery('alert'); - expect(store.search).toBe('alert'); - }); -}); diff --git a/public/app/stores/AlertListStore/AlertListStore.ts b/public/app/stores/AlertListStore/AlertListStore.ts deleted file mode 100644 index c2b9f5e4962..00000000000 --- a/public/app/stores/AlertListStore/AlertListStore.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { types, getEnv, flow } from 'mobx-state-tree'; -import { AlertRule as AlertRuleModel } from './AlertRule'; -import { setStateFields } from './helpers'; - -type AlertRuleType = typeof AlertRuleModel.Type; -export interface AlertRule extends AlertRuleType {} - -export const AlertListStore = types - .model('AlertListStore', { - rules: types.array(AlertRuleModel), - stateFilter: types.optional(types.string, 'all'), - search: types.optional(types.string, ''), - }) - .views(self => ({ - get filteredRules() { - const regex = new RegExp(self.search, 'i'); - return self.rules.filter(alert => { - return regex.test(alert.name) || regex.test(alert.stateText) || regex.test(alert.info); - }); - }, - })) - .actions(self => ({ - loadRules: flow(function* load(filters) { - const backendSrv = getEnv(self).backendSrv; - self.stateFilter = filters.state; // store state filter used in api query - const apiRules = yield backendSrv.get('/api/alerts', filters); - self.rules.clear(); - - for (const rule of apiRules) { - setStateFields(rule, rule.state); - - if (rule.state !== 'paused') { - if (rule.executionError) { - rule.info = 'Execution Error: ' + rule.executionError; - } - if (rule.evalData && rule.evalData.noData) { - rule.info = 'Query returned no data'; - } - } - - self.rules.push(AlertRuleModel.create(rule)); - } - }), - setSearchQuery(query: string) { - self.search = query; - }, - })); diff --git a/public/app/stores/AlertListStore/AlertRule.ts b/public/app/stores/AlertListStore/AlertRule.ts deleted file mode 100644 index 9c039be6ec2..00000000000 --- a/public/app/stores/AlertListStore/AlertRule.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { types, getEnv, flow } from 'mobx-state-tree'; -import { setStateFields } from './helpers'; - -export const AlertRule = types - .model('AlertRule', { - id: types.identifier(types.number), - dashboardId: types.number, - panelId: types.number, - name: types.string, - state: types.string, - stateText: types.string, - stateIcon: types.string, - stateClass: types.string, - stateAge: types.string, - info: types.optional(types.string, ''), - url: types.string, - }) - .views(self => ({ - get isPaused() { - return self.state === 'paused'; - }, - })) - .actions(self => ({ - /** - * will toggle alert rule paused state - */ - togglePaused: flow(function* togglePaused() { - const backendSrv = getEnv(self).backendSrv; - const payload = { paused: !self.isPaused }; - const res = yield backendSrv.post(`/api/alerts/${self.id}/pause`, payload); - setStateFields(self, res.state); - self.info = ''; - }), - })); diff --git a/public/app/stores/AlertListStore/helpers.ts b/public/app/stores/AlertListStore/helpers.ts deleted file mode 100644 index 4d1ddcc30e0..00000000000 --- a/public/app/stores/AlertListStore/helpers.ts +++ /dev/null @@ -1,13 +0,0 @@ -import moment from 'moment'; -import alertDef from 'app/features/alerting/state/alertDef'; - -export function setStateFields(rule, state) { - const stateModel = alertDef.getStateDisplayModel(state); - rule.state = state; - rule.stateText = stateModel.text; - rule.stateIcon = stateModel.iconClass; - rule.stateClass = stateModel.stateClass; - rule.stateAge = moment(rule.newStateDate) - .fromNow() - .replace(' ago', ''); -} diff --git a/public/app/stores/RootStore/RootStore.ts b/public/app/stores/RootStore/RootStore.ts index bb85a85d9dd..5853744a68f 100644 --- a/public/app/stores/RootStore/RootStore.ts +++ b/public/app/stores/RootStore/RootStore.ts @@ -1,8 +1,6 @@ import { types } from 'mobx-state-tree'; import { SearchStore } from './../SearchStore/SearchStore'; -import { ServerStatsStore } from './../ServerStatsStore/ServerStatsStore'; import { NavStore } from './../NavStore/NavStore'; -import { AlertListStore } from './../AlertListStore/AlertListStore'; import { ViewStore } from './../ViewStore/ViewStore'; import { FolderStore } from './../FolderStore/FolderStore'; import { PermissionsStore } from './../PermissionsStore/PermissionsStore'; @@ -12,13 +10,7 @@ export const RootStore = types.model({ search: types.optional(SearchStore, { sections: [], }), - serverStats: types.optional(ServerStatsStore, { - stats: [], - }), nav: types.optional(NavStore, {}), - alertList: types.optional(AlertListStore, { - rules: [], - }), permissions: types.optional(PermissionsStore, { fetching: false, items: [], diff --git a/public/app/stores/ServerStatsStore/ServerStat.ts b/public/app/stores/ServerStatsStore/ServerStat.ts deleted file mode 100644 index bd819a51e76..00000000000 --- a/public/app/stores/ServerStatsStore/ServerStat.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { types } from 'mobx-state-tree'; - -export const ServerStat = types.model('ServerStat', { - name: types.string, - value: types.optional(types.number, 0), -}); diff --git a/public/app/stores/ServerStatsStore/ServerStatsStore.ts b/public/app/stores/ServerStatsStore/ServerStatsStore.ts deleted file mode 100644 index d27285d7a3b..00000000000 --- a/public/app/stores/ServerStatsStore/ServerStatsStore.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { types, getEnv, flow } from 'mobx-state-tree'; -import { ServerStat } from './ServerStat'; - -export const ServerStatsStore = types - .model('ServerStatsStore', { - stats: types.array(ServerStat), - error: types.optional(types.string, ''), - }) - .actions(self => ({ - load: flow(function* load() { - const backendSrv = getEnv(self).backendSrv; - const res = yield backendSrv.get('/api/admin/stats'); - self.stats.clear(); - self.stats.push(ServerStat.create({ name: 'Total dashboards', value: res.dashboards })); - self.stats.push(ServerStat.create({ name: 'Total users', value: res.users })); - self.stats.push(ServerStat.create({ name: 'Active users (seen last 30 days)', value: res.activeUsers })); - self.stats.push(ServerStat.create({ name: 'Total orgs', value: res.orgs })); - self.stats.push(ServerStat.create({ name: 'Total playlists', value: res.playlists })); - self.stats.push(ServerStat.create({ name: 'Total snapshots', value: res.snapshots })); - self.stats.push(ServerStat.create({ name: 'Total dashboard tags', value: res.tags })); - self.stats.push(ServerStat.create({ name: 'Total starred dashboards', value: res.stars })); - self.stats.push(ServerStat.create({ name: 'Total alerts', value: res.alerts })); - }), - })); From 1e5ad4da7894d1b617ac221fd06e2798a90c2893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 4 Sep 2018 12:59:46 -0700 Subject: [PATCH 22/27] redux: minor changes to redux thunk actions and use of typings --- .../features/alerting/AlertRuleList.test.tsx | 2 +- .../app/features/alerting/AlertRuleList.tsx | 2 +- public/app/features/alerting/state/actions.ts | 36 +++++++------------ 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index 9bcdcd41a3b..3c0c410d1a9 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import AlertRuleList, { Props } from './AlertRuleList'; +import { AlertRuleList, Props } from './AlertRuleList'; import { AlertRule, NavModel } from '../../types'; import appEvents from '../../core/app_events'; diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index e8458e72f11..4b48da47256 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -20,7 +20,7 @@ export interface Props { search: string; } -class AlertRuleList extends PureComponent { +export class AlertRuleList extends PureComponent { stateFilters = [ { text: 'All', value: 'all' }, { text: 'OK', value: 'ok' }, diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts index 2dff257685f..ca50d9e1038 100644 --- a/public/app/features/alerting/state/actions.ts +++ b/public/app/features/alerting/state/actions.ts @@ -1,6 +1,6 @@ -import { Dispatch } from 'redux'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { AlertRuleApi, StoreState } from 'app/types'; +import { ThunkAction } from 'redux-thunk'; export enum ActionTypes { LoadAlertRules = 'LOAD_ALERT_RULES', @@ -29,31 +29,19 @@ export const setSearchQuery = (query: string): SetSearchQueryAction => ({ export type Action = LoadAlertRulesAction | SetSearchQueryAction; -export const getAlertRulesAsync = (options: { state: string }) => async ( - dispatch: Dispatch -): Promise => { - try { +type ThunkResult = ThunkAction; + +export function getAlertRulesAsync(options: { state: string }): ThunkResult { + return async dispatch => { const rules = await getBackendSrv().get('/api/alerts', options); dispatch(loadAlertRules(rules)); - return rules; - } catch (error) { - console.error(error); - throw error; - } -}; - -export const togglePauseAlertRule = (id: number, options: { paused: boolean }) => async ( - // Maybe fix dispatch type? - dispatch: Dispatch, - getState: () => StoreState -): Promise => { - try { + }; +} + +export function togglePauseAlertRule(id: number, options: { paused: boolean }): ThunkResult { + return async (dispatch, getState) => { await getBackendSrv().post(`/api/alerts/${id}/pause`, options); const stateFilter = getState().location.query.state || 'all'; dispatch(getAlertRulesAsync({ state: stateFilter.toString() })); - return true; - } catch (error) { - console.log(error); - throw error; - } -}; + }; +} From 68767acb1c8a027a2dde7967d64a2adba24c80bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 4 Sep 2018 13:15:45 -0700 Subject: [PATCH 23/27] fix: Updated test --- public/app/features/alerting/AlertRuleList.test.tsx | 6 +----- .../alerting/__snapshots__/AlertRuleList.test.tsx.snap | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index 3c0c410d1a9..70892d86589 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -4,10 +4,6 @@ import { AlertRuleList, Props } from './AlertRuleList'; import { AlertRule, NavModel } from '../../types'; import appEvents from '../../core/app_events'; -jest.mock('react-redux', () => ({ - connect: () => params => params, -})); - jest.mock('../../core/app_events', () => ({ emit: jest.fn(), })); @@ -93,7 +89,7 @@ describe('Life cycle', () => { const { instance } = setup(); instance.fetchRules = jest.fn(); - instance.componentDidUpdate({ stateFilter: 'ok' }); + instance.componentDidUpdate({ stateFilter: 'ok' } as Props); expect(instance.fetchRules).toHaveBeenCalled(); }); diff --git a/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap index 99869ba6126..46000d974db 100644 --- a/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap +++ b/public/app/features/alerting/__snapshots__/AlertRuleList.test.tsx.snap @@ -101,7 +101,7 @@ exports[`Render should render alert rules 1`] = `
        - - Date: Tue, 4 Sep 2018 22:38:18 -0700 Subject: [PATCH 24/27] mobx: removed unused SearchStore --- public/app/containers/ContainerProps.ts | 2 -- .../core/components/search/SearchResult.tsx | 14 ++-------- public/app/stores/RootStore/RootStore.ts | 4 --- public/app/stores/SearchStore/ResultItem.ts | 10 ------- .../stores/SearchStore/SearchResultSection.ts | 27 ------------------- public/app/stores/SearchStore/SearchStore.ts | 22 --------------- 6 files changed, 2 insertions(+), 77 deletions(-) delete mode 100644 public/app/stores/SearchStore/ResultItem.ts delete mode 100644 public/app/stores/SearchStore/SearchResultSection.ts delete mode 100644 public/app/stores/SearchStore/SearchStore.ts diff --git a/public/app/containers/ContainerProps.ts b/public/app/containers/ContainerProps.ts index 903edd83567..ce09b992f80 100644 --- a/public/app/containers/ContainerProps.ts +++ b/public/app/containers/ContainerProps.ts @@ -1,11 +1,9 @@ -import { SearchStore } from './../stores/SearchStore/SearchStore'; import { NavStore } from './../stores/NavStore/NavStore'; import { PermissionsStore } from './../stores/PermissionsStore/PermissionsStore'; import { ViewStore } from './../stores/ViewStore/ViewStore'; import { FolderStore } from './../stores/FolderStore/FolderStore'; interface ContainerProps { - search: typeof SearchStore.Type; nav: typeof NavStore.Type; permissions: typeof PermissionsStore.Type; view: typeof ViewStore.Type; diff --git a/public/app/core/components/search/SearchResult.tsx b/public/app/core/components/search/SearchResult.tsx index 3141d29ac7f..13333c168f9 100644 --- a/public/app/core/components/search/SearchResult.tsx +++ b/public/app/core/components/search/SearchResult.tsx @@ -1,22 +1,13 @@ import React from 'react'; import classNames from 'classnames'; -import { observer } from 'mobx-react'; -import { store } from 'app/stores/store'; -export interface SearchResultProps { - search: any; -} - -@observer -export class SearchResult extends React.Component { +export class SearchResult extends React.Component { constructor(props) { super(props); this.state = { - search: store.search, + search: '', }; - - store.search.query(); } render() { @@ -30,7 +21,6 @@ export interface SectionProps { section: any; } -@observer export class SearchResultSection extends React.Component { constructor(props) { super(props); diff --git a/public/app/stores/RootStore/RootStore.ts b/public/app/stores/RootStore/RootStore.ts index 5853744a68f..fba25e5f015 100644 --- a/public/app/stores/RootStore/RootStore.ts +++ b/public/app/stores/RootStore/RootStore.ts @@ -1,5 +1,4 @@ import { types } from 'mobx-state-tree'; -import { SearchStore } from './../SearchStore/SearchStore'; import { NavStore } from './../NavStore/NavStore'; import { ViewStore } from './../ViewStore/ViewStore'; import { FolderStore } from './../FolderStore/FolderStore'; @@ -7,9 +6,6 @@ import { PermissionsStore } from './../PermissionsStore/PermissionsStore'; import { TeamsStore } from './../TeamsStore/TeamsStore'; export const RootStore = types.model({ - search: types.optional(SearchStore, { - sections: [], - }), nav: types.optional(NavStore, {}), permissions: types.optional(PermissionsStore, { fetching: false, diff --git a/public/app/stores/SearchStore/ResultItem.ts b/public/app/stores/SearchStore/ResultItem.ts deleted file mode 100644 index eb0ff021526..00000000000 --- a/public/app/stores/SearchStore/ResultItem.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { types } from 'mobx-state-tree'; - -export const ResultItem = types.model('ResultItem', { - id: types.identifier(types.number), - folderId: types.optional(types.number, 0), - title: types.string, - url: types.string, - icon: types.string, - folderTitle: types.optional(types.string, ''), -}); diff --git a/public/app/stores/SearchStore/SearchResultSection.ts b/public/app/stores/SearchStore/SearchResultSection.ts deleted file mode 100644 index 70b3ad48e96..00000000000 --- a/public/app/stores/SearchStore/SearchResultSection.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { types } from 'mobx-state-tree'; -import { ResultItem } from './ResultItem'; - -export const SearchResultSection = types - .model('SearchResultSection', { - id: types.identifier(), - title: types.string, - icon: types.string, - expanded: types.boolean, - items: types.array(ResultItem), - }) - .actions(self => ({ - toggle() { - self.expanded = !self.expanded; - - for (let i = 0; i < 100; i++) { - self.items.push( - ResultItem.create({ - id: i, - title: 'Dashboard ' + self.items.length, - icon: 'gicon gicon-dashboard', - url: 'asd', - }) - ); - } - }, - })); diff --git a/public/app/stores/SearchStore/SearchStore.ts b/public/app/stores/SearchStore/SearchStore.ts deleted file mode 100644 index 36897f05f38..00000000000 --- a/public/app/stores/SearchStore/SearchStore.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { types } from 'mobx-state-tree'; -import { SearchResultSection } from './SearchResultSection'; - -export const SearchStore = types - .model('SearchStore', { - sections: types.array(SearchResultSection), - }) - .actions(self => ({ - query() { - for (let i = 0; i < 100; i++) { - self.sections.push( - SearchResultSection.create({ - id: 'starred' + i, - title: 'starred', - icon: 'fa fa-fw fa-star-o', - expanded: false, - items: [], - }) - ); - } - }, - })); From a440d3510a36af850fb003ad39741f991d8c521b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Sep 2018 17:55:38 +0200 Subject: [PATCH 25/27] renaming things in admin --- public/app/core/selectors/navModel.ts | 2 +- ...n_edit_org_ctrl.ts => AdminEditOrgCtrl.ts} | 4 +-- ...edit_user_ctrl.ts => AdminEditUserCtrl.ts} | 5 +--- ...list_orgs_ctrl.ts => AdminListOrgsCtrl.ts} | 4 +-- ...st_users_ctrl.ts => AdminListUsersCtrl.ts} | 0 .../{containers => }/ServerStats.test.tsx | 4 +-- .../admin/{containers => }/ServerStats.tsx | 2 +- .../__snapshots__/ServerStats.test.tsx.snap | 0 .../app/features/admin/{admin.ts => index.ts} | 26 ++++++------------- .../admin/{apis/index.ts => state/apis.ts} | 0 public/app/features/all.ts | 2 +- public/app/routes/routes.ts | 2 +- public/test/jest-setup.ts | 12 ++++----- public/test/mocks/common.ts | 2 +- 14 files changed, 24 insertions(+), 41 deletions(-) rename public/app/features/admin/{admin_edit_org_ctrl.ts => AdminEditOrgCtrl.ts} (88%) rename public/app/features/admin/{admin_edit_user_ctrl.ts => AdminEditUserCtrl.ts} (95%) rename public/app/features/admin/{admin_list_orgs_ctrl.ts => AdminListOrgsCtrl.ts} (84%) rename public/app/features/admin/{admin_list_users_ctrl.ts => AdminListUsersCtrl.ts} (100%) rename public/app/features/admin/{containers => }/ServerStats.test.tsx (89%) rename public/app/features/admin/{containers => }/ServerStats.tsx (96%) rename public/app/features/admin/{containers => }/__snapshots__/ServerStats.test.tsx.snap (100%) rename public/app/features/admin/{admin.ts => index.ts} (57%) rename public/app/features/admin/{apis/index.ts => state/apis.ts} (100%) diff --git a/public/app/core/selectors/navModel.ts b/public/app/core/selectors/navModel.ts index a7e1c3330bd..8b3a3edd84e 100644 --- a/public/app/core/selectors/navModel.ts +++ b/public/app/core/selectors/navModel.ts @@ -1,7 +1,7 @@ import { NavModel, NavModelItem, NavIndex } from 'app/types'; function getNotFoundModel(): NavModel { - var node: NavModelItem = { + const node: NavModelItem = { id: 'not-found', text: 'Page not found', icon: 'fa fa-fw fa-warning', diff --git a/public/app/features/admin/admin_edit_org_ctrl.ts b/public/app/features/admin/AdminEditOrgCtrl.ts similarity index 88% rename from public/app/features/admin/admin_edit_org_ctrl.ts rename to public/app/features/admin/AdminEditOrgCtrl.ts index ec3f8548023..3117c5f0f9b 100644 --- a/public/app/features/admin/admin_edit_org_ctrl.ts +++ b/public/app/features/admin/AdminEditOrgCtrl.ts @@ -1,6 +1,5 @@ -import angular from 'angular'; -export class AdminEditOrgCtrl { +export default class AdminEditOrgCtrl { /** @ngInject */ constructor($scope, $routeParams, backendSrv, $location, navModelSrv) { $scope.init = () => { @@ -48,4 +47,3 @@ export class AdminEditOrgCtrl { } } -angular.module('grafana.controllers').controller('AdminEditOrgCtrl', AdminEditOrgCtrl); diff --git a/public/app/features/admin/admin_edit_user_ctrl.ts b/public/app/features/admin/AdminEditUserCtrl.ts similarity index 95% rename from public/app/features/admin/admin_edit_user_ctrl.ts rename to public/app/features/admin/AdminEditUserCtrl.ts index c34ccdc1cad..bf72c1746aa 100644 --- a/public/app/features/admin/admin_edit_user_ctrl.ts +++ b/public/app/features/admin/AdminEditUserCtrl.ts @@ -1,7 +1,6 @@ -import angular from 'angular'; import _ from 'lodash'; -export class AdminEditUserCtrl { +export default class AdminEditUserCtrl { /** @ngInject */ constructor($scope, $routeParams, backendSrv, $location, navModelSrv) { $scope.user = {}; @@ -117,5 +116,3 @@ export class AdminEditUserCtrl { $scope.init(); } } - -angular.module('grafana.controllers').controller('AdminEditUserCtrl', AdminEditUserCtrl); diff --git a/public/app/features/admin/admin_list_orgs_ctrl.ts b/public/app/features/admin/AdminListOrgsCtrl.ts similarity index 84% rename from public/app/features/admin/admin_list_orgs_ctrl.ts rename to public/app/features/admin/AdminListOrgsCtrl.ts index 0513752aa3e..9190f7f494e 100644 --- a/public/app/features/admin/admin_list_orgs_ctrl.ts +++ b/public/app/features/admin/AdminListOrgsCtrl.ts @@ -1,6 +1,5 @@ -import angular from 'angular'; -export class AdminListOrgsCtrl { +export default class AdminListOrgsCtrl { /** @ngInject */ constructor($scope, backendSrv, navModelSrv) { $scope.init = () => { @@ -33,4 +32,3 @@ export class AdminListOrgsCtrl { } } -angular.module('grafana.controllers').controller('AdminListOrgsCtrl', AdminListOrgsCtrl); diff --git a/public/app/features/admin/admin_list_users_ctrl.ts b/public/app/features/admin/AdminListUsersCtrl.ts similarity index 100% rename from public/app/features/admin/admin_list_users_ctrl.ts rename to public/app/features/admin/AdminListUsersCtrl.ts diff --git a/public/app/features/admin/containers/ServerStats.test.tsx b/public/app/features/admin/ServerStats.test.tsx similarity index 89% rename from public/app/features/admin/containers/ServerStats.test.tsx rename to public/app/features/admin/ServerStats.test.tsx index e12dfc3bed4..cbcc580f612 100644 --- a/public/app/features/admin/containers/ServerStats.test.tsx +++ b/public/app/features/admin/ServerStats.test.tsx @@ -2,14 +2,14 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { ServerStats } from './ServerStats'; import { createNavModel } from 'test/mocks/common'; -import { ServerStat } from '../apis'; +import { ServerStat } from './state/apis'; describe('ServerStats', () => { it('Should render table with stats', done => { const navModel = createNavModel('Admin', 'stats'); const stats: ServerStat[] = [{ name: 'Total dashboards', value: 10 }, { name: 'Total Users', value: 1 }]; - let getServerStats = () => { + const getServerStats = () => { return Promise.resolve(stats); }; diff --git a/public/app/features/admin/containers/ServerStats.tsx b/public/app/features/admin/ServerStats.tsx similarity index 96% rename from public/app/features/admin/containers/ServerStats.tsx rename to public/app/features/admin/ServerStats.tsx index 97419ec9301..40be87ed4d3 100644 --- a/public/app/features/admin/containers/ServerStats.tsx +++ b/public/app/features/admin/ServerStats.tsx @@ -3,7 +3,7 @@ import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import { NavModel, StoreState } from 'app/types'; import { getNavModel } from 'app/core/selectors/navModel'; -import { getServerStats, ServerStat } from '../apis'; +import { getServerStats, ServerStat } from './state/apis'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; interface Props { diff --git a/public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap similarity index 100% rename from public/app/features/admin/containers/__snapshots__/ServerStats.test.tsx.snap rename to public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap diff --git a/public/app/features/admin/admin.ts b/public/app/features/admin/index.ts similarity index 57% rename from public/app/features/admin/admin.ts rename to public/app/features/admin/index.ts index 00e98821779..7fc14791ea7 100644 --- a/public/app/features/admin/admin.ts +++ b/public/app/features/admin/index.ts @@ -1,7 +1,7 @@ -import AdminListUsersCtrl from './admin_list_users_ctrl'; -import './admin_list_orgs_ctrl'; -import './admin_edit_org_ctrl'; -import './admin_edit_user_ctrl'; +import AdminListUsersCtrl from './AdminListUsersCtrl'; +import AdminEditUserCtrl from './AdminEditUserCtrl'; +import AdminListOrgsCtrl from './AdminListOrgsCtrl'; +import AdminEditOrgCtrl from './AdminEditOrgCtrl'; import coreModule from 'app/core/core_module'; @@ -27,21 +27,11 @@ class AdminHomeCtrl { } } -export class AdminStatsCtrl { - stats: any; - navModel: any; - - /** @ngInject */ - constructor(backendSrv: any, navModelSrv) { - this.navModel = navModelSrv.getNav('cfg', 'admin', 'server-stats', 1); +coreModule.controller('AdminListUsersCtrl', AdminListUsersCtrl); +coreModule.controller('AdminEditUserCtrl', AdminEditUserCtrl); - backendSrv.get('/api/admin/stats').then(stats => { - this.stats = stats; - }); - } -} +coreModule.controller('AdminListOrgsCtrl', AdminListOrgsCtrl); +coreModule.controller('AdminEditOrgCtrl', AdminEditOrgCtrl); coreModule.controller('AdminSettingsCtrl', AdminSettingsCtrl); coreModule.controller('AdminHomeCtrl', AdminHomeCtrl); -coreModule.controller('AdminStatsCtrl', AdminStatsCtrl); -coreModule.controller('AdminListUsersCtrl', AdminListUsersCtrl); diff --git a/public/app/features/admin/apis/index.ts b/public/app/features/admin/state/apis.ts similarity index 100% rename from public/app/features/admin/apis/index.ts rename to public/app/features/admin/state/apis.ts diff --git a/public/app/features/all.ts b/public/app/features/all.ts index 065f399cae3..0285e9c352a 100644 --- a/public/app/features/all.ts +++ b/public/app/features/all.ts @@ -8,7 +8,7 @@ import './playlist/all'; import './snapshot/all'; import './panel/all'; import './org/all'; -import './admin/admin'; +import './admin'; import './alerting/NotificationsEditCtrl'; import './alerting/NotificationsListCtrl'; import './styleguide/styleguide'; diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index 86afe685887..0fd76a8c8eb 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -1,7 +1,7 @@ import './dashboard_loaders'; import './ReactContainer'; -import ServerStats from 'app/features/admin/containers/ServerStats'; +import ServerStats from 'app/features/admin/ServerStats'; import AlertRuleList from 'app/features/alerting/AlertRuleList'; import FolderSettings from 'app/containers/ManageDashboards/FolderSettings'; import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions'; diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index f97e4ec4b91..9079754dc28 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -21,19 +21,19 @@ configure({ adapter: new Adapter() }); const global = window as any; global.$ = global.jQuery = $; -const localStorageMock = (function() { - var store = {}; +const localStorageMock = (() => { + let store = {}; return { - getItem: function(key) { + getItem: key => { return store[key]; }, - setItem: function(key, value) { + setItem: (key, value) => { store[key] = value.toString(); }, - clear: function() { + clear: () => { store = {}; }, - removeItem: function(key) { + removeItem: key => { delete store[key]; }, }; diff --git a/public/test/mocks/common.ts b/public/test/mocks/common.ts index 1c7bdb4f1e2..385f72621a9 100644 --- a/public/test/mocks/common.ts +++ b/public/test/mocks/common.ts @@ -31,7 +31,7 @@ export function createNavModel(title: string, ...tabs: string[]): NavModel { breadcrumbs: [], }; - for (let tab of tabs) { + for (const tab of tabs) { node.children.push({ id: tab, icon: 'icon', From 6bdaf57ae7b340e6b04a7143a70fe6b19395da80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Sep 2018 09:29:53 +0200 Subject: [PATCH 26/27] refactor: changed AlertRuleItem pause action to callback --- .../features/alerting/AlertRuleItem.test.tsx | 2 +- .../app/features/alerting/AlertRuleItem.tsx | 20 +++++-------------- .../features/alerting/AlertRuleList.test.tsx | 1 + .../app/features/alerting/AlertRuleList.tsx | 17 ++++++++++++++-- .../__snapshots__/AlertRuleItem.test.tsx.snap | 2 +- .../__snapshots__/AlertRuleList.test.tsx.snap | 6 ++++-- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/public/app/features/alerting/AlertRuleItem.test.tsx b/public/app/features/alerting/AlertRuleItem.test.tsx index 1b356fa5687..bd37e127c39 100644 --- a/public/app/features/alerting/AlertRuleItem.test.tsx +++ b/public/app/features/alerting/AlertRuleItem.test.tsx @@ -21,7 +21,7 @@ const setup = (propOverrides?: object) => { url: 'https://something.something.darkside', }, search: '', - togglePauseAlertRule: jest.fn(), + onTogglePause: jest.fn(), }; Object.assign(props, propOverrides); diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx index 0e6b1c5fb90..f47a6348303 100644 --- a/public/app/features/alerting/AlertRuleItem.tsx +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -1,23 +1,15 @@ import React, { PureComponent } from 'react'; -import { connect } from 'react-redux'; import Highlighter from 'react-highlight-words'; import classNames from 'classnames/bind'; -import { togglePauseAlertRule } from './state/actions'; import { AlertRule } from '../../types'; export interface Props { rule: AlertRule; search: string; - togglePauseAlertRule: typeof togglePauseAlertRule; + onTogglePause: () => void; } -class AlertRuleItem extends PureComponent { - togglePaused = () => { - const { rule } = this.props; - - this.props.togglePauseAlertRule(rule.id, { paused: rule.state !== 'paused' }); - }; - +class AlertRuleItem extends PureComponent { renderText(text: string) { return ( { } render() { - const { rule } = this.props; + const { rule, onTogglePause } = this.props; const stateClass = classNames({ fa: true, @@ -61,7 +53,7 @@ class AlertRuleItem extends PureComponent { @@ -74,6 +66,4 @@ class AlertRuleItem extends PureComponent { } } -export default connect(null, { - togglePauseAlertRule, -})(AlertRuleItem); +export default AlertRuleItem; diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index 70892d86589..2d1cf653540 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -15,6 +15,7 @@ const setup = (propOverrides?: object) => { updateLocation: jest.fn(), getAlertRulesAsync: jest.fn(), setSearchQuery: jest.fn(), + togglePauseAlertRule: jest.fn(), stateFilter: '', search: '', }; diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 4b48da47256..d25fc659af5 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -7,7 +7,7 @@ import appEvents from 'app/core/app_events'; import { updateLocation } from 'app/core/actions'; import { getNavModel } from 'app/core/selectors/navModel'; import { NavModel, StoreState, AlertRule } from 'app/types'; -import { getAlertRulesAsync, setSearchQuery } from './state/actions'; +import { getAlertRulesAsync, setSearchQuery, togglePauseAlertRule } from './state/actions'; import { getAlertRuleItems, getSearchQuery } from './state/selectors'; export interface Props { @@ -16,6 +16,7 @@ export interface Props { updateLocation: typeof updateLocation; getAlertRulesAsync: typeof getAlertRulesAsync; setSearchQuery: typeof setSearchQuery; + togglePauseAlertRule: typeof togglePauseAlertRule; stateFilter: string; search: string; } @@ -71,6 +72,10 @@ export class AlertRuleList extends PureComponent { this.props.setSearchQuery(value); }; + onTogglePause = (rule: AlertRule) => { + this.props.togglePauseAlertRule(rule.id, { paused: rule.state !== 'paused' }); + }; + alertStateFilterOption = ({ text, value }) => { return (