From 071f30aa4229769b7c21b2920e4defb3bc00c248 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 4 Jun 2017 17:52:12 +0800 Subject: [PATCH 01/14] configure redux-thunk --- package.json | 1 + src-react/store.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8b439c2..8fb1c78 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "react-dom": "^15.5.4", "react-redux": "^5.0.4", "redux": "^3.6.0", + "redux-thunk": "^2.2.0", "rimraf": "~2.6.1", "semver": "~5.3.0", "serve-static": "~1.12.2", diff --git a/src-react/store.js b/src-react/store.js index 5af56c6..5a16f3a 100644 --- a/src-react/store.js +++ b/src-react/store.js @@ -1,6 +1,7 @@ -import { createStore } from 'redux'; +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; import ungitApp from './reducers'; -const store = createStore(ungitApp); +const store = createStore(ungitApp, applyMiddleware(thunk)); export default store; \ No newline at end of file From ab8e04c805e42aad4c5377d7a77f73f6864fc3a0 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 4 Jun 2017 18:17:38 +0800 Subject: [PATCH 02/14] add cors for express --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 8fb1c78..8dc29e6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "bootstrap-sass": "^3.3.7", "color": "~1.0.3", "cookie-parser": "~1.4.3", + "cors": "^2.8.3", "crossroads": "~0.12.2", "diff2html": "^2.3.0", "express": "~4.15.2", From 2f94e0fa906bbee39aa60156032129d8dea00ae7 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 4 Jun 2017 18:18:05 +0800 Subject: [PATCH 03/14] use cors and create new api for fetching configuration --- source/server.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source/server.js b/source/server.js index 473151f..5f77050 100644 --- a/source/server.js +++ b/source/server.js @@ -3,6 +3,7 @@ const BugTracker = require('./bugtracker'); const bugtracker = new BugTracker('server'); const usageStatistics = require('./usage-statistics'); const express = require('express'); +const cors = require('cors') const gitApi = require('./git-api'); const winston = require('winston'); const sysinfo = require('./sysinfo'); @@ -107,6 +108,7 @@ const noCache = (req, res, next) => { app.use(noCache); app.use(require('body-parser').json()); +app.use(cors()); // we should consider to remove it when we complete this project if (config.autoShutdownTimeout) { let autoShutdownTimeout; @@ -270,6 +272,20 @@ app.get('/serverdata.js', (req, res) => { }); }); +app.get('/ungit/config', (req, res) => { + sysinfo.getUserHash() + .then((hash) => { + const ungitConfig = { + config, + userHash: hash, + version: config.ungitDevVersion, + platform: os.platform(), + pluginApiVersion: require('../package.json').ungitPluginApiVersion + }; + res.send(JSON.stringify(ungitConfig)); + }); +}); + app.get('/api/latestversion', (req, res) => { sysinfo.getUngitLatestVersion() .then((latestVersion) => { From b97cd663030ae3d8dd0fdbb00e2e8fde0334d3b7 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 4 Jun 2017 18:19:31 +0800 Subject: [PATCH 04/14] basic implementation for fetching ungit configuration on path container --- src-react/actions/ungit-config.js | 19 +++++++++++++++++++ src-react/constants/action-types.js | 1 + src-react/containers/path.js | 27 ++++++++++++++++++++------- src-react/reducers/index.js | 14 +++++++++++--- 4 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 src-react/actions/ungit-config.js create mode 100644 src-react/constants/action-types.js diff --git a/src-react/actions/ungit-config.js b/src-react/actions/ungit-config.js new file mode 100644 index 0000000..5eb9bcd --- /dev/null +++ b/src-react/actions/ungit-config.js @@ -0,0 +1,19 @@ +import * as types from 'constants/action-types'; +export function fetchUngitConfig() { + return dispatch => { + // consider wrap API call in separate modules + // it will be easy to stub module's function when testing + fetch('http://localhost:8448/ungit/config') + .then(response => response.json()) + .then(json => { + dispatch(receiveUgitConfig(json)); + }); + }; +}; + +export function receiveUgitConfig(ungitConfig) { + return { + type: types.RECEIVE_UNGIT_CONFIG, + ungitConfig + }; +}; \ No newline at end of file diff --git a/src-react/constants/action-types.js b/src-react/constants/action-types.js new file mode 100644 index 0000000..d072643 --- /dev/null +++ b/src-react/constants/action-types.js @@ -0,0 +1 @@ +export const RECEIVE_UNGIT_CONFIG = 'RECEIVE_UNGIT_CONFIG'; \ No newline at end of file diff --git a/src-react/containers/path.js b/src-react/containers/path.js index 499d4e9..8ffe285 100644 --- a/src-react/containers/path.js +++ b/src-react/containers/path.js @@ -1,19 +1,32 @@ import React, { Component } from 'react'; +import { bindActionCreators } from 'redux'; import { connect } from 'react-redux' +import * as ungitConfigActionCreators from 'actions/ungit-config'; import 'styles/styles.scss'; -@connect(state => { return { ...state } }) +@connect(state => { + return { ...state }; +}, dispatch => { + return { + actions: bindActionCreators(Object.assign({}, ungitConfigActionCreators), dispatch) + }; +}) class Path extends Component { + + componentWillMount() { + const { actions } = this.props; + actions.fetchUngitConfig(); + } + render() { return ( -
-
-

Welcome to { this.props.app }

+
+
+
+
+
-

- To get started, edit src/container/Path.js and save to reload. -

); } diff --git a/src-react/reducers/index.js b/src-react/reducers/index.js index c89fed9..f500e6d 100644 --- a/src-react/reducers/index.js +++ b/src-react/reducers/index.js @@ -1,9 +1,17 @@ +import * as types from 'constants/action-types'; + const initialState = { - app: 'React' + ungitConfig: null }; -const ungitApp = function(state, action) { - return { ...initialState }; +const ungitApp = function(state = initialState, action) { + switch(action.type) { + case types.RECEIVE_UNGIT_CONFIG: + const { ungitConfig } = action; + return { ...state, ungitConfig }; + default: + return { ...state }; + } } export default ungitApp; \ No newline at end of file From 8dbd6984c34e6022dce674ad0598859d0238f7b9 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 4 Jun 2017 21:23:28 +0800 Subject: [PATCH 05/14] enhance error handling and also handle UI staus when API is calling --- src-react/actions/ungit-config.js | 22 ++++++++++++++++++++-- src-react/constants/action-types.js | 4 +++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src-react/actions/ungit-config.js b/src-react/actions/ungit-config.js index 5eb9bcd..4261588 100644 --- a/src-react/actions/ungit-config.js +++ b/src-react/actions/ungit-config.js @@ -1,12 +1,17 @@ import * as types from 'constants/action-types'; + export function fetchUngitConfig() { return dispatch => { + dispatch(pending()); // consider wrap API call in separate modules // it will be easy to stub module's function when testing fetch('http://localhost:8448/ungit/config') .then(response => response.json()) .then(json => { dispatch(receiveUgitConfig(json)); + }) + .catch(e => { + dispatch(apiError(e.message)); }); }; }; @@ -14,6 +19,19 @@ export function fetchUngitConfig() { export function receiveUgitConfig(ungitConfig) { return { type: types.RECEIVE_UNGIT_CONFIG, - ungitConfig + payload: ungitConfig + }; +}; + +export function pending() { + return { + type: types.PATH_PAGE_PENDING + }; +}; + +export function apiError(message) { + return { + type: types.PATH_PAGE_API_ERR, + payload: message }; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src-react/constants/action-types.js b/src-react/constants/action-types.js index d072643..e19c0d9 100644 --- a/src-react/constants/action-types.js +++ b/src-react/constants/action-types.js @@ -1 +1,3 @@ -export const RECEIVE_UNGIT_CONFIG = 'RECEIVE_UNGIT_CONFIG'; \ No newline at end of file +export const RECEIVE_UNGIT_CONFIG = 'RECEIVE_UNGIT_CONFIG'; +export const PATH_PAGE_PENDING = 'PATH_PAGE_PENDING'; +export const PATH_PAGE_API_ERR = 'PATH_PAGE_API_ERR'; \ No newline at end of file From 568dce0200a3ab3e22d740860ea0c8b5b4ed2a76 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 4 Jun 2017 21:25:41 +0800 Subject: [PATCH 06/14] Refine reducers: 1. move initialState to store and inject it when store creating 2. separate reducers --- src-react/reducers/index.js | 21 ++++++++------------- src-react/reducers/path.js | 17 +++++++++++++++++ src-react/reducers/ungit-config.js | 13 +++++++++++++ src-react/store.js | 10 +++++++++- 4 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 src-react/reducers/path.js create mode 100644 src-react/reducers/ungit-config.js diff --git a/src-react/reducers/index.js b/src-react/reducers/index.js index f500e6d..ff48d57 100644 --- a/src-react/reducers/index.js +++ b/src-react/reducers/index.js @@ -1,17 +1,12 @@ -import * as types from 'constants/action-types'; +import { combineReducers } from 'redux'; -const initialState = { - ungitConfig: null -}; +import path from './path'; +import ungitConfig from './ungit-config'; -const ungitApp = function(state = initialState, action) { - switch(action.type) { - case types.RECEIVE_UNGIT_CONFIG: - const { ungitConfig } = action; - return { ...state, ungitConfig }; - default: - return { ...state }; - } -} + +const ungitApp = combineReducers({ + ungitConfig, + path +}); export default ungitApp; \ No newline at end of file diff --git a/src-react/reducers/path.js b/src-react/reducers/path.js new file mode 100644 index 0000000..825b771 --- /dev/null +++ b/src-react/reducers/path.js @@ -0,0 +1,17 @@ +import * as types from 'constants/action-types'; + +function path(state, action) { + switch(action.type) { + case types.PATH_PAGE_PENDING: + return { ...state, pending: true }; + case types.RECEIVE_UNGIT_CONFIG: + return { ...state, pending: false }; + case types.PATH_PAGE_API_ERR: + const { payload: errMessage } = action + return { ...state, pending: false, errMessage }; + default: + return { ...state }; + } +} + +export default path; \ No newline at end of file diff --git a/src-react/reducers/ungit-config.js b/src-react/reducers/ungit-config.js new file mode 100644 index 0000000..6bbac31 --- /dev/null +++ b/src-react/reducers/ungit-config.js @@ -0,0 +1,13 @@ +import * as types from 'constants/action-types'; + +function ungitConfig(state, action) { + switch(action.type) { + case types.RECEIVE_UNGIT_CONFIG: + const { payload: ungitConfig } = action; + return { ...ungitConfig }; + default: + return { ...state }; + } +} + +export default ungitConfig; \ No newline at end of file diff --git a/src-react/store.js b/src-react/store.js index 5a16f3a..2818c55 100644 --- a/src-react/store.js +++ b/src-react/store.js @@ -2,6 +2,14 @@ import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import ungitApp from './reducers'; -const store = createStore(ungitApp, applyMiddleware(thunk)); +const initialState = { + ungitConfig: null, + path: { + pending: false, + errMessage: null + } +}; + +const store = createStore(ungitApp, initialState, applyMiddleware(thunk)); export default store; \ No newline at end of file From 03e2edd9e85bcc64121f4e6427cb800dd63fac49 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 5 Jun 2017 23:43:44 +0800 Subject: [PATCH 07/14] [refactor-1] separate actionCreators: ungitConfig: for ungit configuraion userConfig: for user configuration version: for the versions of ungit itself and git common: some common actionCreators bootstrap: for initialization, aggregrate some actionCreators and use it when app init --- src-react/actions/bootstrap.js | 12 ++++++++ src-react/actions/common.js | 15 ++++++++++ src-react/actions/ungit-config.js | 23 +++++---------- src-react/actions/user-config.js | 25 ++++++++++++++++ src-react/actions/version.js | 46 +++++++++++++++++++++++++++++ src-react/constants/action-types.js | 3 ++ 6 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 src-react/actions/bootstrap.js create mode 100644 src-react/actions/common.js create mode 100644 src-react/actions/user-config.js create mode 100644 src-react/actions/version.js diff --git a/src-react/actions/bootstrap.js b/src-react/actions/bootstrap.js new file mode 100644 index 0000000..6d8e971 --- /dev/null +++ b/src-react/actions/bootstrap.js @@ -0,0 +1,12 @@ +import { fetchUngitConfig } from './ungit-config'; +import { fetchLatestVersion, fetchGitVersion } from './version'; +import { pending } from './common'; + +export function bootstrap() { + return dispatch => { + dispatch(pending()); + dispatch(fetchUngitConfig()); + dispatch(fetchLatestVersion()); + dispatch(fetchGitVersion()); + }; +} \ No newline at end of file diff --git a/src-react/actions/common.js b/src-react/actions/common.js new file mode 100644 index 0000000..f6d09fd --- /dev/null +++ b/src-react/actions/common.js @@ -0,0 +1,15 @@ +/* This export common using actionCreator */ +import * as types from 'constants/action-types'; + +export function pending() { + return { + type: types.PATH_PAGE_PENDING + }; +}; + +export function apiError(message) { + return { + type: types.PATH_PAGE_API_ERR, + payload: message + }; +} \ No newline at end of file diff --git a/src-react/actions/ungit-config.js b/src-react/actions/ungit-config.js index 4261588..73c8318 100644 --- a/src-react/actions/ungit-config.js +++ b/src-react/actions/ungit-config.js @@ -1,13 +1,17 @@ import * as types from 'constants/action-types'; +import { fetchUserConfig } from './user-config'; +import { apiError } from './common'; export function fetchUngitConfig() { return dispatch => { - dispatch(pending()); // consider wrap API call in separate modules // it will be easy to stub module's function when testing fetch('http://localhost:8448/ungit/config') .then(response => response.json()) .then(json => { + if (!json.config.bugtracking) { + dispatch(fetchUserConfig()); + } dispatch(receiveUgitConfig(json)); }) .catch(e => { @@ -16,22 +20,9 @@ export function fetchUngitConfig() { }; }; -export function receiveUgitConfig(ungitConfig) { +function receiveUgitConfig(ungitConfig) { return { type: types.RECEIVE_UNGIT_CONFIG, payload: ungitConfig }; -}; - -export function pending() { - return { - type: types.PATH_PAGE_PENDING - }; -}; - -export function apiError(message) { - return { - type: types.PATH_PAGE_API_ERR, - payload: message - }; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src-react/actions/user-config.js b/src-react/actions/user-config.js new file mode 100644 index 0000000..03f71a6 --- /dev/null +++ b/src-react/actions/user-config.js @@ -0,0 +1,25 @@ +import * as types from 'constants/action-types'; +import { apiError } from './common'; + +export function fetchUserConfig() { + return dispatch => { + // consider wrap API call in separate modules + // it will be easy to stub module's function when testing + fetch('http://localhost:8448/api/userconfig') + .then(response => response.json()) + .then(json => { + dispatch(receiveUserConfig(json)); + }) + .catch(e => { + dispatch(apiError(e.message)); + }); + }; +}; + + +function receiveUserConfig(userConfig) { + return { + type: types.RECEIVE_USER_CONFIG, + payload: userConfig + }; +}; \ No newline at end of file diff --git a/src-react/actions/version.js b/src-react/actions/version.js new file mode 100644 index 0000000..a4c53fb --- /dev/null +++ b/src-react/actions/version.js @@ -0,0 +1,46 @@ +import * as types from 'constants/action-types'; +import { apiError } from './common'; + +export function fetchLatestVersion() { + return dispatch => { + // consider wrap API call in separate modules + // it will be easy to stub module's function when testing + fetch('http://localhost:8448/api/latestversion') + .then(response => response.json()) + .then(json => { + dispatch(receiveLatestVersion(json)); + }) + .catch(e => { + dispatch(apiError(e.message)); + }); + }; +} + +export function fetchGitVersion() { + return dispatch => { + // consider wrap API call in separate modules + // it will be easy to stub module's function when testing + fetch('http://localhost:8448/api/gitversion') + .then(response => response.json()) + .then(json => { + dispatch(receiveGitVersion(json)); + }) + .catch(e => { + dispatch(apiError(e.message)); + }); + }; +} + +function receiveGitVersion(gitVersion) { + return { + type: types.RECEIVE_GIT_VERSION, + payload: gitVersion + }; +} + +function receiveLatestVersion(latestVersion) { + return { + type: types.RECEIVE_LATEST_VERSION, + payload: latestVersion + }; +}; \ No newline at end of file diff --git a/src-react/constants/action-types.js b/src-react/constants/action-types.js index e19c0d9..69ec0e4 100644 --- a/src-react/constants/action-types.js +++ b/src-react/constants/action-types.js @@ -1,3 +1,6 @@ +export const RECEIVE_GIT_VERSION = 'RECEIVE_GIT_VERSION'; +export const RECEIVE_LATEST_VERSION = 'RECEIVE_LATEST_VERSION'; +export const RECEIVE_USER_CONFIG = 'RECEIVE_USER_CONFIG'; export const RECEIVE_UNGIT_CONFIG = 'RECEIVE_UNGIT_CONFIG'; export const PATH_PAGE_PENDING = 'PATH_PAGE_PENDING'; export const PATH_PAGE_API_ERR = 'PATH_PAGE_API_ERR'; \ No newline at end of file From f7c8b7e6933b910e52dc4d75eed28e0d9d624e03 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 5 Jun 2017 23:44:37 +0800 Subject: [PATCH 08/14] [refactor-2] add some states into state tree --- src-react/reducers/index.js | 4 ++++ src-react/reducers/path.js | 7 +++++-- src-react/reducers/user-config.js | 13 +++++++++++++ src-react/reducers/versions.js | 16 ++++++++++++++++ src-react/store.js | 7 ++++++- 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src-react/reducers/user-config.js create mode 100644 src-react/reducers/versions.js diff --git a/src-react/reducers/index.js b/src-react/reducers/index.js index ff48d57..38607cc 100644 --- a/src-react/reducers/index.js +++ b/src-react/reducers/index.js @@ -2,10 +2,14 @@ import { combineReducers } from 'redux'; import path from './path'; import ungitConfig from './ungit-config'; +import userConfig from './user-config'; +import versions from './versions'; const ungitApp = combineReducers({ ungitConfig, + userConfig, + versions, path }); diff --git a/src-react/reducers/path.js b/src-react/reducers/path.js index 825b771..f84d434 100644 --- a/src-react/reducers/path.js +++ b/src-react/reducers/path.js @@ -5,10 +5,13 @@ function path(state, action) { case types.PATH_PAGE_PENDING: return { ...state, pending: true }; case types.RECEIVE_UNGIT_CONFIG: + case types.RECEIVE_USER_CONFIG: + case types.RECEIVE_GIT_VERSION: + case types.RECEIVE_LATEST_VERSION: return { ...state, pending: false }; case types.PATH_PAGE_API_ERR: - const { payload: errMessage } = action - return { ...state, pending: false, errMessage }; + const { payload: errMessage } = action; + return { ...state, pending: false, errMessage: [ ...state.errMessage, errMessage ] }; default: return { ...state }; } diff --git a/src-react/reducers/user-config.js b/src-react/reducers/user-config.js new file mode 100644 index 0000000..23f3b65 --- /dev/null +++ b/src-react/reducers/user-config.js @@ -0,0 +1,13 @@ +import * as types from 'constants/action-types'; + +function userConfig(state, action) { + switch(action.type) { + case types.RECEIVE_USER_CONFIG: + const { payload: userConfig } = action; + return { ...userConfig }; + default: + return { ...state }; + } +} + +export default userConfig; \ No newline at end of file diff --git a/src-react/reducers/versions.js b/src-react/reducers/versions.js new file mode 100644 index 0000000..9ddae17 --- /dev/null +++ b/src-react/reducers/versions.js @@ -0,0 +1,16 @@ +import * as types from 'constants/action-types'; + +function userConfig(state, action) { + switch(action.type) { + case types.RECEIVE_GIT_VERSION: + const { payload: gitVersion } = action; + return { ...state, gitVersion }; + case types.RECEIVE_LATEST_VERSION: + const { payload: latestVersion } = action; + return { ...state, latestVersion }; + default: + return { ...state }; + } +} + +export default userConfig; \ No newline at end of file diff --git a/src-react/store.js b/src-react/store.js index 2818c55..f8148be 100644 --- a/src-react/store.js +++ b/src-react/store.js @@ -4,9 +4,14 @@ import ungitApp from './reducers'; const initialState = { ungitConfig: null, + userConfig: null, + versions: { + gitVersion: null, + latestVersion: null + }, path: { pending: false, - errMessage: null + errMessage: [] } }; From ae63c56ed8d76a58a145f43453ba612bbb65a098 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 5 Jun 2017 23:45:18 +0800 Subject: [PATCH 09/14] change to calling bootstrap action for initialize data --- src-react/containers/path.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-react/containers/path.js b/src-react/containers/path.js index 8ffe285..811c3be 100644 --- a/src-react/containers/path.js +++ b/src-react/containers/path.js @@ -2,21 +2,21 @@ import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux' -import * as ungitConfigActionCreators from 'actions/ungit-config'; +import * as bootstrapActionCreators from 'actions/bootstrap'; import 'styles/styles.scss'; @connect(state => { return { ...state }; }, dispatch => { return { - actions: bindActionCreators(Object.assign({}, ungitConfigActionCreators), dispatch) + actions: bindActionCreators(Object.assign({}, bootstrapActionCreators), dispatch) }; }) class Path extends Component { componentWillMount() { const { actions } = this.props; - actions.fetchUngitConfig(); + actions.bootstrap(); } render() { From 1b73f13cdb2b5f6a9866598d4a5d4ab66fe0c661 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 10 Jun 2017 13:53:49 +0800 Subject: [PATCH 10/14] implement pending mechanism --- src-react/actions/bootstrap.js | 2 +- src-react/actions/common.js | 5 +++-- src-react/containers/path.js | 12 ++++++++++-- src-react/reducers/path.js | 6 +++--- src-react/store.js | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src-react/actions/bootstrap.js b/src-react/actions/bootstrap.js index 6d8e971..f5a4676 100644 --- a/src-react/actions/bootstrap.js +++ b/src-react/actions/bootstrap.js @@ -4,7 +4,7 @@ import { pending } from './common'; export function bootstrap() { return dispatch => { - dispatch(pending()); + dispatch(pending(4)); dispatch(fetchUngitConfig()); dispatch(fetchLatestVersion()); dispatch(fetchGitVersion()); diff --git a/src-react/actions/common.js b/src-react/actions/common.js index f6d09fd..de1a3c2 100644 --- a/src-react/actions/common.js +++ b/src-react/actions/common.js @@ -1,9 +1,10 @@ /* This export common using actionCreator */ import * as types from 'constants/action-types'; -export function pending() { +export function pending(count) { return { - type: types.PATH_PAGE_PENDING + type: types.PATH_PAGE_PENDING, + payload: count || 1 }; }; diff --git a/src-react/containers/path.js b/src-react/containers/path.js index 811c3be..ae9dd87 100644 --- a/src-react/containers/path.js +++ b/src-react/containers/path.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux' +import AlerArea from 'components/alert-area'; import * as bootstrapActionCreators from 'actions/bootstrap'; import 'styles/styles.scss'; @@ -20,12 +21,19 @@ class Path extends Component { } render() { + const { path: { pending, errMessage }, app } = this.props; return (
-
-
+ { + pending === 0 && errMessage.length === 0 ? ( + + ) : null + }
); diff --git a/src-react/reducers/path.js b/src-react/reducers/path.js index f84d434..f3d3469 100644 --- a/src-react/reducers/path.js +++ b/src-react/reducers/path.js @@ -3,15 +3,15 @@ import * as types from 'constants/action-types'; function path(state, action) { switch(action.type) { case types.PATH_PAGE_PENDING: - return { ...state, pending: true }; + return { ...state, pending: state.pending + action.payload }; case types.RECEIVE_UNGIT_CONFIG: case types.RECEIVE_USER_CONFIG: case types.RECEIVE_GIT_VERSION: case types.RECEIVE_LATEST_VERSION: - return { ...state, pending: false }; + return { ...state, pending: state.pending - 1 }; case types.PATH_PAGE_API_ERR: const { payload: errMessage } = action; - return { ...state, pending: false, errMessage: [ ...state.errMessage, errMessage ] }; + return { ...state, pending: state.pending - 1, errMessage: [ ...state.errMessage, errMessage ] }; default: return { ...state }; } diff --git a/src-react/store.js b/src-react/store.js index f8148be..3f8030c 100644 --- a/src-react/store.js +++ b/src-react/store.js @@ -10,7 +10,7 @@ const initialState = { latestVersion: null }, path: { - pending: false, + pending: null, errMessage: [] } }; From 1743ba56d7e1ff2d1c061ba07f8a824f2efb7431 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 10 Jun 2017 13:58:21 +0800 Subject: [PATCH 11/14] create config reducers --- src-react/reducers/config.js | 13 +++++++++++++ src-react/reducers/index.js | 9 ++------- src-react/store.js | 12 +++++++----- 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 src-react/reducers/config.js diff --git a/src-react/reducers/config.js b/src-react/reducers/config.js new file mode 100644 index 0000000..fcb2a08 --- /dev/null +++ b/src-react/reducers/config.js @@ -0,0 +1,13 @@ +import { combineReducers } from 'redux'; + +import ungitConfig from './ungit-config'; +import userConfig from './user-config'; +import versions from './versions'; + +const config = combineReducers({ + ungitConfig, + userConfig, + versions +}); + +export default config; \ No newline at end of file diff --git a/src-react/reducers/index.js b/src-react/reducers/index.js index 38607cc..49e9d38 100644 --- a/src-react/reducers/index.js +++ b/src-react/reducers/index.js @@ -1,15 +1,10 @@ import { combineReducers } from 'redux'; import path from './path'; -import ungitConfig from './ungit-config'; -import userConfig from './user-config'; -import versions from './versions'; - +import config from './config'; const ungitApp = combineReducers({ - ungitConfig, - userConfig, - versions, + config, path }); diff --git a/src-react/store.js b/src-react/store.js index 3f8030c..a51ac47 100644 --- a/src-react/store.js +++ b/src-react/store.js @@ -3,11 +3,13 @@ import thunk from 'redux-thunk'; import ungitApp from './reducers'; const initialState = { - ungitConfig: null, - userConfig: null, - versions: { - gitVersion: null, - latestVersion: null + config: { + ungitConfig: null, + userConfig: null, + versions: { + gitVersion: null, + latestVersion: null + } }, path: { pending: null, From f7530a5e5a1389141b057ffab5c0dc1dff076e17 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 10 Jun 2017 14:33:16 +0800 Subject: [PATCH 12/14] implement app and alert-area --- .../alert-area/bug-tracking-nag-screen.js | 17 +++++ .../alert-area/git-version-error.js | 18 ++++++ src-react/components/alert-area/index.js | 62 +++++++++++++++++++ .../alert-area/new-version-available.js | 24 +++++++ src-react/components/alert-area/nps-survey.js | 37 +++++++++++ src-react/containers/path.js | 3 +- src-react/reducers/app.js | 48 ++++++++++++++ src-react/reducers/index.js | 17 ++--- src-react/store.js | 6 ++ 9 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 src-react/components/alert-area/bug-tracking-nag-screen.js create mode 100644 src-react/components/alert-area/git-version-error.js create mode 100644 src-react/components/alert-area/index.js create mode 100644 src-react/components/alert-area/new-version-available.js create mode 100644 src-react/components/alert-area/nps-survey.js create mode 100644 src-react/reducers/app.js diff --git a/src-react/components/alert-area/bug-tracking-nag-screen.js b/src-react/components/alert-area/bug-tracking-nag-screen.js new file mode 100644 index 0000000..a587388 --- /dev/null +++ b/src-react/components/alert-area/bug-tracking-nag-screen.js @@ -0,0 +1,17 @@ +import React, { Component } from 'react'; + +class BugTrackingNagScreen extends Component { + render() { + return ( +
+ +

Help make ungit better with the press of a button!

+ + + +
+ ); + } +} + +export default BugTrackingNagScreen; \ No newline at end of file diff --git a/src-react/components/alert-area/git-version-error.js b/src-react/components/alert-area/git-version-error.js new file mode 100644 index 0000000..e164f9a --- /dev/null +++ b/src-react/components/alert-area/git-version-error.js @@ -0,0 +1,18 @@ +import React, { Component, PropTypes } from 'react'; + +class GitVersionError extends Component { + static propTypes = { + gitVersionError: PropTypes.string + } + + render() { + return ( +
+ { this.props.gitVersionError } + +
+ ); + } +} + +export default GitVersionError; \ No newline at end of file diff --git a/src-react/components/alert-area/index.js b/src-react/components/alert-area/index.js new file mode 100644 index 0000000..095d6b1 --- /dev/null +++ b/src-react/components/alert-area/index.js @@ -0,0 +1,62 @@ +import React, { Component, PropTypes } from 'react'; + +import GitVersionError from './git-version-error'; +import NewVersionAvailable from './new-version-available'; +import BugTrackingNagScreen from './bug-tracking-nag-screen'; +import NPSSurvey from './nps-survey'; + +class AlertArea extends Component { + static propTypes = { + config: PropTypes.object.isRequired, + gitVersionErrorVisible: PropTypes.bool.isRequired, + showNewVersionAvailable: PropTypes.bool.isRequired, + showBugtrackingNagscreen: PropTypes.bool.isRequired, + showNPSSurvey: PropTypes.bool.isRequired + } + + render() { + const { + config: { + ungitConfig: { platform }, + versions: { + gitVersion: { error }, + latestVersion: { + latestVersion + } + } + }, + gitVersionErrorVisible, + showNewVersionAvailable, + showBugtrackingNagscreen, + showNPSSurvey + } = this.props; + return ( +
+ { + gitVersionErrorVisible ? ( + + ) : null + } + { + showNewVersionAvailable ? ( + + ) : null + } + { + showBugtrackingNagscreen ? ( + + ) : null + } + { + showNPSSurvey ? ( + + ) : null + } +
+ ); + } +} + +export default AlertArea; \ No newline at end of file diff --git a/src-react/components/alert-area/new-version-available.js b/src-react/components/alert-area/new-version-available.js new file mode 100644 index 0000000..ed138cf --- /dev/null +++ b/src-react/components/alert-area/new-version-available.js @@ -0,0 +1,24 @@ +import React, { Component, PropTypes } from 'react'; + +class NewVersionAvailable extends Component { + static propTypes = { + latestVersion: PropTypes.string, + platform: PropTypes.string + } + + render() { + const newVersionInstallCommand = `#{this.props.platform ? '' : 'sudo -H'}npm update -g ungit`; + return ( +
+ A new version of ungit ({ this.props.latestVersion }) is + available! Run + { newVersionInstallCommand } to install. + See what's new in the + changelog. + +
+ ); + } +} + +export default NewVersionAvailable; \ No newline at end of file diff --git a/src-react/components/alert-area/nps-survey.js b/src-react/components/alert-area/nps-survey.js new file mode 100644 index 0000000..b313d9c --- /dev/null +++ b/src-react/components/alert-area/nps-survey.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; + +class NPSSurvey extends Component { + static propTypes = { + } + + render() { + return ( +
+ + Hi! This is a one-question survey to learn more about how people use Ungit. You can dismiss it by clicking the x in the upper right corner. +

Question: How likely are you to recommend Ungit to your friends and colleagues?

+

+

+ 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +
+
+ Not at all likely +
Extremely likely
+
+

+
+ ); + } +} + +export default NPSSurvey; \ No newline at end of file diff --git a/src-react/containers/path.js b/src-react/containers/path.js index ae9dd87..e556d5a 100644 --- a/src-react/containers/path.js +++ b/src-react/containers/path.js @@ -31,7 +31,8 @@ class Path extends Component { + showBugtrackingNagscreen={ app.showBugtrackingNagscreen } + showNPSSurvey={ app.showNPSSurvey } /> ) : null }
diff --git a/src-react/reducers/app.js b/src-react/reducers/app.js new file mode 100644 index 0000000..52c889e --- /dev/null +++ b/src-react/reducers/app.js @@ -0,0 +1,48 @@ +import * as types from 'constants/action-types'; + +function app(state, action, config) { + + switch(action.type) { + case types.RECEIVE_UNGIT_CONFIG: { + const { ungitConfig } = config; + const bugtrackingNagscreenDismissed = localStorage.getItem('bugtrackingNagscreenDismissed'); + const showBugtrackingNagscreen = !ungitConfig.config.bugtracking && !bugtrackingNagscreenDismissed; + + return { ...state, showBugtrackingNagscreen }; + } + + case types.RECEIVE_GIT_VERSION: { + const { ungitConfig, versions: { gitVersion } } = config; + const gitVersionCheckOverride = ungitConfig.config && ungitConfig.config.gitVersionCheckOverride; + const gitVersionErrorDismissed = localStorage.getItem('gitVersionErrorDismissed'); + const gitVersionError = gitVersion && !gitVersion.satisfied && gitVersion.error; + const gitVersionErrorVisible = !gitVersionCheckOverride && gitVersionError && gitVersionErrorDismissed; + + return { ...state, gitVersionErrorVisible }; + } + + case types.RECEIVE_LATEST_VERSION: { + const { ungitConfig, versions: { latestVersion } } = config; + const gitVersionCheckOverride = ungitConfig.config && ungitConfig.config.gitVersionCheckOverride; + const outdated = latestVersion && latestVersion.outdated; + const showNewVersionAvailable = !gitVersionCheckOverride && outdated; + + return { ...state, showNewVersionAvailable }; + } + + case types.RECEIVE_USER_CONFIG: { + const { userConfig } = config; + const bugtrackingNagscreenDismissed = localStorage.getItem('bugtrackingNagscreenDismissed'); + const showBugtrackingNagscreen = !userConfig.bugtracking && !bugtrackingNagscreenDismissed; + + return { ...state, showBugtrackingNagscreen }; + } + default: + const NPSSurveyLastDismissed = parseInt(localStorage.getItem('NPSSurveyLastDismissed') || '0', 10); + const monthsSinceNPSLastDismissed = (Date.now() - NPSSurveyLastDismissed) / (1000 * 60 * 60 * 24 * 30); + const showNPSSurvey = monthsSinceNPSLastDismissed >= 6 && Math.random() < 0.01; + return { ...state, showNPSSurvey }; + } +} + +export default app; \ No newline at end of file diff --git a/src-react/reducers/index.js b/src-react/reducers/index.js index 49e9d38..2659960 100644 --- a/src-react/reducers/index.js +++ b/src-react/reducers/index.js @@ -1,11 +1,14 @@ -import { combineReducers } from 'redux'; - -import path from './path'; import config from './config'; +import app from './app'; +import path from './path'; -const ungitApp = combineReducers({ - config, - path -}); +function ungitApp(state, action) { + const _config = config(state.config, action); + return { + config: _config, + app: app(state.app, action, _config), + path: path(state.path, action, _config), + }; +} export default ungitApp; \ No newline at end of file diff --git a/src-react/store.js b/src-react/store.js index a51ac47..88a8839 100644 --- a/src-react/store.js +++ b/src-react/store.js @@ -11,6 +11,12 @@ const initialState = { latestVersion: null } }, + app: { + gitVersionErrorVisible: false, + showNewVersionAvailable: false, + showBugtrackingNagscreen: false, + showNPSSurvey: false + }, path: { pending: null, errMessage: [] From 689e5b0e0d1e1a6db9d410505ad7f4777798b507 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 11 Jun 2017 21:03:39 +0800 Subject: [PATCH 13/14] typo --- src-react/actions/ungit-config.js | 4 ++-- src-react/containers/path.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src-react/actions/ungit-config.js b/src-react/actions/ungit-config.js index 73c8318..60cab78 100644 --- a/src-react/actions/ungit-config.js +++ b/src-react/actions/ungit-config.js @@ -12,7 +12,7 @@ export function fetchUngitConfig() { if (!json.config.bugtracking) { dispatch(fetchUserConfig()); } - dispatch(receiveUgitConfig(json)); + dispatch(receiveUngitConfig(json)); }) .catch(e => { dispatch(apiError(e.message)); @@ -20,7 +20,7 @@ export function fetchUngitConfig() { }; }; -function receiveUgitConfig(ungitConfig) { +function receiveUngitConfig(ungitConfig) { return { type: types.RECEIVE_UNGIT_CONFIG, payload: ungitConfig diff --git a/src-react/containers/path.js b/src-react/containers/path.js index e556d5a..2585882 100644 --- a/src-react/containers/path.js +++ b/src-react/containers/path.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux' -import AlerArea from 'components/alert-area'; +import AlertArea from 'components/alert-area'; import * as bootstrapActionCreators from 'actions/bootstrap'; import 'styles/styles.scss'; @@ -28,7 +28,7 @@ class Path extends Component {
{ pending === 0 && errMessage.length === 0 ? ( - Date: Sun, 11 Jun 2017 21:07:18 +0800 Subject: [PATCH 14/14] move one pending call into ungitconfig due to there's some condition --- src-react/actions/bootstrap.js | 2 +- src-react/actions/ungit-config.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src-react/actions/bootstrap.js b/src-react/actions/bootstrap.js index f5a4676..582d442 100644 --- a/src-react/actions/bootstrap.js +++ b/src-react/actions/bootstrap.js @@ -4,7 +4,7 @@ import { pending } from './common'; export function bootstrap() { return dispatch => { - dispatch(pending(4)); + dispatch(pending(3)); dispatch(fetchUngitConfig()); dispatch(fetchLatestVersion()); dispatch(fetchGitVersion()); diff --git a/src-react/actions/ungit-config.js b/src-react/actions/ungit-config.js index 60cab78..de55431 100644 --- a/src-react/actions/ungit-config.js +++ b/src-react/actions/ungit-config.js @@ -1,6 +1,6 @@ import * as types from 'constants/action-types'; import { fetchUserConfig } from './user-config'; -import { apiError } from './common'; +import { apiError, pending } from './common'; export function fetchUngitConfig() { return dispatch => { @@ -10,6 +10,7 @@ export function fetchUngitConfig() { .then(response => response.json()) .then(json => { if (!json.config.bugtracking) { + dispatch(pending()); dispatch(fetchUserConfig()); } dispatch(receiveUngitConfig(json));