From 533632f4e57d39a9ae17ab798573516416f1fa3a Mon Sep 17 00:00:00 2001 From: Pham Hai Duong Date: Thu, 7 May 2020 22:51:32 +0700 Subject: [PATCH 1/7] feat: integrate api for billing bar chart (#1168) Changes - Integrate api for billing bar chart - Write test - Refactor some places --- .../src/actions/__tests__/developer.ts | 23 +++ packages/marketplace/src/actions/developer.ts | 13 +- .../pages/__tests__/developer-home.tsx | 16 +++ .../service-chart/__mocks__/billing.ts | 83 +++++++++++ .../__snapshots__/service-chart.test.tsx.snap | 133 ++++++++++------- .../__tests__/service-chart.test.tsx | 134 +++++++++++++++++- .../billing/service-chart/service-chart.tsx | 122 ++++++++++++---- .../detailed/detailed-tab.tsx | 4 +- .../marketplace/src/constants/action-types.ts | 13 ++ packages/marketplace/src/constants/routes.ts | 1 + packages/marketplace/src/core/store.ts | 97 +++++++------ .../src/reducers/__tests__/developer.ts | 39 +++++ .../marketplace/src/reducers/developer.ts | 69 ++++++++- .../src/sagas/__stubs__/billing.ts | 83 +++++++++++ .../src/sagas/__stubs__/developer-identity.ts | 33 +++++ .../src/sagas/__stubs__/developer.ts | 6 + .../src/sagas/__tests__/developer.ts | 111 +++++++++++++-- packages/marketplace/src/sagas/api.ts | 43 ++++++ packages/marketplace/src/sagas/developer.ts | 74 ++++++++-- .../src/selector/__tests__/developer.ts | 127 ++++++++++++++++- .../marketplace/src/selector/developer.ts | 26 +++- packages/marketplace/src/selector/index.ts | 1 + .../src/utils/__tests__/route-dispatcher.ts | 15 +- .../marketplace/src/utils/route-dispatcher.ts | 6 +- 24 files changed, 1104 insertions(+), 168 deletions(-) create mode 100644 packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__mocks__/billing.ts create mode 100644 packages/marketplace/src/sagas/__stubs__/billing.ts create mode 100644 packages/marketplace/src/sagas/__stubs__/developer-identity.ts create mode 100644 packages/marketplace/src/selector/index.ts diff --git a/packages/marketplace/src/actions/__tests__/developer.ts b/packages/marketplace/src/actions/__tests__/developer.ts index e3ae0d1806..3d79ce0c6c 100644 --- a/packages/marketplace/src/actions/__tests__/developer.ts +++ b/packages/marketplace/src/actions/__tests__/developer.ts @@ -5,11 +5,15 @@ import { developerClearData, developerCreate, developerSetFormState, + fetchBilling, + fetchBillingSuccess, + fetchBillingFailure, } from '../developer' import ActionTypes from '../../constants/action-types' import { appsDataStub } from '../../sagas/__stubs__/apps' import { CreateDeveloperModel } from '@reapit/foundations-ts-definitions' import { appPermissionStub } from '@/sagas/__stubs__/app-permission' +import { billing } from '@/sagas/__stubs__/billing' describe('developer actions', () => { it('should create a developerLoading action', () => { @@ -50,4 +54,23 @@ describe('developer actions', () => { expect(developerSetFormState.type).toEqual(ActionTypes.DEVELOPER_SET_FORM_STATE) expect(developerSetFormState('DONE').data).toEqual('DONE') }) + + it('should create a fetchBilling action', () => { + expect(fetchBilling.type).toEqual(ActionTypes.DEVELOPER_FETCH_BILLING) + expect(fetchBilling({ dateFrom: '2020-01', dateTo: '2020-05', applicationId: ['123'] }).data).toEqual({ + dateFrom: '2020-01', + dateTo: '2020-05', + applicationId: ['123'], + }) + }) + + it('should create a fetchBillingFailure action', () => { + expect(fetchBillingSuccess.type).toEqual(ActionTypes.DEVELOPER_FETCH_BILLING_SUCCESS) + expect(fetchBillingSuccess(billing).data).toEqual(billing) + }) + + it('should create a fetchBillingFailure action', () => { + expect(fetchBillingFailure.type).toEqual(ActionTypes.DEVELOPER_FETCH_BILLING_FAILURE) + expect(fetchBillingFailure('error').data).toEqual('error') + }) }) diff --git a/packages/marketplace/src/actions/developer.ts b/packages/marketplace/src/actions/developer.ts index 29457a1c4c..91f2870f5b 100644 --- a/packages/marketplace/src/actions/developer.ts +++ b/packages/marketplace/src/actions/developer.ts @@ -1,8 +1,10 @@ +// TODO: WILL MOVE ALL DEVELOPER ACTIONS TO HERE import { actionCreator } from '../utils/actions' import ActionTypes from '../constants/action-types' -import { DeveloperItem, DeveloperRequestParams } from '../reducers/developer' -import { CreateDeveloperModel } from '@reapit/foundations-ts-definitions' -import { FormState } from '../types/core' +import { DeveloperItem, DeveloperRequestParams, Billing } from '../reducers/developer' +import { CreateDeveloperModel, DeveloperModel } from '@reapit/foundations-ts-definitions' +import { FormState } from '@/types/core' +import { FetchBillingParams } from '@/sagas/api' export const developerRequestData = actionCreator(ActionTypes.DEVELOPER_REQUEST_DATA) export const developerRequestDataFailure = actionCreator(ActionTypes.DEVELOPER_REQUEST_DATA_FAILURE) @@ -11,3 +13,8 @@ export const developerReceiveData = actionCreator(Act export const developerClearData = actionCreator(ActionTypes.DEVELOPER_CLEAR_DATA) export const developerCreate = actionCreator(ActionTypes.DEVELOPER_CREATE) export const developerSetFormState = actionCreator(ActionTypes.DEVELOPER_SET_FORM_STATE) +export const fetchMyIdentity = actionCreator(ActionTypes.DEVELOPER_FETCH_MY_IDENTITY) +export const setMyIdentity = actionCreator(ActionTypes.DEVELOPER_SET_MY_IDENTITY) +export const fetchBilling = actionCreator(ActionTypes.DEVELOPER_FETCH_BILLING) +export const fetchBillingSuccess = actionCreator(ActionTypes.DEVELOPER_FETCH_BILLING_SUCCESS) +export const fetchBillingFailure = actionCreator(ActionTypes.DEVELOPER_FETCH_BILLING_FAILURE) diff --git a/packages/marketplace/src/components/pages/__tests__/developer-home.tsx b/packages/marketplace/src/components/pages/__tests__/developer-home.tsx index f5818c6bf6..6ea9d7a8d1 100644 --- a/packages/marketplace/src/components/pages/__tests__/developer-home.tsx +++ b/packages/marketplace/src/components/pages/__tests__/developer-home.tsx @@ -18,6 +18,14 @@ describe('DeveloperHome', () => { it('should match a snapshot', () => { const mockProps: DeveloperProps = { developerState: { + myIdentity: {}, + error: null, + billing: { + from: '', + to: '', + requestsByPeriod: [], + }, + isServiceChartLoading: false, loading: false, isVisible: false, developerData: { @@ -52,6 +60,14 @@ describe('DeveloperHome', () => { it('should match a snapshot', () => { const mockProps: DeveloperProps = { developerState: { + isServiceChartLoading: false, + myIdentity: {}, + error: null, + billing: { + from: '', + to: '', + requestsByPeriod: [], + }, loading: false, isVisible: true, developerData: { diff --git a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__mocks__/billing.ts b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__mocks__/billing.ts new file mode 100644 index 0000000000..e319cefd39 --- /dev/null +++ b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__mocks__/billing.ts @@ -0,0 +1,83 @@ +export const billing = { + from: '2019-11', + to: '2020-05', + requestsByPeriod: [ + { + period: '2019-11', + periodStart: '2019-11-01', + periodEnd: '2019-11-30', + periodName: 'November 2019', + requestCount: 0, + endpointCount: 0, + netAmount: 0.0, + grossAmount: 0.0, + vatAmount: 0.0, + }, + { + period: '2019-12', + periodStart: '2019-12-01', + periodEnd: '2019-12-31', + periodName: 'December 2019', + requestCount: 0, + endpointCount: 0, + netAmount: 0.0, + grossAmount: 0.0, + vatAmount: 0.0, + }, + { + period: '2020-01', + periodStart: '2020-01-01', + periodEnd: '2020-01-31', + periodName: 'January 2020', + requestCount: 0, + endpointCount: 0, + netAmount: 0.0, + grossAmount: 0.0, + vatAmount: 0.0, + }, + { + period: '2020-02', + periodStart: '2020-02-01', + periodEnd: '2020-02-29', + periodName: 'February 2020', + requestCount: 0, + endpointCount: 0, + netAmount: 0.0, + grossAmount: 0.0, + vatAmount: 0.0, + }, + { + period: '2020-03', + periodStart: '2020-03-01', + periodEnd: '2020-03-31', + periodName: 'March 2020', + requestCount: 0, + endpointCount: 0, + netAmount: 0.0, + grossAmount: 0.0, + vatAmount: 0.0, + }, + { + period: '2020-04', + periodStart: '2020-04-01', + periodEnd: '2020-04-30', + periodName: 'April 2020', + requestCount: 614, + endpointCount: 6, + netAmount: 8.1, + grossAmount: 6.75, + vatAmount: 1.35, + }, + { + period: '2020-05', + periodStart: '2020-05-01', + periodEnd: '2020-05-31', + periodName: 'May 2020', + requestCount: 0, + endpointCount: 0, + netAmount: 0.0, + grossAmount: 0.0, + vatAmount: 0.0, + }, + ], +} diff --git a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/__snapshots__/service-chart.test.tsx.snap b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/__snapshots__/service-chart.test.tsx.snap index 1e537c93bd..320f18a50c 100644 --- a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/__snapshots__/service-chart.test.tsx.snap +++ b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/__snapshots__/service-chart.test.tsx.snap @@ -1,56 +1,91 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ServiceChart should match a snapshot 1`] = ` - -
- - Services - - + -
-
+ } + width={50} + /> + +`; + +exports[`ServiceChart handleUseEffect should render Loader 1`] = ` +
+ +
+`; + +exports[`ServiceChart should match a snapshot 1`] = ` + + + `; diff --git a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/service-chart.test.tsx b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/service-chart.test.tsx index 97e369e03a..f20af62e17 100644 --- a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/service-chart.test.tsx +++ b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/__tests__/service-chart.test.tsx @@ -1,10 +1,140 @@ import * as React from 'react' +import * as ReactRedux from 'react-redux' +import { ReduxState } from '@/types/core' import { shallow } from 'enzyme' +import configureStore from 'redux-mock-store' +import { + ServiceChart, + handleUseEffect, + HandleUseEffectParams, + renderChart, + mapServiceChartDataSet, +} from '../service-chart' +import { developerState } from '@/sagas/__stubs__/developer' +import { fetchBilling } from '@/actions/developer' +import { ChartData } from 'react-chartjs-2' +import { billing } from '../__mocks__/billing' -import { ServiceChart } from '../service-chart' +const mockState = { + developer: developerState, +} as ReduxState describe('ServiceChart', () => { + let store + let spyDispatch + beforeEach(() => { + /* mocking store */ + const mockStore = configureStore() + store = mockStore(mockState) + /* mocking useDispatch on our mock store */ + spyDispatch = jest.spyOn(ReactRedux, 'useDispatch').mockImplementation(() => store.dispatch) + }) it('should match a snapshot', () => { - expect(shallow()).toMatchSnapshot() + expect( + shallow( + + + , + ), + ).toMatchSnapshot() + }) + + describe('handleFetchAppUsageStatsDataUseCallback', () => { + it('should call billing API correctly', () => { + const mockParams = { + myAppIds: ['123', '456'], + dateFrom: '2020-01', + dateTo: '2020-05', + dispatch: spyDispatch, + } as HandleUseEffectParams + const fn = handleUseEffect(mockParams) + fn() + expect(spyDispatch).toBeCalledWith( + fetchBilling({ applicationId: mockParams.myAppIds, dateFrom: mockParams.dateFrom, dateTo: mockParams.dateTo }), + ) + }) + + it('should not call fetch billing data', () => { + const mockParams = { + myAppIds: [], + dateFrom: '2020-01', + dateTo: '2020-05', + dispatch: spyDispatch, + } as HandleUseEffectParams + const fn = handleUseEffect(mockParams) + fn() + expect(spyDispatch).not.toBeCalledWith( + fetchBilling({ applicationId: mockParams.myAppIds, dateFrom: mockParams.dateFrom, dateTo: mockParams.dateTo }), + ) + }) + }) + + describe('handleUseEffect', () => { + it('should render Loader', () => { + const datasets = { + label: ['A', 'B', 'C'], + datasets: [ + { + label: 'Resource', + backgroundColor: 'rgba(255,99,132,0.2)', + borderColor: 'rgba(255,99,132,1)', + borderWidth: 1, + hoverBackgroundColor: 'rgba(255,99,132,0.4)', + hoverBorderColor: 'rgba(255,99,132,1)', + data: [1, 2, 3] as number[], + }, + ], + } as ChartData + const wrapper = shallow(
{renderChart(true, datasets)}
) + expect(wrapper).toMatchSnapshot() + }) + + it('should render Chart', () => { + const datasets = { + label: ['A', 'B', 'C'], + datasets: [ + { + label: 'Resource', + backgroundColor: 'rgba(255,99,132,0.2)', + borderColor: 'rgba(255,99,132,1)', + borderWidth: 1, + hoverBackgroundColor: 'rgba(255,99,132,0.4)', + hoverBorderColor: 'rgba(255,99,132,1)', + data: [1, 2, 3] as number[], + }, + ], + } as ChartData + const wrapper = shallow(
{renderChart(false, datasets)}
) + expect(wrapper).toMatchSnapshot() + }) + }) + + describe('mapServiceChartDataSet', () => { + it('should return correctly', () => { + const result = mapServiceChartDataSet(billing) + const expected = { + labels: [ + 'November 2019', + 'December 2019', + 'January 2020', + 'February 2020', + 'March 2020', + 'April 2020', + 'May 2020', + ], + datasets: [ + { + label: 'Resource', + backgroundColor: 'rgba(255,99,132,0.2)', + borderColor: 'rgba(255,99,132,1)', + borderWidth: 1, + hoverBackgroundColor: 'rgba(255,99,132,0.4)', + hoverBorderColor: 'rgba(255,99,132,1)', + data: [0, 0, 0, 0, 0, 6.75, 0] as number[], + }, + ], + } + expect(result).toEqual(expected) + }) }) }) diff --git a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/service-chart.tsx b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/service-chart.tsx index d607af3ec0..fb0e461988 100644 --- a/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/service-chart.tsx +++ b/packages/marketplace/src/components/ui/developer-analytics/billing/service-chart/service-chart.tsx @@ -1,12 +1,23 @@ import React from 'react' -import { Bar } from 'react-chartjs-2' -import { H4, FlexContainerResponsive } from '@reapit/elements' +import { Bar, ChartData } from 'react-chartjs-2' +import { H4, FlexContainerResponsive, DATE_TIME_FORMAT, Loader } from '@reapit/elements' +import { AppSummaryModel, DeveloperModel } from '@reapit/foundations-ts-definitions' import styles from '@/styles/pages/developer-analytics.scss?mod' +import { useDispatch, useSelector } from 'react-redux' +import { + selectDeveloperApps, + selectMyIdentity, + selectBilling, + selectDeveloperLoading, + selectIsServiceChartLoading, +} from '@/selector' +import { fetchBilling } from '@/actions/developer' +import dayjs from 'dayjs' +import { Billing, RequestByPeriod } from '@/reducers/developer' +import { Dispatch } from 'redux' -// TODO will replace mock data when have API -const data = { - labels: ['January', 'February', 'March', 'April'], - datasets: [ +export const mapServiceChartDataSet = (billing: Billing | null) => { + let datasets = [ { label: 'Resource', backgroundColor: 'rgba(255,99,132,0.2)', @@ -14,34 +25,93 @@ const data = { borderWidth: 1, hoverBackgroundColor: 'rgba(255,99,132,0.4)', hoverBorderColor: 'rgba(255,99,132,1)', - data: [10, 5, 10, 20], + data: [] as number[], }, - ], + ] + let labels: string[] = [] + if (!billing) { + return { + labels, + datasets, + } + } + billing.requestsByPeriod.forEach((item: RequestByPeriod) => { + labels.push(item.periodName) + datasets[0].data.push(item.grossAmount) + }) + return { + labels, + datasets, + } +} + +export type StateProps = { + myApps: AppSummaryModel[] + myIdentity: DeveloperModel + billing: Billing | null + loading: boolean + isServiceChartLoading: boolean +} + +export type HandleUseEffectParams = { + myAppIds: string[] + dateFrom: string + dateTo: string + dispatch: Dispatch +} + +export const handleUseEffect = ({ myAppIds, dateFrom, dateTo, dispatch }: HandleUseEffectParams) => () => { + const isHaveDeveloperApps = myAppIds.length > 0 + if (isHaveDeveloperApps) { + dispatch(fetchBilling({ applicationId: myAppIds, dateFrom: dateFrom, dateTo: dateTo })) + } +} + +export const renderChart = (isLoading: boolean, datasets: ChartData) => { + if (isLoading) { + return + } + return ( + + ) } export const ServiceChart: React.FC = () => { + const dispatch = useDispatch() + const myApps = useSelector(selectDeveloperApps) + const myIdentity = useSelector(selectMyIdentity) + const billing = useSelector(selectBilling) + const loading = useSelector(selectDeveloperLoading) + const isServiceChartLoading = useSelector(selectIsServiceChartLoading) + + const myAppIds = myApps.map((item: AppSummaryModel) => item.id) as string[] + const dateFrom = dayjs(myIdentity.created).format(DATE_TIME_FORMAT.YYYY_MM) as string + const dateTo = dayjs().format(DATE_TIME_FORMAT.YYYY_MM) + React.useEffect(handleUseEffect({ myAppIds, dateFrom, dateTo, dispatch }), [myIdentity.id, myApps.length]) + const datasets = mapServiceChartDataSet(billing) + const isLoading = loading || isServiceChartLoading return (

Services

- + {renderChart(isLoading, datasets)}
) diff --git a/packages/marketplace/src/components/ui/developer-analytics/detailed/detailed-tab.tsx b/packages/marketplace/src/components/ui/developer-analytics/detailed/detailed-tab.tsx index 76b47fe95b..2c7d362e88 100644 --- a/packages/marketplace/src/components/ui/developer-analytics/detailed/detailed-tab.tsx +++ b/packages/marketplace/src/components/ui/developer-analytics/detailed/detailed-tab.tsx @@ -9,7 +9,7 @@ import { httpTrafficPerDayRequestData } from '@/actions/app-http-traffic-event' import { appInstallationsRequestData, appInstallationsFilterRequestData } from '@/actions/app-installations' import { InstallationModel, AppSummaryModel } from '@reapit/foundations-ts-definitions' import { getAppUsageStats, getAppHttpTraffic } from '@/selector/analytics' -import { getDevelopers } from '@/selector/developer' +import { selectDeveloper } from '@/selector/developer' import { getInstallations } from '@/selector/installations' import { FlexContainerBasic, Grid, GridItem, FlexContainerResponsive, DATE_TIME_FORMAT } from '@reapit/elements' @@ -120,7 +120,7 @@ export type MapState = { export const mapState = (state: ReduxState): MapState => { return { installations: getInstallations(state), - developer: getDevelopers(state), + developer: selectDeveloper(state), appUsageStats: getAppUsageStats(state), appHttpTraffic: getAppHttpTraffic(state), } diff --git a/packages/marketplace/src/constants/action-types.ts b/packages/marketplace/src/constants/action-types.ts index 80d844dcde..f7e2b854e4 100644 --- a/packages/marketplace/src/constants/action-types.ts +++ b/packages/marketplace/src/constants/action-types.ts @@ -1,7 +1,20 @@ /** + * TODO: will refactor this to separate DEVELOPER PORTAL ACTIONS, CLIENT AND ADMIN + * by <>_<> * Please follow the <>_<> pattern and group actions by STATE */ const ActionTypes = { + // DEVELOPER PORTAL + DEVELOPER_FETCH_MY_IDENTITY: 'DEVELOPER_FETCH_MY_IDENTITY', + DEVELOPER_SET_MY_IDENTITY: 'DEVELOPER_SET_MY_IDENTITY', + DEVELOPER_FETCH_BILLING: 'DEVELOPER_FETCH_BILLING', + DEVELOPER_FETCH_BILLING_SUCCESS: 'DEVELOPER_FETCH_BILLING_SUCCESS', + DEVELOPER_FETCH_BILLING_FAILURE: 'DEVELOPER_FETCH_BILLING_FAILURE', + // CLIENT PORTAL + + // ADMIN PORTAL + + // MISC // Developer App Modal SET_DEVELOPER_APP_MODAL_STATE_EDIT_DETAIL: 'SET_DEVELOPER_APP_MODAL_STATE_EDIT_DETAIL', SET_DEVELOPER_APP_MODAL_STATE_VIEW_DETAIL: 'SET_DEVELOPER_APP_MODAL_STATE_VIEW_DETAIL', diff --git a/packages/marketplace/src/constants/routes.ts b/packages/marketplace/src/constants/routes.ts index 706188fcec..4e3a26d282 100644 --- a/packages/marketplace/src/constants/routes.ts +++ b/packages/marketplace/src/constants/routes.ts @@ -15,6 +15,7 @@ const Routes = { DEVELOPER_MY_APPS_EDIT: '/developer/apps/:appid/edit', DEVELOPER_API_DOCS: '/developer/api-docs', DEVELOPER_ANALYTICS: '/developer/analytics', + DEVELOPER_ANALYTICS_BILLING: '/developer/analytics/billing', DEVELOPER_ANALYTICS_TAB: '/developer/analytics/:activeTab?', DEVELOPER_RESET_PASSWORD: '/developer/reset-password', DEVELOPER_WEBHOOKS: '/developer/webhooks', diff --git a/packages/marketplace/src/core/store.ts b/packages/marketplace/src/core/store.ts index 98865c6192..82fe54f174 100644 --- a/packages/marketplace/src/core/store.ts +++ b/packages/marketplace/src/core/store.ts @@ -2,56 +2,55 @@ import { createStore, applyMiddleware, compose, combineReducers, Store as ReduxS import createSagaMiddleware from 'redux-saga' import { fork, all } from '@redux-saga/core/effects' import { ReduxState } from '../types/core' - -import auth from '../reducers/auth' -import client from '../reducers/client' -import installedApps from '../reducers/installed-apps' -import myApps from '../reducers/my-apps' -import developer from '../reducers/developer' -import appDetail from '../reducers/app-detail' -import error from '../reducers/error' -import submitApp from '../reducers/submit-app' -import submitRevision from '../reducers/submit-revision' -import adminApprovals from '../reducers/admin-approvals' -import adminDevManagement from '../reducers/admin-dev-management' -import developerSetStatus from '../reducers/developer-set-status' -import revisionDetail from '../reducers/revision-detail' -import appDetailModal from '../reducers/app-detail-modal' -import appDeleteReducer from '../reducers/app-delete' -import appCategories from '../reducers/app-categories' -import settingsReducer from '../reducers/settings' -import adminApps from '../reducers/admin-apps' -import appInstallationsReducer from '../reducers/app-installations' -import appUsageStatsReducer from '../reducers/app-usage-stats' -import adminStatsReducer from '../reducers/admin-stats' -import revisionsReducer from '../reducers/revisions' -import appHttpTrafficEventReducer from '../reducers/app-http-traffic-event' -import integrationTypes from '../reducers/app-integration-types' +import auth from '@/reducers/auth' +import client from '@/reducers/client' +import installedApps from '@/reducers/installed-apps' +import myApps from '@/reducers/my-apps' +import developer from '@/reducers/developer' +import appDetail from '@/reducers/app-detail' +import error from '@/reducers/error' +import submitApp from '@/reducers/submit-app' +import submitRevision from '@/reducers/submit-revision' +import adminApprovals from '@/reducers/admin-approvals' +import adminDevManagement from '@/reducers/admin-dev-management' +import developerSetStatus from '@/reducers/developer-set-status' +import revisionDetail from '@/reducers/revision-detail' +import appDetailModal from '@/reducers/app-detail-modal' +import appDeleteReducer from '@/reducers/app-delete' +import appCategories from '@/reducers/app-categories' +import settingsReducer from '@/reducers/settings' +import adminApps from '@/reducers/admin-apps' +import appInstallationsReducer from '@/reducers/app-installations' +import appUsageStatsReducer from '@/reducers/app-usage-stats' +import adminStatsReducer from '@/reducers/admin-stats' +import revisionsReducer from '@/reducers/revisions' +import appHttpTrafficEventReducer from '@/reducers/app-http-traffic-event' +import integrationTypes from '@/reducers/app-integration-types' import webhookEditReducer from '../reducers/webhook-edit-modal' -import webhookSubscriptions from '../reducers/webhook-subscriptions' - -import authSagas from '../sagas/auth' -import clientSagas from '../sagas/client' -import appDetailSagas from '../sagas/app-detail' -import installedAppsSagas from '../sagas/installed-apps' -import appUsageStatsSagas from '../sagas/app-usage-stats' -import appHttpTrafficEventSagas from '../sagas/app-http-trafic-event' -import myAppsSagas from '../sagas/my-apps' -import developerSagas from '../sagas/developer' -import submitAppSagas from '../sagas/submit-app' -import submitRevisionSagas from '../sagas/submit-revision' -import adminApprovalSagas from '../sagas/admin-approvals' -import adminDevManagementSagas from '../sagas/admin-dev-management' -import developerSetStatusSagas from '../sagas/developer-set-status' -import revisionDetailSagas from '../sagas/revision-detail' -import revisionsSagas from '../sagas/revisions' -import appDeleteSagas from '../sagas/app-delete' -import settingSagas from '../sagas/settings' -import adminAppsSagas from '../sagas/admin-apps' -import appInstallationsSagas from '../sagas/app-installations' -import noticationMessage from '../reducers/notification-message' -import adminStatsSaga from '../sagas/admin-stats' -import webhookSubscriptionsSagas from '../sagas/webhook-subscriptions' +import webhookSubscriptions from '@/reducers/webhook-subscriptions' + +import authSagas from '@/sagas/auth' +import clientSagas from '@/sagas/client' +import appDetailSagas from '@/sagas/app-detail' +import installedAppsSagas from '@/sagas/installed-apps' +import appUsageStatsSagas from '@/sagas/app-usage-stats' +import appHttpTrafficEventSagas from '@/sagas/app-http-trafic-event' +import myAppsSagas from '@/sagas/my-apps' +import developerSagas from '@/sagas/developer' +import submitAppSagas from '@/sagas/submit-app' +import submitRevisionSagas from '@/sagas/submit-revision' +import adminApprovalSagas from '@/sagas/admin-approvals' +import adminDevManagementSagas from '@/sagas/admin-dev-management' +import developerSetStatusSagas from '@/sagas/developer-set-status' +import revisionDetailSagas from '@/sagas/revision-detail' +import revisionsSagas from '@/sagas/revisions' +import appDeleteSagas from '@/sagas/app-delete' +import settingSagas from '@/sagas/settings' +import adminAppsSagas from '@/sagas/admin-apps' +import appInstallationsSagas from '@/sagas/app-installations' +import noticationMessage from '@/reducers/notification-message' +import adminStatsSaga from '@/sagas/admin-stats' +import webhookSubscriptionsSagas from '@/sagas/webhook-subscriptions' import { injectSwitchModeToWindow } from '@reapit/elements' import webhookEditSagas from '../sagas/webhook-edit-modal' diff --git a/packages/marketplace/src/reducers/__tests__/developer.ts b/packages/marketplace/src/reducers/__tests__/developer.ts index d145a733e2..048be6b4cc 100644 --- a/packages/marketplace/src/reducers/__tests__/developer.ts +++ b/packages/marketplace/src/reducers/__tests__/developer.ts @@ -2,6 +2,7 @@ import developerReducer, { defaultState } from '../developer' import { ActionType } from '../../types/core' import ActionTypes from '../../constants/action-types' import { appsDataStub } from '../../sagas/__stubs__/apps' +import { billing } from '@/sagas/__stubs__/billing' describe('developer reducer', () => { it('should return default state if action not matched', () => { @@ -62,4 +63,42 @@ describe('developer reducer', () => { } expect(newState).toEqual(expected) }) + + it('should set isServiceChartLoading when call DEVELOPER_FETCH_BILLING is called', () => { + const newState = developerReducer(undefined, { + type: ActionTypes.DEVELOPER_FETCH_BILLING as ActionType, + data: null, + }) + const expected = { + ...defaultState, + isServiceChartLoading: true, + } + expect(newState).toEqual(expected) + }) + + it('should set billing and isServiceChartLoading when call DEVELOPER_FETCH_BILLING_SUCCESS is called', () => { + const newState = developerReducer(undefined, { + type: ActionTypes.DEVELOPER_FETCH_BILLING_SUCCESS as ActionType, + data: billing, + }) + const expected = { + ...defaultState, + isServiceChartLoading: false, + billing: billing, + } + expect(newState).toEqual(expected) + }) + + it('should set fetchBillingFailure when call DEVELOPER_FETCH_BILLING_FAILURE is called', () => { + const newState = developerReducer(undefined, { + type: ActionTypes.DEVELOPER_FETCH_BILLING_FAILURE as ActionType, + data: 'error', + }) + const expected = { + ...defaultState, + isServiceChartLoading: false, + error: 'error', + } + expect(newState).toEqual(expected) + }) }) diff --git a/packages/marketplace/src/reducers/developer.ts b/packages/marketplace/src/reducers/developer.ts index 9f4b370be7..8df1a151b2 100644 --- a/packages/marketplace/src/reducers/developer.ts +++ b/packages/marketplace/src/reducers/developer.ts @@ -1,7 +1,16 @@ import { Action, FormState } from '../types/core' import { isType } from '../utils/actions' -import { developerLoading, developerReceiveData, developerClearData, developerSetFormState } from '../actions/developer' -import { PagedResultAppSummaryModel_, ScopeModel } from '@reapit/foundations-ts-definitions' +import { + developerLoading, + developerReceiveData, + developerClearData, + developerSetFormState, + setMyIdentity, + fetchBilling, + fetchBillingSuccess, + fetchBillingFailure, +} from '@/actions/developer' +import { PagedResultAppSummaryModel_, ScopeModel, DeveloperModel } from '@reapit/foundations-ts-definitions' import { developerAppShowModal } from '@/actions/developer-app-modal' export interface DeveloperRequestParams { @@ -14,11 +23,33 @@ export interface DeveloperItem { scopes: ScopeModel[] } +export type RequestByPeriod = { + period: string + periodStart: string + periodEnd: string + periodName: string + requestCount: number + endpointCount: number + netAmount: number + grossAmount: number + vatAmount: number +} + +export type Billing = { + from: string + to: string + requestsByPeriod: RequestByPeriod[] +} + export interface DeveloperState { loading: boolean developerData: DeveloperItem | null formState: FormState isVisible: boolean + myIdentity: DeveloperModel | null + billing: Billing | null + isServiceChartLoading: boolean + error: unknown } export const defaultState: DeveloperState = { @@ -26,6 +57,10 @@ export const defaultState: DeveloperState = { developerData: null, formState: 'PENDING', isVisible: false, + myIdentity: null, + billing: null, + isServiceChartLoading: true, + error: null, } const developerReducer = (state: DeveloperState = defaultState, action: Action): DeveloperState => { @@ -66,6 +101,36 @@ const developerReducer = (state: DeveloperState = defaultState, action: Action { }) }) +describe('fetchMyIdentitySagas', () => { + const developerId = '1' + const gen = cloneableGenerator(fetchMyIdentitySagas as any)({ data: params }) + expect(gen.next().value).toEqual(put(developerLoading(true))) + expect(gen.next().value).toEqual(select(selectDeveloperId)) + expect(gen.next(developerId).value).toEqual(call(api.fetchMyIdentity, developerId)) + it('api call success', () => { + const clone = gen.clone() + expect(clone.next(developerIdentity).value).toEqual(put(setMyIdentity(developerIdentity))) + expect(clone.next().value).toEqual(put(developerLoading(false))) + expect(clone.next().done).toEqual(true) + }) + + it('api call error', () => { + const clone = gen.clone() + // @ts-ignore + expect(clone.throw('error').value).toEqual(put(developerLoading(false))) + expect(clone.next().value).toEqual( + put( + errorThrownServer({ + type: 'SERVER', + message: errorMessages.DEFAULT_SERVER_ERROR, + }), + ), + ) + expect(clone.next().done).toEqual(true) + }) +}) + +describe('fetchBillingSagas', () => { + const params = { + dateFrom: '2020-01', + dateTo: '2020-05', + applicationId: ['1', '2'], + } as FetchBillingParams + const gen = cloneableGenerator(fetchBillingSagas as any)({ data: params }) + expect(gen.next().value).toEqual(call(api.fetchBilling, params)) + it('api call success', () => { + const clone = gen.clone() + expect(clone.next(billing).value).toEqual(put(fetchBillingSuccess(billing))) + expect(clone.next().done).toEqual(true) + }) + it('api call error', () => { + const clone = gen.clone() + // @ts-ignore + expect(clone.throw('error').value).toEqual(put(fetchBillingFailure('error'))) + expect(clone.next().value).toEqual( + put( + errorThrownServer({ + type: 'SERVER', + message: errorMessages.DEFAULT_SERVER_ERROR, + }), + ), + ) + expect(clone.next().done).toEqual(true) + }) +}) + describe('developer thunks', () => { describe('developerRequestDataListen', () => { it('should request data listen', () => { @@ -136,6 +204,22 @@ describe('developer thunks', () => { }) }) + describe('developerCreateListen', () => { + it('should trigger developerCreact action', () => { + const gen = fetchMyIdentitySagasListen() + expect(gen.next().value).toEqual(takeLatest(ActionTypes.DEVELOPER_FETCH_MY_IDENTITY, fetchMyIdentitySagas)) + expect(gen.next().done).toBe(true) + }) + }) + + describe('developerCreateListen', () => { + it('should trigger developerCreact action', () => { + const gen = fetchBillingSagasListen() + expect(gen.next().value).toEqual(takeLatest(ActionTypes.DEVELOPER_FETCH_BILLING, fetchBillingSagas)) + expect(gen.next().done).toBe(true) + }) + }) + describe('developerCreateListen', () => { it('should trigger developerCreact action', () => { const gen = developerCreateListen() @@ -148,7 +232,14 @@ describe('developer thunks', () => { it('should listen developer request data & create app action', () => { const gen = developerSagas() - expect(gen.next().value).toEqual(all([fork(developerRequestDataListen), fork(developerCreateListen)])) + expect(gen.next().value).toEqual( + all([ + fork(developerRequestDataListen), + fork(developerCreateListen), + fork(fetchMyIdentitySagasListen), + fork(fetchBillingSagasListen), + ]), + ) expect(gen.next().done).toBe(true) }) }) diff --git a/packages/marketplace/src/sagas/api.ts b/packages/marketplace/src/sagas/api.ts index acc7d137d5..bccf24b994 100644 --- a/packages/marketplace/src/sagas/api.ts +++ b/packages/marketplace/src/sagas/api.ts @@ -1,9 +1,12 @@ +// TODO: TO MOVE ALL API CALL TO HERE + import { fetcher, setQueryParams } from '@reapit/elements' import { URLS, generateHeader } from '../constants/api' import { RevisionsRequestParams } from '@/actions/revisions' import { APPS_PER_PAGE } from '@/constants/paginator' import { logger } from 'logger' +// Apps API export const fetchAdminApps = async ({ params }) => { try { const response = await fetcher({ @@ -53,7 +56,47 @@ export const fetchAppRevisions = async (params: RevisionsRequestParams) => { } } +// developer API +export const fetchMyIdentity = async (developerId: string) => { + try { + const response = await fetcher({ + url: `${URLS.developers}/${developerId}`, + api: window.reapit.config.marketplaceApiUrl, + method: 'GET', + headers: generateHeader(window.reapit.config.marketplaceApiKey), + }) + return response + } catch (error) { + logger(error) + throw new Error(error) + } +} + +// billing API +export type FetchBillingParams = { + dateFrom: string + dateTo: string + applicationId: string[] +} + +export const fetchBilling = async (params: FetchBillingParams) => { + try { + const response = await fetcher({ + url: `${URLS.trafficEvents}/billing?${setQueryParams(params)}`, + api: window.reapit.config.marketplaceApiUrl, + method: 'GET', + headers: generateHeader(window.reapit.config.marketplaceApiKey), + }) + return response + } catch (error) { + logger(error) + throw new Error(error) + } +} + export default { fetchAdminApps, deleteApp, + fetchMyIdentity, + fetchBilling, } diff --git a/packages/marketplace/src/sagas/developer.ts b/packages/marketplace/src/sagas/developer.ts index 5c0a945afc..c9fc56b416 100644 --- a/packages/marketplace/src/sagas/developer.ts +++ b/packages/marketplace/src/sagas/developer.ts @@ -1,21 +1,25 @@ import { fetcher } from '@reapit/elements' import { put, fork, takeLatest, all, call, select } from '@redux-saga/core/effects' -import { errorThrownServer } from '../actions/error' import { CreateDeveloperModel } from '@reapit/foundations-ts-definitions' -import { APPS_PER_PAGE } from '@/constants/paginator' -import { selectDeveloperId } from '@/selector/developer' -import { DeveloperItem, DeveloperRequestParams } from '@/reducers/developer' import { logger } from 'logger' import { developerLoading, developerReceiveData, developerSetFormState, developerRequestDataFailure, -} from '../actions/developer' -import ActionTypes from '../constants/action-types' -import errorMessages from '../constants/error-messages' -import { URLS, generateHeader } from '../constants/api' -import { Action } from '../types/core' + setMyIdentity, + fetchBillingSuccess, + fetchBillingFailure, +} from '@/actions/developer' +import { APPS_PER_PAGE } from '@/constants/paginator' +import { DeveloperItem, DeveloperRequestParams } from '@/reducers/developer' +import { errorThrownServer } from '@/actions/error' +import ActionTypes from '@/constants/action-types' +import errorMessages from '@/constants/error-messages' +import { URLS, generateHeader } from '@/constants/api' +import { Action } from '@/types/core' +import { selectDeveloperId } from '@/selector' +import api, { FetchBillingParams } from './api' export const developerDataFetch = function*({ data }) { yield put(developerLoading(true)) @@ -88,6 +92,43 @@ export const developerCreate = function*({ data }: Action) } } +export const fetchMyIdentitySagas = function*() { + try { + yield put(developerLoading(true)) + const developerId = yield select(selectDeveloperId) + const developerIdentity = yield call(api.fetchMyIdentity, developerId) + if (developerIdentity) { + yield put(setMyIdentity(developerIdentity)) + } + yield put(developerLoading(false)) + } catch (err) { + logger(err) + yield put(developerLoading(false)) + yield put( + errorThrownServer({ + type: 'SERVER', + message: errorMessages.DEFAULT_SERVER_ERROR, + }), + ) + } +} + +export const fetchBillingSagas = function*({ data }: Action) { + try { + const billingResponse = yield call(api.fetchBilling, data) + yield put(fetchBillingSuccess(billingResponse)) + } catch (err) { + logger(err) + yield put(fetchBillingFailure(err)) + yield put( + errorThrownServer({ + type: 'SERVER', + message: errorMessages.DEFAULT_SERVER_ERROR, + }), + ) + } +} + export const developerRequestDataListen = function*() { yield takeLatest>(ActionTypes.DEVELOPER_REQUEST_DATA, developerDataFetch) } @@ -96,8 +137,21 @@ export const developerCreateListen = function*() { yield takeLatest(ActionTypes.DEVELOPER_CREATE, developerCreate) } +export const fetchMyIdentitySagasListen = function*() { + yield takeLatest(ActionTypes.DEVELOPER_FETCH_MY_IDENTITY, fetchMyIdentitySagas) +} + +export const fetchBillingSagasListen = function*() { + yield takeLatest(ActionTypes.DEVELOPER_FETCH_BILLING, fetchBillingSagas) +} + const developerSagas = function*() { - yield all([fork(developerRequestDataListen), fork(developerCreateListen)]) + yield all([ + fork(developerRequestDataListen), + fork(developerCreateListen), + fork(fetchMyIdentitySagasListen), + fork(fetchBillingSagasListen), + ]) } export default developerSagas diff --git a/packages/marketplace/src/selector/__tests__/developer.ts b/packages/marketplace/src/selector/__tests__/developer.ts index 537c60dc49..3f2f35aeeb 100644 --- a/packages/marketplace/src/selector/__tests__/developer.ts +++ b/packages/marketplace/src/selector/__tests__/developer.ts @@ -1,5 +1,14 @@ import { ReduxState } from '@/types/core' -import { selectDeveloperId, selectDeveloperEmail } from '../developer' +import { + selectDeveloperId, + selectDeveloperEmail, + selectDeveloper, + selectDeveloperApps, + selectMyIdentity, + selectBilling, + selectDeveloperLoading, + selectIsServiceChartLoading, +} from '../developer' describe('selectDeveloperId', () => { it('should run correctly', () => { @@ -44,3 +53,119 @@ describe('selectDeveloperEmail', () => { expect(result).toEqual(undefined) }) }) + +describe('selectDeveloper', () => { + it('should run correctly', () => { + const input = { + developer: { + isServiceChartLoading: false, + developerData: {}, + }, + } as ReduxState + const result = selectDeveloper(input) + expect(result).toEqual(input.developer) + }) + + it('should run correctly and return undefined', () => { + const input = {} as ReduxState + const result = selectDeveloper(input) + expect(result).toEqual(undefined) + }) +}) + +describe('selectDeveloperApps', () => { + it('should run correctly', () => { + const input = { + developer: { + isServiceChartLoading: false, + developerData: { + data: { + data: [{}], + }, + }, + }, + } as ReduxState + const result = selectDeveloperApps(input) + expect(result).toEqual(input.developer.developerData?.data.data) + }) + + it('should run correctly and return []', () => { + const input = {} as ReduxState + const result = selectDeveloperApps(input) + expect(result).toEqual([]) + }) +}) + +describe('selectMyIdentity', () => { + it('should run correctly', () => { + const input = { + developer: { + isServiceChartLoading: false, + myIdentity: {}, + }, + } as ReduxState + const result = selectMyIdentity(input) + expect(result).toEqual(input.developer.myIdentity) + }) + + it('should run correctly and return {}', () => { + const input = {} as ReduxState + const result = selectMyIdentity(input) + expect(result).toEqual({}) + }) +}) + +describe('selectBilling', () => { + it('should run correctly', () => { + const input = { + developer: { + isServiceChartLoading: false, + billing: {}, + }, + } as ReduxState + const result = selectBilling(input) + expect(result).toEqual(input.developer.billing) + }) + + it('should run correctly and return null', () => { + const input = {} as ReduxState + const result = selectBilling(input) + expect(result).toEqual(null) + }) +}) + +describe('selectDeveloperLoading', () => { + it('should run correctly', () => { + const input = { + developer: { + loading: true, + }, + } as ReduxState + const result = selectDeveloperLoading(input) + expect(result).toEqual(true) + }) + + it('should run correctly and return null', () => { + const input = {} as ReduxState + const result = selectDeveloperLoading(input) + expect(result).toEqual(undefined) + }) +}) + +describe('selectIsServiceChartLoading', () => { + it('should run correctly', () => { + const input = { + developer: { + isServiceChartLoading: true, + }, + } as ReduxState + const result = selectIsServiceChartLoading(input) + expect(result).toEqual(true) + }) + + it('should run correctly and return null', () => { + const input = {} as ReduxState + const result = selectIsServiceChartLoading(input) + expect(result).toEqual(undefined) + }) +}) diff --git a/packages/marketplace/src/selector/developer.ts b/packages/marketplace/src/selector/developer.ts index 15506849cd..452340f456 100644 --- a/packages/marketplace/src/selector/developer.ts +++ b/packages/marketplace/src/selector/developer.ts @@ -1,5 +1,7 @@ +// TODO: Will move all developerSelector to here for reusable import { ReduxState } from '@/types/core' -import { AppSummaryModel } from '@reapit/foundations-ts-definitions' +import { AppSummaryModel, DeveloperModel } from '@reapit/foundations-ts-definitions' +import { Billing } from '@/reducers/developer' export const selectDeveloperId = (state: ReduxState) => { return state.auth.loginSession?.loginIdentity.developerId @@ -9,10 +11,26 @@ export const selectDeveloperEmail = (state: ReduxState) => { return state?.settings?.developerInfomation?.email } -export const getDevelopers = (state: ReduxState) => { +export const selectDeveloper = (state: ReduxState) => { return state.developer } -export const selectDeveloperApps = (state: ReduxState) => { - return state?.developer?.developerData?.data?.data || ([] as AppSummaryModel[]) +export const selectDeveloperApps = (state: ReduxState): AppSummaryModel[] => { + return state.developer?.developerData?.data.data || ([] as AppSummaryModel[]) +} + +export const selectMyIdentity = (state: ReduxState): DeveloperModel => { + return state.developer?.myIdentity || {} +} + +export const selectBilling = (state: ReduxState): Billing | null => { + return state.developer?.billing || null +} + +export const selectDeveloperLoading = (state: ReduxState): boolean => { + return state.developer?.loading +} + +export const selectIsServiceChartLoading = (state: ReduxState): boolean => { + return state.developer?.isServiceChartLoading } diff --git a/packages/marketplace/src/selector/index.ts b/packages/marketplace/src/selector/index.ts new file mode 100644 index 0000000000..9aab0d3c22 --- /dev/null +++ b/packages/marketplace/src/selector/index.ts @@ -0,0 +1 @@ +export * from './developer' diff --git a/packages/marketplace/src/utils/__tests__/route-dispatcher.ts b/packages/marketplace/src/utils/__tests__/route-dispatcher.ts index 20e9d329b3..05c7450dd3 100644 --- a/packages/marketplace/src/utils/__tests__/route-dispatcher.ts +++ b/packages/marketplace/src/utils/__tests__/route-dispatcher.ts @@ -3,17 +3,17 @@ import store from '../../core/store' import Routes from '../../constants/routes' import { GET_ALL_PAGE_SIZE } from '../../constants/paginator' import { RouteValue } from '../../types/core' -import { clientRequestData } from '../../actions/client' -import { developerRequestData } from '../../actions/developer' -import { myAppsRequestData } from '../../actions/my-apps' -import { installedAppsRequestData } from '../../actions/installed-apps' -import { adminApprovalsRequestData } from '../../actions/admin-approvals' -import { getAccessToken } from '../../utils/session' +import { clientRequestData } from '@/actions/client' +import { developerRequestData, fetchMyIdentity } from '@/actions/developer' +import { myAppsRequestData } from '@/actions/my-apps' +import { installedAppsRequestData } from '@/actions/installed-apps' +import { adminApprovalsRequestData } from '@/actions/admin-approvals' +import { getAccessToken } from '@/utils/session' import { requestDeveloperData } from '@/actions/settings' import { getParamsFromPath } from '@/utils/client-url-params' jest.mock('@reapit/elements') -jest.mock('../../utils/session') +jest.mock('@/utils/session') jest.mock('../../core/store') jest.mock('../../sagas/client') jest.mock('../../sagas/developer') @@ -77,5 +77,6 @@ describe('routeDispatcher', () => { it('should dispatch to appInstallationsRequestData & developerRequestData for the analytics route', async () => { await routeDispatcher(Routes.DEVELOPER_ANALYTICS_TAB as RouteValue) expect(store.dispatch).toHaveBeenCalledWith(developerRequestData({ appsPerPage: GET_ALL_PAGE_SIZE, page: 1 })) + expect(store.dispatch).toHaveBeenCalledWith(fetchMyIdentity()) }) }) diff --git a/packages/marketplace/src/utils/route-dispatcher.ts b/packages/marketplace/src/utils/route-dispatcher.ts index c2a50a8a65..0d7c63f6d4 100644 --- a/packages/marketplace/src/utils/route-dispatcher.ts +++ b/packages/marketplace/src/utils/route-dispatcher.ts @@ -7,7 +7,7 @@ import store from '../core/store' import { clientRequestData } from '../actions/client' import { myAppsRequestData } from '../actions/my-apps' import { installedAppsRequestData } from '../actions/installed-apps' -import { developerRequestData } from '../actions/developer' +import { developerRequestData, fetchMyIdentity } from '@/actions/developer' import { adminApprovalsRequestData } from '../actions/admin-approvals' import { adminDevManagementRequestData } from '../actions/admin-dev-management' import { submitAppRequestData } from '../actions/submit-app' @@ -24,7 +24,6 @@ const routeDispatcher = async (route: RouteValue, params?: StringMap, search?: s const id = params && params.appid ? params.appid : '' const queryParams = new URLSearchParams(search) const appId = queryParams.get('appId') - const PAGE_SIZE_FOR_ALL_APPS = 999 switch (route) { case Routes.CLIENT: @@ -47,6 +46,7 @@ const routeDispatcher = async (route: RouteValue, params?: StringMap, search?: s break case Routes.DEVELOPER_ANALYTICS_TAB: { // Fetch all apps to map app name to installations + store.dispatch(fetchMyIdentity()) store.dispatch(developerRequestData({ page: 1, appsPerPage: GET_ALL_PAGE_SIZE })) if (appId) { const clientId = selectClientId(store.state) @@ -90,7 +90,7 @@ const routeDispatcher = async (route: RouteValue, params?: StringMap, search?: s store.dispatch(requestDeveloperData()) break case Routes.DEVELOPER_WEBHOOKS: - store.dispatch(developerRequestData({ page: 1, appsPerPage: PAGE_SIZE_FOR_ALL_APPS } as DeveloperRequestParams)) + store.dispatch(developerRequestData({ page: 1, appsPerPage: GET_ALL_PAGE_SIZE } as DeveloperRequestParams)) store.dispatch(webhookTopicsRequestData()) break case Routes.DEVELOPER_HELP: From ac134898d1fe5d6f26e45a0918e9ae606c65e6ba Mon Sep 17 00:00:00 2001 From: Pham Hai Duong Date: Thu, 7 May 2020 22:51:54 +0700 Subject: [PATCH 2/7] feat: #1164 add SBOX to customer of subscription drop down (#1172) feat: #1164 add SBOX to customer of subscription drop down (#1172) Changes - modify generateCustomerOptions for return correct value --- .../__snapshots__/webhook-edit-modal.tsx.snap | 28 +++++---- .../ui/__tests__/webhook-edit-modal.tsx | 22 +++---- .../src/components/ui/webhook-edit-modal.tsx | 60 ++++++++++--------- 3 files changed, 60 insertions(+), 50 deletions(-) diff --git a/packages/marketplace/src/components/ui/__tests__/__snapshots__/webhook-edit-modal.tsx.snap b/packages/marketplace/src/components/ui/__tests__/__snapshots__/webhook-edit-modal.tsx.snap index 9a02b0abf6..a3fbd9b47b 100644 --- a/packages/marketplace/src/components/ui/__tests__/__snapshots__/webhook-edit-modal.tsx.snap +++ b/packages/marketplace/src/components/ui/__tests__/__snapshots__/webhook-edit-modal.tsx.snap @@ -32,10 +32,10 @@ exports[`WebhookEditModal should match a snapshot 5`] = `
diff --git a/packages/marketplace/src/components/ui/__tests__/webhook-edit-modal.tsx b/packages/marketplace/src/components/ui/__tests__/webhook-edit-modal.tsx index 58be2ea916..dfdd7e262e 100644 --- a/packages/marketplace/src/components/ui/__tests__/webhook-edit-modal.tsx +++ b/packages/marketplace/src/components/ui/__tests__/webhook-edit-modal.tsx @@ -55,9 +55,9 @@ describe('WebhookEditModal', () => { const appId = '' const fn = onEdit(editWebhook, webhookId, appId) const values: FormValuesType = { - WebhookURL: '', - SubscriptionTopics: [], - SubscriptionCustomers: [], + url: '', + topicIds: [], + customerIds: [], active: false, } fn(values) @@ -68,9 +68,9 @@ describe('WebhookEditModal', () => { const webhookId = '' const fn = onCreate(createWebhook, webhookId) const values = { - WebhookURL: '', - SubscriptionTopics: [], - SubscriptionCustomers: [], + url: '', + topicIds: [], + customerIds: [], active: false, } fn(values) @@ -147,7 +147,7 @@ describe('WebhookEditModal', () => { created: 'string', appId: 'string', client: 'client', - status: '', + status: 'Terminated', authFlow: '', }, { @@ -155,16 +155,16 @@ describe('WebhookEditModal', () => { created: 'string', appId: 'appId', client: 'client', - status: '', + status: 'Active', authFlow: '', }, ] const expected = [ { - value: 'client', - label: 'client', - description: 'client', + value: 'SBOX', + label: 'SBOX', + description: 'SBOX', }, { value: 'client', diff --git a/packages/marketplace/src/components/ui/webhook-edit-modal.tsx b/packages/marketplace/src/components/ui/webhook-edit-modal.tsx index a95c32ee2e..6eb717b195 100644 --- a/packages/marketplace/src/components/ui/webhook-edit-modal.tsx +++ b/packages/marketplace/src/components/ui/webhook-edit-modal.tsx @@ -90,14 +90,23 @@ export const generateTopicOptions = (topics: TopicItem[]) => { } export const generateCustomerOptions = (customers: CustomerItem[]) => { - return customers.map( - customer => - ({ + const customerOptions: SelectOption[] = [ + { + value: 'SBOX', + label: 'SBOX', + description: 'SBOX', + } as SelectOption, + ] + customers.forEach((customer: CustomerItem) => { + if (customer.status === 'Active') { + customerOptions.push({ value: customer.client, label: customer.client, description: customer.client, - } as SelectOption), - ) + } as SelectOption) + } + }) + return customerOptions } export const onCreate = (createWebhook: (data: CreateWebhookParams) => void, appId: string) => ( @@ -105,10 +114,10 @@ export const onCreate = (createWebhook: (data: CreateWebhookParams) => void, app ) => { const params: CreateWebhookParams = { applicationId: appId, - url: values.WebhookURL, + url: values.url, description: '', - topicIds: values.SubscriptionTopics, - customerIds: values.SubscriptionCustomers, + topicIds: values.topicIds, + customerIds: values.customerIds, active: values.active, } createWebhook(params) @@ -120,10 +129,10 @@ export const onEdit = (editWebhook: (data: EditWebhookParams) => void, webhookId const params: EditWebhookParams = { applicationId: appId, webhookId, - url: values.WebhookURL, + url: values.url, description: '', - topicIds: values.SubscriptionTopics, - customerIds: values.SubscriptionCustomers, + topicIds: values.topicIds, + customerIds: values.customerIds, active: values.active, } editWebhook(params) @@ -138,9 +147,9 @@ export type WebhookModalInnerProps = WebhookModalInnerMappedAction & } export type FormValuesType = { - WebhookURL: string - SubscriptionTopics: string[] - SubscriptionCustomers: string[] + url: string + topicIds: string[] + customerIds: string[] active: boolean } @@ -158,9 +167,9 @@ export const WebhookModalInner: React.FunctionComponent const modalConfig = isUpdate ? EDIT_MODAL : CREATE_MODAL const initFormValues: FormValuesType = { - WebhookURL: webhookData?.url, - SubscriptionTopics: webhookData?.topicIds, - SubscriptionCustomers: webhookData?.customerIds, + url: webhookData?.url, + topicIds: webhookData?.topicIds, + customerIds: webhookData?.customerIds, active: webhookData?.active, } @@ -200,27 +209,20 @@ export const WebhookModalInner: React.FunctionComponent

- + Date: Fri, 8 May 2020 00:09:22 +0700 Subject: [PATCH 3/7] feat: #1106 web component config update (#1157) * feat: #1106 Web components update config * feat: #1106 Fixing customerId not included --- packages/demo-site/config.example.json | 1 + packages/demo-site/index.ejs | 4 +++- packages/demo-site/scripts/build.js | 2 +- .../client/components/appointment-bookings.svelte | 1 + .../src/appointment-bookings/client/core/index.ts | 2 ++ .../src/appointment-bookings/client/index.html | 4 +++- .../src/common/utils/__tests__/get-client-headers.ts | 4 +++- .../web-components/src/common/utils/fetcher-client.ts | 2 +- .../src/common/utils/get-client-headers.ts | 3 ++- packages/web-components/src/scripts/start-dev.js | 5 +++-- .../src/search-widget/client/api/__tests__/properties.ts | 7 ++++++- .../client/api/__tests__/property-images.ts | 4 ++-- .../src/search-widget/client/api/__tests__/property.ts | 3 ++- .../src/search-widget/client/api/properties.ts | 9 +++++---- .../src/search-widget/client/api/property-images.ts | 3 ++- .../src/search-widget/client/api/property.ts | 5 +++-- .../client/components/__tests__/search-widget.ts | 2 ++ .../client/components/property-detail.svelte | 4 +++- .../search-widget/client/components/search-form.svelte | 2 ++ .../search-widget/client/components/search-widget.svelte | 2 ++ .../client/core/__tests__/__snapshots__/index.ts.snap | 4 +++- .../src/search-widget/client/core/__tests__/app.ts | 1 + .../src/search-widget/client/core/__tests__/index.ts | 1 + .../src/search-widget/client/core/__tests__/store.ts | 1 + .../src/search-widget/client/core/app.svelte | 5 +++-- .../src/search-widget/client/core/index.ts | 4 +++- .../src/search-widget/client/core/store.ts | 1 + .../web-components/src/search-widget/client/index.html | 3 ++- .../client/components/viewing-booking.svelte | 2 ++ .../client/core/__tests__/__snapshots__/index.ts.snap | 6 ++++-- .../src/viewing-booking/client/core/__tests__/index.ts | 1 + .../src/viewing-booking/client/core/__tests__/store.ts | 1 + .../src/viewing-booking/client/core/index.ts | 9 ++++++++- .../src/viewing-booking/client/core/store.ts | 1 + .../web-components/src/viewing-booking/client/index.html | 3 ++- 35 files changed, 83 insertions(+), 29 deletions(-) diff --git a/packages/demo-site/config.example.json b/packages/demo-site/config.example.json index a91582428c..51aae992af 100644 --- a/packages/demo-site/config.example.json +++ b/packages/demo-site/config.example.json @@ -1,4 +1,5 @@ { "API_KEY": "", + "CUSTOMER_ID": "", "CDN_URL": "https://web-components.reapit.cloud" } diff --git a/packages/demo-site/index.ejs b/packages/demo-site/index.ejs index e1d2a81d53..c05e9d2380 100644 --- a/packages/demo-site/index.ejs +++ b/packages/demo-site/index.ejs @@ -4,7 +4,7 @@ - Web Components + DEMO