diff --git a/src/actions/dashboard.js b/src/actions/dashboard.js new file mode 100644 index 0000000..fe18949 --- /dev/null +++ b/src/actions/dashboard.js @@ -0,0 +1,147 @@ +import {getAllUsers, retrieveAllAvailableFirmware, retrieveAllBounties} from "../blockchain/contracts"; +import {containsIgnoreCase} from "./search"; +import {getUserDevices} from "./profile"; +export const DASHBOARD_DATA = 'DASHBOARD_DATA'; + + +function updateDashboardData(payload) { + return { + type: DASHBOARD_DATA, + payload + }; +} + +// TODO must consider where to store, and how to calculate, last months values as compared to this ones for various fields + +/** + * High-level function for triggering the Dashboard data retrival. + * @param currentUserAddress + * @returns {function(*): Promise} + */ +export function retrieveDashboardData(currentUserAddress) { + return async (dispatch) => { + + let firmwareResults = await retrieveAllAvailableFirmware(); + let userFirmware = (firmwareResults).filter(fw => isUserFirmware(currentUserAddress, fw)); + // search users on user reputation blockchain for user inclusion + let userAddresses = await getAllUsers(); + // search bounties on the bounty blockchain for bounty inclusion + let bountyResults = await retrieveAllBounties(); + let userBounties = (bountyResults).filter(b => isUserBounty(currentUserAddress, b)); + + // getting user devices from local storage + let userDevices = await getUserDevices(); + + + // retrieve data values + let communityData = communityContributions(firmwareResults, userAddresses, bountyResults); + let bountiesData = bountiesClaimed (userBounties); + let firmwareData = firmwareStats (userFirmware) + let deviceData = deviceStats (userDevices) + + // trigger the reducer data update + dispatch(updateDashboardData({community: communityData, + bounties: bountiesData, + firmware: firmwareData, + devices: deviceData + })) + + } +} +/** + * + * @returns {function(*): Promise<{totalFirmware: number, totalBounties: number, userAmount: number}>} + */ +function communityContributions(firmwares, users, bounties) { + + // amount of users; total bounties; total firmware + return { + userAmount : users.length, + totalBounties : bounties.length, + totalFirmware : firmwares.length, + monthlyContributions : 0 + } +} + +/** + * + * @param userBounties + * @returns {{overallClaimed: number, amountSubmitted: number, monthlyClaims: number}} + */ +function bountiesClaimed(userBounties) { + + let claimedBounties = userBounties.filter(b => isBountyClaimed(true, b)); + let claimedPercent = 100 * (claimedBounties.length / userBounties.length); + // TODO check above works with rounding as a percentage + + // Amount Submitted; Overall Claimed; Monthly Claims + return { + amountSubmitted : userBounties.length, + overallClaimed : claimedPercent, + monthlyClaims : 0 + } +} + +/** + * + * @param userFirmwares + * @returns {{firmwareAmount: number, overallDownloads: number, monthlyDownloads: number}} + */ +function firmwareStats(userFirmwares) { + + // TODO replace this firmware downloads value (consider using FirmwareWithThumbs) -- look for Michael + let firmwareDownloads = 0; + + // Amount Submitted; Overall Claimed; Monthly Claims + return { + firmwareAmount : userFirmwares.length, + overallDownloads : firmwareDownloads, + monthlyDownloads : 0 + } +} + +/** + * Retrieves the device statistics for display in the dashboard. + * @param devices - list of {Device} objects + * @returns {{deviceDetails: {onMap: number, totalDevices: number}, inactive: {number: number, percent: number}, active: {number: number, percent: number}, unknown: {number: number, percent: number}}} + */ +function deviceStats(devices) { + // TODO retrieve the values around the device status for each + // (likely have to get Michael to provide class (and method to produce) to wrap the device in status + location + let numberOfDevices = devices.length; + + let activeDevices = 12; //: TODO Michael + let inactiveDevices = 14; //: TODO Michael + let numberOfDeviceOnMap = 15; //: TODO Michael + let unknownDevices = (numberOfDevices-activeDevices-inactiveDevices); + + let activeDevicesPercentage = 100 * (activeDevices / numberOfDevices) + let inactiveDevicesPercentage = 100 * (inactiveDevices / numberOfDevices) + let unknownDevicesPercentage = 100 * (unknownDevices / numberOfDevices) + + return { + active: {number: activeDevices , percent : activeDevicesPercentage}, + unknown: {number: unknownDevices , percent : unknownDevicesPercentage}, + inactive: {number: inactiveDevices , percent : inactiveDevicesPercentage}, + deviceDetails: {onMap : numberOfDeviceOnMap , totalDevices: numberOfDevices} + } +} + +function isUserBounty(term, bounty) { + return containsIgnoreCase(bounty.bountySetter, term); +} + +function isBountyClaimed(term, bounty) { + // TODO replace with some kind of claimed flag (see Michael for getting this setup) -- Michael!!!! :L + return containsIgnoreCase(bounty.block_num, term); +} + +/** + * Return true if the users firmware contains the developers name + * @param term + * @param firmware + * @returns {boolean} + */ +function isUserFirmware(term, firmware) { + return containsIgnoreCase(firmware.developer, term); +} diff --git a/src/actions/profile.js b/src/actions/profile.js index d0f8165..b71e488 100644 --- a/src/actions/profile.js +++ b/src/actions/profile.js @@ -61,6 +61,23 @@ export function initUserDevices() { } +/** + * Reads available user devices from the browser cache + */ +export function getUserDevices() { + return async (dispatch) => { + let devices = []; + if (localStorage[deviceLocalStorageKey]) { + try{ + devices = JSON.parse(localStorage['devices']); + } catch (e) { + devices = []; + } + } + return devices; + } +} + /** * Add to the device list. * TODO implement UI binding and functionality diff --git a/src/actions/search.js b/src/actions/search.js index 081359a..a9e9fb7 100644 --- a/src/actions/search.js +++ b/src/actions/search.js @@ -31,7 +31,7 @@ function searchFailure(payload) { }; } -function containsIgnoreCase(string, term) { +export function containsIgnoreCase(string, term) { if (string == null || term == null) { return false; } diff --git a/src/actions/user.js b/src/actions/user.js index c8a27f5..9a3002f 100644 --- a/src/actions/user.js +++ b/src/actions/user.js @@ -4,6 +4,7 @@ import Box from "3box"; import {setProfilePassword, setUserProfile} from "./profile"; import { setBounties, setFirmware } from './model'; import { getPG } from '../filecoin/client'; +import {retrieveDashboardData} from "./dashboard"; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; export const LOGIN_FAILURE = 'LOGIN_FAILURE'; @@ -120,6 +121,7 @@ export function enableUserEthereum() { let userPassword = await space.private.get('password'); dispatch(setProfilePassword({userPassword: userPassword})); dispatch(setUserProfile({userAddress: ethereumAddress})); + dispatch(retrieveDashboardData(ethereumAddress)) // Accounts now exposed dispatch(ethereumAuthSuccess({ diff --git a/src/pages/dashboard/Dashboard.js b/src/pages/dashboard/Dashboard.js index b088690..74f013d 100644 --- a/src/pages/dashboard/Dashboard.js +++ b/src/pages/dashboard/Dashboard.js @@ -3,6 +3,7 @@ import React from 'react'; import s from './Dashboard.module.scss'; import DeviceRow from "./components/DeviceRow"; import TableRow from "./components/TableRow"; +import {connect} from "react-redux"; class Dashboard extends React.Component { @@ -12,6 +13,14 @@ class Dashboard extends React.Component { } render() { + const { + devicesStats, + communityStats, + firmwareStats, + bountiesStats, + } = this.props; + + console.log('Dashboard', this.props) return (

