Skip to content

Commit

Permalink
feat: #238 Update analytics page in dev portal (#256)
Browse files Browse the repository at this point in the history
* fix: Fix developer traffic chart loading issues

* fix: Remove redundant codes

* fix: Update testing snapshots

* fix: Revert admin stats test snapshot

* feat: #238 - Fix ui issues in analytics page

* feat: #238 - Add total traffic count label

* feat: #238 - Update test - Fix general UI issues

* feat: #238 - Update analytics page test

* feat: #238 - Add test for anonymous funcs - Update hooks lint rules

* feat: #238 - Temporarily disabled rules of hooks lint
  • Loading branch information
nphivu414 authored Feb 13, 2020
1 parent 64fb284 commit 650f045
Show file tree
Hide file tree
Showing 13 changed files with 671 additions and 526 deletions.
9 changes: 6 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'prettier'],
plugins: ['react', '@typescript-eslint', 'prettier', 'react-hooks'],
ignorePatterns: [
'__mocks__/',
'node_modules/',
Expand All @@ -39,12 +39,13 @@ module.exports = {
'platform-schema.ts',
],
rules: {
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }],
semi: ['error', 'never'],
'no-unused-vars': ['error', { vars: 'all', args: 'after-used' }],
'@typescript-eslint/no-unused-vars': [2, { args: 'none' }],
'prettier/prettier': 'error',
'prettier/prettier': ['error', {
'endOfLine': 'auto'
}],
'max-len': ['error', { code: 120, ignoreUrls: true }],
'no-confusing-arrow': ['error', { allowParens: false }],
'no-mixed-operators': [
Expand All @@ -64,6 +65,8 @@ module.exports = {
indent: 0,
// Disabling as we are validating types with TypeScript not PropTypes
'react/prop-types': 0,
"react-hooks/rules-of-hooks": 0,
"react-hooks/exhaustive-deps": 0,
},
settings: {
react: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^2.3.0",
"favicons-webpack-plugin": "^2.1.0",
"file-loader": "^3.0.1",
"fork-ts-checker-notifier-webpack-plugin": "^1.0.0",
Expand Down

Large diffs are not rendered by default.

47 changes: 45 additions & 2 deletions packages/marketplace/src/components/pages/__tests__/analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react'
import dayjs from 'dayjs'
import { shallow } from 'enzyme'
import {
AnalyticsPage,
Expand All @@ -11,9 +12,12 @@ import {
sortAppByDateInstalled,
countAppHasInstallation,
countAppNoInstallation,
handleFetchAppUsageStatsDataUseCallback,
handleFetchAppUsageStatsDataUseEffect,
mapStateToProps,
mapDispatchToProps,
} from '../analytics'
import { installationsStub } from '@/sagas/__stubs__/installations'
import { mapStateToProps } from '@/components/pages/analytics'
import { appsDataStub } from '@/sagas/__stubs__/apps'
import { ReduxState } from '@/types/core'
import { DeveloperState } from '@/reducers/developer'
Expand Down Expand Up @@ -43,10 +47,19 @@ const appUsageStats: AppUsageStatsState = {
appUsageStatsData: {},
}

const loadStats = jest.fn()

describe('AnalyticsPage', () => {
it('should match snapshot', () => {
expect(
shallow(<AnalyticsPage installations={installations} developer={developer} appUsageStats={appUsageStats} />),
shallow(
<AnalyticsPage
installations={installations}
developer={developer}
appUsageStats={appUsageStats}
loadStats={loadStats}
/>,
),
).toMatchSnapshot()
})

Expand All @@ -59,6 +72,7 @@ describe('AnalyticsPage', () => {
installations={installationsLoading}
developer={developerLoading}
appUsageStats={appUsageStats}
loadStats={loadStats}
/>,
),
).toMatchSnapshot()
Expand All @@ -75,6 +89,35 @@ describe('mapStateToProps', () => {
})
})

describe('mapDispatchToProps', () => {
it('should render correctly', () => {
const mockDispatch = jest.fn()
const { loadStats } = mapDispatchToProps(mockDispatch)
loadStats({
dateFrom: dayjs().toISOString(),
})
expect(mockDispatch).toBeCalled()
})
})

describe('handleFetchAppUsageStatsDataUseCallback', () => {
it('should run correctly', () => {
const installationAppData = installationsStub.data || []
const fn = handleFetchAppUsageStatsDataUseCallback(installationAppData, loadStats)
fn()
expect(loadStats).toBeCalled()
})
})

describe('handleFetchAppUsageStatsDataUseEffect', () => {
it('should run correctly', () => {
const mockFunction = jest.fn()
const fn = handleFetchAppUsageStatsDataUseEffect(mockFunction)
fn()
expect(mockFunction).toBeCalled()
})
})

describe('InstallationTable', () => {
const installedApps = handleMapAppNameToInstallation(
installations.installationsAppData?.data || [],
Expand Down
124 changes: 81 additions & 43 deletions packages/marketplace/src/components/pages/analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import * as React from 'react'
import ErrorBoundary from '@/components/hocs/error-boundary'
import { Table, FlexContainerBasic, H3, H4, Loader, toLocalTime, Pagination, Grid, GridItem } from '@reapit/elements'
import DeveloperInstallationsChart from '@/components/ui/developer-installations-chart'
import DeveloperTrafficChart from '@/components/ui/developer-traffic-chart'
import {
Table,
FlexContainerBasic,
H3,
H4,
Loader,
toLocalTime,
Pagination,
Grid,
GridItem,
FlexContainerResponsive,
} from '@reapit/elements'
import orderBy from 'lodash.orderby'
import { Dispatch } from 'redux'
import { connect } from 'react-redux'
import { ReduxState } from '@/types/core'
import { AppUsageStatsState } from '@/reducers/app-usage-stats'
import { appUsageStatsRequestData, AppUsageStatsParams } from '@/actions/app-usage-stats'
import { InstallationModel, AppSummaryModel } from '@reapit/foundations-ts-definitions'
import { DeveloperState } from '@/reducers/developer'
import { AppInstallationsState } from '@/reducers/app-installations'
import { INSTALLATIONS_PER_PAGE } from '@/constants/paginator'
import { withRouter } from 'react-router'
import styles from '@/styles/pages/analytics.scss?mod'
import DeveloperInstallationsChart from '@/components/ui/developer-installations-chart'
import DeveloperTrafficChart from '@/components/ui/developer-traffic-chart'
import DeveloperTrafficTable from '../ui/developer-traffic-table'
import { AppUsageStatsState } from '@/reducers/app-usage-stats'

export const installationTableColumn = [
{ Header: 'App Name', accessor: 'appName' },
Expand All @@ -39,14 +53,14 @@ export interface AnalyticsPageMappedProps {
}

export interface AnalyticsPageMappedActions {
requestAppDetailData: (data) => void
loadStats: (params: AppUsageStatsParams) => void
}

export interface InstallationModelWithAppName extends InstallationModel {
appName?: string
}

export type AnalyticsPageProps = AnalyticsPageMappedProps
export type AnalyticsPageProps = AnalyticsPageMappedProps & AnalyticsPageMappedActions

export const handleMapAppNameToInstallation = (
installationsAppDataArray: InstallationModel[],
Expand Down Expand Up @@ -143,32 +157,29 @@ export const InstallationTable: React.FC<{
}> = ({ installedApps, installations, developer, loading }) => {
const [pageNumber, setPageNumber] = React.useState<number>(1)

const installationAppDataArray = installations.installationsAppData?.data ?? []
const developerDataArray = developer.developerData?.data?.data ?? []

const appCountEntries = React.useMemo(handleCountCurrentInstallationForEachApp(installedApps, developerDataArray), [
installedApps,
developerDataArray,
])
const memoizedData = React.useMemo(handleUseMemoData(installedApps, pageNumber), [
installationAppDataArray,
pageNumber,
])
const memoizedData = React.useMemo(handleUseMemoData(installedApps, pageNumber), [installedApps, pageNumber])

return (
<div>
{loading ? (
<Loader />
) : (
<>
<H4>Installations</H4>
<p>
<p className="is-italic">
The installations table below shows the individual installations per client with a total number of
installations per app
</p>
<div className={styles.totalCount}>
{Object.entries(appCountEntries).map(([appName, count]) => (
<p key={appName}>
Total current installation for <strong>{appName}</strong>: {count}
Total current installations for <strong>{appName}</strong>: {count}
</p>
))}
</div>
Expand All @@ -186,18 +197,28 @@ export const InstallationTable: React.FC<{
)
}

export const AnalyticsPage: React.FC<AnalyticsPageProps> = ({ installations, developer, appUsageStats }) => {
// if (
// installations.loading ||
// !installations.installationsAppData ||
// developer.loading ||
// !developer.developerData ||
// appUsageStats.loading ||
// !appUsageStats.appUsageStatsData
// ) {
// return <Loader />
// }
export const handleFetchAppUsageStatsDataUseCallback = (
installationAppDataArray: InstallationModel[],
loadStats: (params: AppUsageStatsParams) => void,
) => {
return () => {
const orderedInstallationsByDate: InstallationModel[] = orderBy(installationAppDataArray, ['created'], ['asc'])
const firstInstallationDate = orderedInstallationsByDate[0]
if (firstInstallationDate) {
loadStats({
dateFrom: firstInstallationDate.created,
})
}
}
}

export const handleFetchAppUsageStatsDataUseEffect = (fetchAppUsageStatsData: () => void) => {
return () => {
fetchAppUsageStatsData()
}
}

export const AnalyticsPage: React.FC<AnalyticsPageProps> = ({ installations, developer, appUsageStats, loadStats }) => {
const installationAppDataArray = installations.installationsAppData?.data ?? []
const developerDataArray = developer.developerData?.data?.data ?? []

Expand All @@ -206,31 +227,44 @@ export const AnalyticsPage: React.FC<AnalyticsPageProps> = ({ installations, dev
[installationAppDataArray, developerDataArray],
)

const fetchAppUsageStatsData = React.useCallback(
handleFetchAppUsageStatsDataUseCallback(installationAppDataArray, loadStats),
[installationAppDataArray, loadStats],
)

React.useEffect(handleFetchAppUsageStatsDataUseEffect(fetchAppUsageStatsData), [fetchAppUsageStatsData])

const appUsageStatsLoading = appUsageStats.loading
const appUsageStatsData = appUsageStats.appUsageStatsData || {}
const developerAppsData = developer?.developerData?.data || {}
const installationsAppLoading = installations.loading

return (
<ErrorBoundary>
<FlexContainerBasic hasPadding flexColumn className={styles.wrapAnalytics}>
<H3>Dashboard</H3>
<hr className={styles.hr} />
<Grid isMultiLine>
<GridItem>
<DeveloperInstallationsChart data={installationAppDataArrayWithName} loading={installationsAppLoading} />
</GridItem>
<GridItem>
<DeveloperTrafficChart stats={appUsageStatsData} apps={developerAppsData} loading={appUsageStatsLoading} />
</GridItem>
</Grid>
<DeveloperTrafficTable stats={appUsageStatsData} apps={developerAppsData} loading={appUsageStatsLoading} />
<InstallationTable
installedApps={installationAppDataArrayWithName}
installations={installations}
developer={developer}
loading={installationsAppLoading}
/>
<FlexContainerBasic hasPadding flexColumn>
<FlexContainerResponsive flexColumn hasBackground hasPadding className={styles.wrapAnalytics}>
<H3>Dashboard</H3>
<hr className={styles.hr} />
<Grid isMultiLine>
<GridItem>
<DeveloperInstallationsChart data={installationAppDataArrayWithName} loading={installationsAppLoading} />
</GridItem>
<GridItem>
<DeveloperTrafficChart
stats={appUsageStatsData}
apps={developerAppsData}
loading={appUsageStatsLoading}
/>
</GridItem>
</Grid>
<DeveloperTrafficTable stats={appUsageStatsData} apps={developerAppsData} loading={appUsageStatsLoading} />
<InstallationTable
installedApps={installationAppDataArrayWithName}
installations={installations}
developer={developer}
loading={installationsAppLoading}
/>
</FlexContainerResponsive>
</FlexContainerBasic>
</ErrorBoundary>
)
Expand All @@ -242,4 +276,8 @@ export const mapStateToProps: (state: ReduxState) => AnalyticsPageMappedProps =
appUsageStats: state.appUsageStats,
})

export default withRouter(connect(mapStateToProps)(AnalyticsPage))
export const mapDispatchToProps = (dispatch: Dispatch): AnalyticsPageMappedActions => ({
loadStats: (params: AppUsageStatsParams) => dispatch(appUsageStatsRequestData(params)),
})

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AnalyticsPage))
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ exports[`DeveloperTrafficTable should match a snapshot 1`] = `
<Component>
Traffic
</Component>
<p>
<p
className="is-italic"
>
The traffic table below shows all API calls made against each of your applications since the date your app was created
</p>
<Component
Expand Down Expand Up @@ -43,5 +45,11 @@ exports[`DeveloperTrafficTable should match a snapshot 1`] = `
loading={false}
scrollable={true}
/>
<Component
className="undefined is-pulled-right"
>
Total API Calls:
5
</Component>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DeveloperTrafficTable, {
DeveloperAppTrafficProps,
generateUsageStatsData,
AppUsageStats,
calculateTotalRequest,
} from '../developer-traffic-table'
import { appsDataStub } from '@/sagas/__stubs__/apps'
import { usageStatsDataStub } from '@/sagas/__stubs__/app-usage-stats'
Expand Down Expand Up @@ -41,4 +42,16 @@ describe('DeveloperTrafficTable', () => {
expect(result).toEqual(undefined)
})
})

describe('calculate total api request', () => {
it('should run correctly', () => {
const props = {
apps: appsDataStub.data,
stats: usageStatsDataStub,
}
const usageStatsData = generateUsageStatsData(props)()
const result = calculateTotalRequest(usageStatsData)
expect(result).toEqual(5)
})
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { H4, Alert, Loader } from '@reapit/elements'
import { H4, Loader } from '@reapit/elements'
import { Line } from 'react-chartjs-2'
import { UsageStatsModel, PagedResultAppSummaryModel_ } from '@reapit/foundations-ts-definitions'
import { getAppUsageStatsChartData, getChartConfig, getChartOptions } from '@/utils/app-usage-stats.ts'
Expand All @@ -15,10 +15,6 @@ export const DeveloperTrafficChart: React.FC<DeveloperTrafficChartProps> = ({ st
const appUsageStatsChartData = getAppUsageStatsChartData(appUsage, apps.data)

function renderChart() {
if (!appUsageStatsChartData) {
return <Alert message="You currently have no apps usage stats " type="info" />
}

const { labels, data, appUsageStatsGroupedByDate } = appUsageStatsChartData
const chartData = getChartConfig(labels, data)
const chartOptions = getChartOptions(appUsageStatsGroupedByDate)
Expand Down
Loading

0 comments on commit 650f045

Please sign in to comment.