diff --git a/app/components/Header/header.component.js b/app/components/Header/header.component.js index 8db5c6d..a3f678a 100644 --- a/app/components/Header/header.component.js +++ b/app/components/Header/header.component.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; import IconAdd from 'material-ui/svg-icons/content/add'; import styles from './header.component.scss'; import DomoLogo from '../../../resources/icons/domo-logo.png'; @@ -9,7 +10,7 @@ export default class Header extends Component { return (
- Domo + Domo Electron
diff --git a/app/containers/App/app.container.js b/app/containers/App/app.container.js index 1d2bab5..d5ecb1d 100644 --- a/app/containers/App/app.container.js +++ b/app/containers/App/app.container.js @@ -12,9 +12,18 @@ class App extends Component { } render() { + const listItems = this.props.logins.map(login => +
  • {login}
  • + ); + const content = this.props.isLoading ?

    Loading...

    - :
    {this.props.children}
    ; + : ( +
    + + {this.props.children} +
    + ); return (
    @@ -28,6 +37,7 @@ class App extends Component { App.propTypes = { children: PropTypes.node.isRequired, isLoading: PropTypes.bool.isRequired, + logins: PropTypes.arrayOf(PropTypes.string).isRequired, dispatch: PropTypes.func.isRequired }; diff --git a/app/containers/Data/data.container.js b/app/containers/Data/data.container.js new file mode 100644 index 0000000..59a10b7 --- /dev/null +++ b/app/containers/Data/data.container.js @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import styles from './data.container.scss'; + +import { getDatasetList } from '../../store/actions/data.actions'; + +class Data extends Component { + componentDidMount() { + const { dispatch } = this.props; + dispatch(getDatasetList('name', 50, 0)); + } + + render() { + return ( +
    +

    Datacenter

    + { + this.props.isLoading + ? 'Loading...' + :
      { this.props.datasets.map(ds =>
    • {ds.name}
    • )}
    + } +
    + ); + } +} + +Data.propTypes = { + isLoading: PropTypes.bool.isRequired, + datasets: PropTypes.arrayOf(PropTypes.shape).isRequired, + dispatch: PropTypes.func.isRequired, +}; + +const mapStateToProps = (state) => { + const { data } = state; + + return data; +}; + +export default connect(mapStateToProps)(Data); diff --git a/app/containers/Data/data.container.scss b/app/containers/Data/data.container.scss new file mode 100644 index 0000000..978a02a --- /dev/null +++ b/app/containers/Data/data.container.scss @@ -0,0 +1,14 @@ +.container { + position: absolute; + top: 30%; + left: 10px; + text-align: center; +} + +.container h2 { + font-size: 5rem; +} + +.container a { + font-size: 1.4rem; +} diff --git a/app/containers/Data/data.container.spec.js b/app/containers/Data/data.container.spec.js new file mode 100644 index 0000000..bafe15f --- /dev/null +++ b/app/containers/Data/data.container.spec.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { expect } from 'chai'; +import { shallow } from 'enzyme'; +import Container from './home.container'; + + +function setup() { + const component = shallow(); + return { + component + }; +} + +describe('(Container): Home', () => { + it('should render', () => { + const { component } = setup(); + expect(component.first().type()).to.equal('h1'); + }); +}); diff --git a/app/containers/Data/index.js b/app/containers/Data/index.js new file mode 100644 index 0000000..a85d146 --- /dev/null +++ b/app/containers/Data/index.js @@ -0,0 +1,3 @@ +import Data from './data.container'; + +export default Data; diff --git a/app/containers/Home/home.container.js b/app/containers/Home/home.container.js index 9752034..e33ce50 100644 --- a/app/containers/Home/home.container.js +++ b/app/containers/Home/home.container.js @@ -1,9 +1,16 @@ import React, { Component } from 'react'; +import { NavLink } from 'react-router-dom'; +import styles from './home.container.scss'; export default class Home extends Component { render() { return ( -

    Hello World

    +
    +

    Hello World

    +
      +
    • Datasets
    • +
    +
    ); } } diff --git a/app/main.dev.js b/app/main.dev.js index a37c968..9bec8c5 100644 --- a/app/main.dev.js +++ b/app/main.dev.js @@ -72,4 +72,5 @@ app.on('ready', async () => { }); // Register IPC Event Listeners -ipcMain.on('get-logins', Events.getLogins); +ipcMain.on('LOGINS:LIST', Events.getLoginHistory); +ipcMain.on('DATASETS:LIST', Events.listDatasets); diff --git a/app/routes.js b/app/routes.js index bc28b8b..257bdee 100644 --- a/app/routes.js +++ b/app/routes.js @@ -3,10 +3,12 @@ import { Switch, Route } from 'react-router'; import AppContainer from './containers/App'; import HomeContainer from './containers/Home'; +import DataContainer from './containers/Data'; export default () => ( + diff --git a/app/store/actions/data.actions.js b/app/store/actions/data.actions.js new file mode 100644 index 0000000..b22ba78 --- /dev/null +++ b/app/store/actions/data.actions.js @@ -0,0 +1,33 @@ +import { ipcRenderer } from 'electron'; + +export const actions = { + DATASET_LIST_REQUESTED: 'DATASETS:LIST', + DATASET_LIST_RETRIEVED: 'DATASETS:LIST:SUCCESS', + DATASET_LIST_FAILED: 'DATASETS:LIST:FAILED' +}; + +export function listRequested() { + return { type: actions.DATASET_LIST_REQUESTED }; +} + +export function listRetrieved(res) { + return { + type: actions.DATASET_LIST_RETRIEVED, + datasets: res.data ? res.data : res + }; +} + +export function listFailed(error) { + return { + type: actions.DATASET_LIST_FAILED, + error, + }; +} + +export const getDatasetList = (sort, limit, offset) => (dispatch) => { + dispatch(listRequested()); + + ipcRenderer.send(actions.DATASET_LIST_REQUESTED, { sort, limit, offset }); + ipcRenderer.on(actions.DATASET_LIST_RETRIEVED, (event, args) => dispatch(listRetrieved(args))); + ipcRenderer.on(actions.DATASET_LIST_FAILED, (event, err) => dispatch(listFailed(err))); +}; diff --git a/app/store/actions/login.actions.js b/app/store/actions/login.actions.js index 088a4e0..524932e 100644 --- a/app/store/actions/login.actions.js +++ b/app/store/actions/login.actions.js @@ -1,11 +1,9 @@ -import { IOService } from '../services'; - -const io = new IOService(); +import { ipcRenderer } from 'electron'; export const actions = { - LOGIN_REQUESTED: 'LOGIN_REQUESTED', - LOGIN_RETRIEVED: 'LOGIN_RETRIEVED', - LOGIN_FAILED: 'LOGIN_FAILED' + LOGIN_REQUESTED: 'LOGINS:LIST', + LOGIN_RETRIEVED: 'LOGINS:LIST:SUCCESS', + LOGIN_FAILED: 'LOGINS:LIST:FAILED' }; export function loginsRequested() { @@ -29,7 +27,7 @@ export function loginsFailed(error) { export const getLogins = () => (dispatch) => { dispatch(loginsRequested()); - return io.getLogins() - .then(res => dispatch(loginsRetrieved(res))) - .catch(err => dispatch(loginsFailed(err))); + ipcRenderer.send(actions.LOGIN_REQUESTED); + ipcRenderer.on(actions.LOGIN_RETRIEVED, (event, args) => dispatch(loginsRetrieved(args))); + ipcRenderer.on(actions.LOGIN_FAILED, (event, args) => dispatch(loginsFailed(args))); }; diff --git a/app/store/reducers/data/data.reducer.js b/app/store/reducers/data/data.reducer.js new file mode 100644 index 0000000..bdcaa42 --- /dev/null +++ b/app/store/reducers/data/data.reducer.js @@ -0,0 +1,41 @@ +import { actions } from '../../actions/data.actions'; + +const { DATASET_LIST_REQUESTED, DATASET_LIST_RETRIEVED, DATASET_LIST_FAILED } = actions; + +function handleListFailed(state, action) { + return { + ...state, + isLoading: false, + error: action.error, + }; +} + +function handleListRequested(state) { + return { + ...state, + isLoading: true, + }; +} + +function handleListRetrieved(state, action) { + return { + ...state, + isLoading: false, + datasets: action.datasets, + }; +} + +const actionHandlers = new Map([ + [DATASET_LIST_REQUESTED, handleListRequested], + [DATASET_LIST_RETRIEVED, handleListRetrieved], + [DATASET_LIST_FAILED, handleListFailed], +]); + +export const INITIAL_STATE = { + datasets: [], + isLoading: false, +}; + +export function dataReducer(state = INITIAL_STATE, action) { + return (actionHandlers.has(action.type) ? actionHandlers.get(action.type)(state, action) : state); +} diff --git a/app/store/services/io/io.service.spec.js b/app/store/reducers/data/data.reducer.spec.js similarity index 100% rename from app/store/services/io/io.service.spec.js rename to app/store/reducers/data/data.reducer.spec.js diff --git a/app/store/reducers/index.js b/app/store/reducers/index.js index b73a9f5..9feb634 100644 --- a/app/store/reducers/index.js +++ b/app/store/reducers/index.js @@ -1,10 +1,13 @@ import { combineReducers } from 'redux'; import { loginReducer, INITIAL_STATE as loginState } from './login/login.reducer'; +import { dataReducer, INITIAL_STATE as dataState } from './data/data.reducer'; export default combineReducers({ login: loginReducer, + data: dataReducer, }); export const INITIAL_STATE = { ...loginState, + ...dataState, }; diff --git a/app/store/services/index.js b/app/store/services/index.js index 16269de..ff8b4c5 100644 --- a/app/store/services/index.js +++ b/app/store/services/index.js @@ -1,5 +1 @@ -import IOService from './io/io.service'; - -export default { - IOService, -}; +export default {}; diff --git a/app/store/services/io/io.service.js b/app/store/services/io/io.service.js deleted file mode 100644 index 8b4f53d..0000000 --- a/app/store/services/io/io.service.js +++ /dev/null @@ -1,8 +0,0 @@ -import { ipcRenderer } from 'electron'; - -export default class IOService { - static getLogins() { - const logins = ipcRenderer.sendSync('get-logins', 'now'); - return Promise.resolve(logins); - } -} diff --git a/app/utils/events.js b/app/utils/events.js index 34ac094..7e47580 100644 --- a/app/utils/events.js +++ b/app/utils/events.js @@ -1,11 +1,59 @@ /* eslint no-param-reassign: ["error", { "props": false }] */ +const Domo = require('domo-sdk'); -module.exports = { - getLogins: (event) => { - event.returnValue = [ - { type: 'basic', user: 'alex.nelson@domo.com', instance: 'anthem.domo.com', sid: '', token: '' }, - { type: 'basic', user: 'alex.nelson@domo.com', instance: 'anthem.domo.com', sid: '', token: '' }, - { type: 'oauth', token: '', secret: '' } - ]; +const logins = [ + { type: 'basic', user: 'alex.nelson@domo.com', instance: 'anthem', sid: '', token: '' }, + { type: 'basic', user: 'alex.nelson@domo.com', instance: 'domo', sid: '', token: '' }, + { type: 'oauth', token: '', secret: '', instance: 'domo-sentinel.beta' } +]; + + +const service = { + getLogins: () => logins, + + getActiveSession: () => new Domo(process.env.DOMO_CLIENT_ID, process.env.DOMO_CLIENT_SECRET, 'api.domo.com'), + + storeLogin: (login) => { + // todo + logins.push(login); + }, + + getLoginHistory: (event) => { + try { + const history = service.getLogins().map(i => i.instance); + event.sender.send('LOGINS:LIST:SUCCESS', history); + } catch (e) { + event.sender.send('LOGINS:LIST:FAILED', e); + } + }, + + loginWithAPI: ({ instance, token, secret }) => { + service.storeLogin({ type: 'oauth', instance, token, secret }); + service.setActiveSession(instance); + }, + + loginWithUsername: ({ instance, username, password }) => { + // todo + service.storeLogin({ type: 'basic', instance, username, sid: '' }); + service.setActiveSession(instance); + }, + + loginWithDevToken: ({ instance, token }) => { + // todo + service.storeLogin({ type: 'basic', instance, token }); + service.setActiveSession(instance); + }, + + listDatasets: (event, { sort, limit, offset }) => { + service.getActiveSession().datasets + .list(sort, limit, offset) + .then(res => { + console.log('ListDataset', res); + event.sender.send('DATASETS:LIST:SUCCESS', res); + return res; + }) + .catch(err => event.sender.send('DATASETS:LIST:FAILED', err)); } }; + +module.exports = service;