Dashboard   @@ -19,11 +28,18 @@ class Dashboard extends React.Component { Hub Overview

- - + +
); } } -export default Dashboard; +const mapStateToProps = state => ({ + communityStats: state.dashboard.community, + bountiesStats: state.dashboard.bounties, + firmwareStats: state.dashboard.firmware, + devicesStats: state.dashboard.devices +}); + +export default connect(mapStateToProps)(Dashboard); diff --git a/src/pages/dashboard/components/DeviceComponents/StatusLines/StatusLines.js b/src/pages/dashboard/components/DeviceComponents/StatusLines/StatusLines.js index 994ca2b..3670c3c 100644 --- a/src/pages/dashboard/components/DeviceComponents/StatusLines/StatusLines.js +++ b/src/pages/dashboard/components/DeviceComponents/StatusLines/StatusLines.js @@ -16,38 +16,40 @@ class StatusLines extends React.Component { render() { + const deviceStats = this.props.devicesStats; + return (

Active

- +
- % + %

Unknown

- +
- % + %

Inactive

- +
- % + %
diff --git a/src/pages/dashboard/components/DeviceRow.js b/src/pages/dashboard/components/DeviceRow.js index 1eddfc1..0794c11 100644 --- a/src/pages/dashboard/components/DeviceRow.js +++ b/src/pages/dashboard/components/DeviceRow.js @@ -13,7 +13,14 @@ import StatusLines from "./DeviceComponents/StatusLines/StatusLines"; class DeviceRow extends React.Component { + constructor(props) { + super(props); + } + render() { + // TODO this object can then be used to replace the 0 values where appropriate + // - should have the same fields as the return value from the function deviceStats in src/actions/dashboard.js + const deviceStats = this.props.devicesStats; return ( @@ -38,12 +45,12 @@ class DeviceRow extends React.Component {
Amount of devices which are active, unknown or inactive on the map.
- +
Map Distribution

Tracking: Active

-   0 device added, 0 devices total +   {deviceStats.deviceDetails.onMap} device added, {deviceStats.deviceDetails.totalDevices} devices total

@@ -53,7 +60,6 @@ class DeviceRow extends React.Component {
-
diff --git a/src/pages/dashboard/components/TableRow.js b/src/pages/dashboard/components/TableRow.js index b2b1290..6256ee8 100644 --- a/src/pages/dashboard/components/TableRow.js +++ b/src/pages/dashboard/components/TableRow.js @@ -12,6 +12,13 @@ class TableRow extends React.Component { } render() { + + const { + communityStats, + firmwareStats, + bountiesStats, + } = this.props; + return ( @@ -26,15 +33,15 @@ class TableRow extends React.Component {
Amount of Users
-

0

+

{communityStats.userAmount}

Total Bounties
-

0

+

{communityStats.totalBounties}

Total Firmware
-

0

+

{communityStats.totalFirmware}

@@ -58,15 +65,15 @@ class TableRow extends React.Component {
Amount Submitted
-

0

+

{bountiesStats.amountSubmitted}

Overall Claimed
-

0%

+

{bountiesStats.overallClaimed}%

Monthly Claims
-

0

+

{bountiesStats.monthlyClaims}

@@ -93,15 +100,15 @@ class TableRow extends React.Component {
My Firmware
-

0

+

{firmwareStats.firmwareAmount}

Overall Downloads
-

0

+

{firmwareStats.overallDownloads}

Monthly Downloads
-

0

+

{firmwareStats.monthlyDownloads}

diff --git a/src/reducers/dashboard.js b/src/reducers/dashboard.js new file mode 100644 index 0000000..6e842d9 --- /dev/null +++ b/src/reducers/dashboard.js @@ -0,0 +1,38 @@ +import { DASHBOARD_DATA } from '../actions/dashboard'; + +export default function dashboard(state = { + community: { + userAmount: 0, + totalBounties: 0, + totalFirmware: 0, + monthlyContributions: 0 + }, + bounties: { + amountSubmitted: 0, + overallClaimed: 0, + monthlyClaims: 0 + }, + firmware: { + firmwareAmount: 0, + overallDownloads: 0, + monthlyDownloads: 0 + }, + devices: { + active: {number: 0, percent: 0}, + unknown: {number: 0, percent: 0}, + inactive: {number: 0, percent: 0}, + deviceDetails: {onMap: 0, totalDevices: 0} + } +}, action) { + switch (action.type) { + case DASHBOARD_DATA: + return Object.assign({}, state, { + community: action.payload.community, + bounties: action.payload.bounties, + firmware: action.payload.firmware, + devices: action.payload.devices, + }); + default: + return state; + } +} diff --git a/src/reducers/index.js b/src/reducers/index.js index ace8f6e..7fe053b 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,6 +1,7 @@ import { combineReducers } from 'redux'; import auth from './auth'; import alerts from './alerts'; +import dashboard from "./dashboard"; import ethereum from "./ethereum"; import navigation from './navigation'; import register from './register'; @@ -12,6 +13,7 @@ import views from './views' export default combineReducers({ alerts, auth, + dashboard, ethereum, navigation, register,