From 38729109f10c5c3f263227363c37aee6f6f0ff57 Mon Sep 17 00:00:00 2001 From: Dan Nguyen <45675930+DanND96@users.noreply.github.com> Date: Wed, 14 Aug 2019 17:33:34 +0700 Subject: [PATCH] [CLD-182] Fix install button display in developer detail modal (#73) * [CLD-182] Fix button install display in developer detail modal * Fix review --- src/actions/__tests__/app-detail.ts | 2 +- src/actions/app-detail.ts | 7 +- src/components/pages/admin-approvals.tsx | 4 +- src/components/pages/client.tsx | 19 +++-- src/components/pages/developer-home.tsx | 2 +- src/components/pages/my-apps.tsx | 10 ++- .../developer-app-modal.tsx.snap | 1 - src/components/ui/app-detail.tsx | 50 +++++++------ src/constants/action-types.ts | 7 ++ src/sagas/__stubs__/app-detail.ts | 1 - src/sagas/__tests__/app-detail.ts | 75 ++++++++++--------- src/sagas/app-detail.ts | 15 ++-- src/selector/client.ts | 2 +- 13 files changed, 110 insertions(+), 85 deletions(-) diff --git a/src/actions/__tests__/app-detail.ts b/src/actions/__tests__/app-detail.ts index 43129d43d4..bf8c3087e0 100644 --- a/src/actions/__tests__/app-detail.ts +++ b/src/actions/__tests__/app-detail.ts @@ -21,7 +21,7 @@ describe('appDetail actions', () => { it('should create a appDetailRequestData action', () => { expect(appDetailRequestData.type).toEqual(ActionTypes.APP_DETAIL_REQUEST_DATA) - expect(appDetailRequestData('xxxx').data).toEqual('xxxx') + expect(appDetailRequestData({ id: '1', clientId: '1' }).data).toEqual({ id: '1', clientId: '1' }) }) it('should create a appDetailClearData action', () => { diff --git a/src/actions/app-detail.ts b/src/actions/app-detail.ts index 80f365a3d7..dbd00a7bc6 100644 --- a/src/actions/app-detail.ts +++ b/src/actions/app-detail.ts @@ -2,7 +2,12 @@ import { actionCreator } from '../utils/actions' import ActionTypes from '../constants/action-types' import { AppDetailItem } from '../reducers/app-detail' -export const appDetailRequestData = actionCreator(ActionTypes.APP_DETAIL_REQUEST_DATA) +export interface AppDetailParams { + id: string + clientId?: string +} + +export const appDetailRequestData = actionCreator(ActionTypes.APP_DETAIL_REQUEST_DATA) export const appDetailLoading = actionCreator(ActionTypes.APP_DETAIL_LOADING) export const appDetailReceiveData = actionCreator(ActionTypes.APP_DETAIL_RECEIVE_DATA) export const appDetailFailure = actionCreator(ActionTypes.APP_DETAIL_REQUEST_DATA_FAILURE) diff --git a/src/components/pages/admin-approvals.tsx b/src/components/pages/admin-approvals.tsx index a87f952723..68ba0a7a54 100644 --- a/src/components/pages/admin-approvals.tsx +++ b/src/components/pages/admin-approvals.tsx @@ -19,7 +19,7 @@ import Modal from '../ui/modal' export interface AdminApprovalsMappedActions { fetchRevisionDetail: (params: RevisionDetailRequestParams) => void - fetchAppDetail: (appId: string) => void + fetchAppDetail: (id: string) => void } export interface AdminApprovalsMappedProps { @@ -124,7 +124,7 @@ const mapStateToProps = (state: ReduxState): AdminApprovalsMappedProps => ({ const mapDispatchToProps = (dispatch: any): AdminApprovalsMappedActions => ({ fetchRevisionDetail: param => dispatch(revisionDetailRequestData(param)), - fetchAppDetail: (appId: string) => dispatch(appDetailRequestData(appId)) + fetchAppDetail: (id: string) => dispatch(appDetailRequestData({ id })) }) export default withRouter( diff --git a/src/components/pages/client.tsx b/src/components/pages/client.tsx index ac0b261c78..64dfef47b2 100644 --- a/src/components/pages/client.tsx +++ b/src/components/pages/client.tsx @@ -12,19 +12,27 @@ import routes from '@/constants/routes' import { appDetailRequestData } from '@/actions/app-detail' import { AppDetailState } from '@/reducers/app-detail' import AppDetailModal from '@/components/ui/app-detail-modal' +import { selectClientId } from '@/selector/client' export interface ClientMappedActions { - fetchAppDetail: (id: string) => void + fetchAppDetail: (id: string, clientId: string) => void } export interface ClientMappedProps { clientState: ClientState appDetail: AppDetailState + clientId: string } export type ClientProps = ClientMappedActions & ClientMappedProps & RouteComponentProps<{ page?: any }> -export const Client: React.FunctionComponent = ({ clientState, match, fetchAppDetail, appDetail }) => { +export const Client: React.FunctionComponent = ({ + clientState, + match, + fetchAppDetail, + appDetail, + clientId +}) => { const pageNumber = match.params && !isNaN(match.params.page) ? Number(match.params.page) : 1 const unfetched = !clientState.clientData const loading = clientState.loading @@ -44,7 +52,7 @@ export const Client: React.FunctionComponent = ({ clientState, matc onCardClick={app => { setVisible(true) if (app.id && (!appDetail.appDetailData || appDetail.appDetailData.data.id !== app.id)) { - fetchAppDetail(app.id) + fetchAppDetail(app.id, clientId) } }} /> @@ -56,11 +64,12 @@ export const Client: React.FunctionComponent = ({ clientState, matc const mapStateToProps = (state: ReduxState): ClientMappedProps => ({ clientState: state.client, - appDetail: state.appDetail + appDetail: state.appDetail, + clientId: selectClientId(state) }) const mapDispatchToProps = (dispatch: any): ClientMappedActions => ({ - fetchAppDetail: (id: string) => dispatch(appDetailRequestData(id)) + fetchAppDetail: (id: string, clientId: string) => dispatch(appDetailRequestData({ id, clientId })) }) export default withRouter( diff --git a/src/components/pages/developer-home.tsx b/src/components/pages/developer-home.tsx index 8f00a13745..cbcb415325 100644 --- a/src/components/pages/developer-home.tsx +++ b/src/components/pages/developer-home.tsx @@ -73,7 +73,7 @@ const mapStateToProps = (state: ReduxState): DeveloperMappedProps => ({ }) const mapDispatchToProps = (dispatch: any): DeveloperMappedActions => ({ - fetchAppDetail: (id: string) => dispatch(appDetailRequestData(id)) + fetchAppDetail: (id: string) => dispatch(appDetailRequestData({ id })) }) export default withRouter( diff --git a/src/components/pages/my-apps.tsx b/src/components/pages/my-apps.tsx index 5ea161477e..557899174e 100644 --- a/src/components/pages/my-apps.tsx +++ b/src/components/pages/my-apps.tsx @@ -14,9 +14,10 @@ import { AppDetailState } from '@/reducers/app-detail' import AppDetailModal from '../ui/app-detail-modal' import { myAppsRequestData } from '@/actions/my-apps' import { appUninstallDone } from '@/actions/app-uninstall' +import { selectClientId } from '@/selector/client' export interface MyAppsMappedActions { - fetchAppDetail: (id: string) => void + fetchAppDetail: (id: string, clientId: string) => void fetchMyApp: (page: number) => void appUninstallDone: () => void } @@ -25,12 +26,14 @@ export interface MyAppsMappedProps { myAppsState: MyAppsState appDetail: AppDetailState appUninstallFormState: FormState + clientId: string } export type MyAppsProps = MyAppsMappedActions & MyAppsMappedProps & RouteComponentProps<{ page?: any }> export const MyApps: React.FunctionComponent = ({ myAppsState, + clientId, match, fetchMyApp, fetchAppDetail, @@ -67,7 +70,7 @@ export const MyApps: React.FunctionComponent = ({ onCardClick={app => { setVisible(true) if (app.id && (!appDetail.appDetailData || appDetail.appDetailData.data.id !== app.id)) { - fetchAppDetail(app.id) + fetchAppDetail(app.id, clientId) } }} /> @@ -80,11 +83,12 @@ export const MyApps: React.FunctionComponent = ({ const mapStateToProps = (state: ReduxState): MyAppsMappedProps => ({ myAppsState: state.myApps, appDetail: state.appDetail, + clientId: selectClientId(state), appUninstallFormState: state.appUninstall.formState }) const mapDispatchToProps = (dispatch: any): MyAppsMappedActions => ({ - fetchAppDetail: (id: string) => dispatch(appDetailRequestData(id)), + fetchAppDetail: (id: string, clientId: string) => dispatch(appDetailRequestData({ id, clientId })), fetchMyApp: (page: number) => dispatch(myAppsRequestData(page)), appUninstallDone: () => dispatch(appUninstallDone()) }) diff --git a/src/components/ui/__tests__/__snapshots__/developer-app-modal.tsx.snap b/src/components/ui/__tests__/__snapshots__/developer-app-modal.tsx.snap index 96c56e4eed..f8d3f40dad 100644 --- a/src/components/ui/__tests__/__snapshots__/developer-app-modal.tsx.snap +++ b/src/components/ui/__tests__/__snapshots__/developer-app-modal.tsx.snap @@ -19,7 +19,6 @@ exports[`DeveloperAppModalInner should match a snapshot when LOADING false 1`] = "developer": "Pete's Proptech World Ltd", "homePage": "http://myawesomeproptechproduct.io", "id": "9b6fd5f7-2c15-483d-b925-01b650538e52", - "installationId": "7b2517b3-aad3-4ad8-b31c-988a4b3d112d", "media": Array [ Object { "description": "Application Icon", diff --git a/src/components/ui/app-detail.tsx b/src/components/ui/app-detail.tsx index 4fef12e7be..d85075e1e1 100644 --- a/src/components/ui/app-detail.tsx +++ b/src/components/ui/app-detail.tsx @@ -41,7 +41,8 @@ export const AppDetail: React.FunctionComponent = ({ setAppDetailModalStatePermission, fetchAppPermission, requestUninstall, - appUninstallFormState + appUninstallFormState, + isCurrentLoggedUserClient }) => { if (!data) { return null @@ -88,29 +89,30 @@ export const AppDetail: React.FunctionComponent = ({

{summary}

- {installedOn ? ( - - ) : ( - { - if (!id) { - return - } - fetchAppPermission(id) - setAppDetailModalStatePermission() - }} - > - Install App - - )} + {isCurrentLoggedUserClient && + (installedOn ? ( + + ) : ( + { + if (!id) { + return + } + fetchAppPermission(id) + setAppDetailModalStatePermission() + }} + > + Install App + + ))} diff --git a/src/constants/action-types.ts b/src/constants/action-types.ts index fe3b1a5755..92ee1b6b27 100644 --- a/src/constants/action-types.ts +++ b/src/constants/action-types.ts @@ -61,6 +61,13 @@ const ActionTypes = { APP_DETAIL_RECEIVE_DATA: 'APP_DETAIL_RECEIVE_DATA', APP_DETAIL_CLEAR_DATA: 'APP_DETAIL_CLEAR_DATA', + // Client App Detail actions + CLIENT_APP_DETAIL_REQUEST_DATA: 'CLIENT_APP_DETAIL_REQUEST_DATA', + CLIENT_APP_DETAIL_LOADING: 'CLIENT_APP_DETAIL_LOADING', + CLIENT_APP_DETAIL_REQUEST_DATA_FAILURE: 'CLIENT_APP_DETAIL_REQUEST_DATA_FAILURE', + CLIENT_APP_DETAIL_RECEIVE_DATA: 'CLIENT_APP_DETAIL_RECEIVE_DATA', + CLIENT_APP_DETAIL_CLEAR_DATA: 'CLIENT_APP_DETAIL_CLEAR_DATA', + // App Permission actions APP_PERMISION_REQUEST_DATA: 'APP_PERMISION_REQUEST_DATA', APP_PERMISION_REQUEST_DATA_FAILURE: 'APP_PERMISION_REQUEST_DATA_FAILURE', diff --git a/src/sagas/__stubs__/app-detail.ts b/src/sagas/__stubs__/app-detail.ts index 90d5f09642..18ed27829e 100644 --- a/src/sagas/__stubs__/app-detail.ts +++ b/src/sagas/__stubs__/app-detail.ts @@ -3,7 +3,6 @@ import { AppDetailItem } from '@/reducers/app-detail' export const appDetailDataStub: AppDetailItem = { data: { id: '9b6fd5f7-2c15-483d-b925-01b650538e52', - installationId: '7b2517b3-aad3-4ad8-b31c-988a4b3d112d', name: "Peter's Properties", summary: 'vitae elementum curabitur vitae nunc sed velit eget gravida cum sociis natoque!!', description: diff --git a/src/sagas/__tests__/app-detail.ts b/src/sagas/__tests__/app-detail.ts index f6e295b328..f3bbe567a2 100644 --- a/src/sagas/__tests__/app-detail.ts +++ b/src/sagas/__tests__/app-detail.ts @@ -1,62 +1,67 @@ import appDetailSagas, { appDetailDataFetch, appDetailDataListen } from '../app-detail' import { appDetailDataStub } from '../__stubs__/app-detail' import ActionTypes from '@/constants/action-types' -import { put, takeLatest, all, fork, call, select } from '@redux-saga/core/effects' -import { appDetailLoading, appDetailReceiveData, appDetailFailure } from '@/actions/app-detail' +import { put, takeLatest, all, fork, call } from '@redux-saga/core/effects' +import { appDetailLoading, appDetailReceiveData, appDetailFailure, AppDetailParams } from '@/actions/app-detail' import { Action } from '@/types/core' import fetcher from '@/utils/fetcher' import { URLS, MARKETPLACE_HEADERS } from '@/constants/api' import { cloneableGenerator } from '@redux-saga/testing-utils' import { REAPIT_API_BASE_URL } from '../../constants/api' -import { selectClientId } from '@/selector/client' -import { errorThrownServer } from '@/actions/error' -import errorMessages from '@/constants/error-messages' jest.mock('../../utils/fetcher') -const params = { data: '9b6fd5f7-2c15-483d-b925-01b650538e52' } +const paramsClientId = { data: { id: '9b6fd5f7-2c15-483d-b925-01b650538e52', clientId: 'DAC' } } +const params = { data: { id: '9b6fd5f7-2c15-483d-b925-01b650538e52' } } -describe('app-detail fetch data', () => { - const gen = cloneableGenerator(appDetailDataFetch)(params) +describe('app-detail fetch data with clientId', () => { + const gen = cloneableGenerator(appDetailDataFetch)(paramsClientId) expect(gen.next().value).toEqual(put(appDetailLoading(true))) - expect(gen.next('1').value).toEqual(select(selectClientId)) - test('clientId not exist', () => { + expect(gen.next().value).toEqual( + call(fetcher, { + url: `${URLS.apps}/${paramsClientId.data.id}?clientId=${paramsClientId.data.clientId}`, + api: REAPIT_API_BASE_URL, + method: 'GET', + headers: MARKETPLACE_HEADERS + }) + ) + + test('api call success', () => { const clone = gen.clone() - expect(clone.next().value).toEqual( - put( - errorThrownServer({ - type: 'SERVER', - message: errorMessages.DEFAULT_SERVER_ERROR - }) - ) - ) + expect(clone.next(appDetailDataStub.data).value).toEqual(put(appDetailReceiveData(appDetailDataStub))) + expect(clone.next().done).toBe(true) }) + test('api call fail', () => { + const clone = gen.clone() + expect(clone.next().value).toEqual(put(appDetailFailure())) + expect(clone.next().done).toBe(true) + }) +}) + +describe('app-detail fetch data without clientId', () => { + const gen = cloneableGenerator(appDetailDataFetch)(params) + expect(gen.next().value).toEqual(put(appDetailLoading(true))) + + expect(gen.next().value).toEqual( + call(fetcher, { + url: `${URLS.apps}/${params.data.id}`, + api: REAPIT_API_BASE_URL, + method: 'GET', + headers: MARKETPLACE_HEADERS + }) + ) + test('api call success', () => { const clone = gen.clone() - expect(clone.next(1).value).toEqual( - call(fetcher, { - url: `${URLS.apps}/${params.data}?clientId=1`, - api: REAPIT_API_BASE_URL, - method: 'GET', - headers: MARKETPLACE_HEADERS - }) - ) expect(clone.next(appDetailDataStub.data).value).toEqual(put(appDetailReceiveData(appDetailDataStub))) expect(clone.next().done).toBe(true) }) test('api call fail', () => { const clone = gen.clone() - expect(clone.next(undefined).value).toEqual( - put( - errorThrownServer({ - type: 'SERVER', - message: errorMessages.DEFAULT_SERVER_ERROR - }) - ) - ) + expect(clone.next().value).toEqual(put(appDetailFailure())) expect(clone.next().done).toBe(true) }) }) @@ -66,7 +71,7 @@ describe('app-detail thunks', () => { it('should trigger request data when called', () => { const gen = appDetailDataListen() expect(gen.next().value).toEqual( - takeLatest>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch) + takeLatest>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch) ) expect(gen.next().done).toBe(true) }) diff --git a/src/sagas/app-detail.ts b/src/sagas/app-detail.ts index a51d35738f..f1f7c40914 100644 --- a/src/sagas/app-detail.ts +++ b/src/sagas/app-detail.ts @@ -1,23 +1,18 @@ import fetcher from '../utils/fetcher' import { URLS, MARKETPLACE_HEADERS, REAPIT_API_BASE_URL } from '../constants/api' -import { appDetailLoading, appDetailReceiveData, appDetailFailure } from '../actions/app-detail' +import { appDetailLoading, appDetailReceiveData, appDetailFailure, AppDetailParams } from '../actions/app-detail' import { put, call, fork, takeLatest, all, select } from '@redux-saga/core/effects' import ActionTypes from '../constants/action-types' import { errorThrownServer } from '../actions/error' import errorMessages from '../constants/error-messages' import { Action } from '@/types/core' -import { selectClientId } from '@/selector/client' -export const appDetailDataFetch = function*({ data: id }) { +export const appDetailDataFetch = function*({ data }) { + const { id, clientId } = data yield put(appDetailLoading(true)) try { - const clientId = yield select(selectClientId) - - if (!clientId) { - throw new Error('Client id does not exist in state') - } const response = yield call(fetcher, { - url: `${URLS.apps}/${id}?clientId=${clientId}`, + url: clientId ? `${URLS.apps}/${id}?clientId=${clientId}` : `${URLS.apps}/${id}`, api: REAPIT_API_BASE_URL, method: 'GET', headers: MARKETPLACE_HEADERS @@ -39,7 +34,7 @@ export const appDetailDataFetch = function*({ data: id }) { } export const appDetailDataListen = function*() { - yield takeLatest>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch) + yield takeLatest>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch) } const appDetailSagas = function*() { diff --git a/src/selector/client.ts b/src/selector/client.ts index e686705c79..f414f3c396 100644 --- a/src/selector/client.ts +++ b/src/selector/client.ts @@ -2,7 +2,7 @@ import { ReduxState } from '@/types/core' import { oc } from 'ts-optchain' export const selectClientId = (state: ReduxState) => { - return oc(state.auth.loginSession).loginIdentity.clientId() + return oc(state.auth.loginSession).loginIdentity.clientId('') } export const selectLoggedUserEmail = (state: ReduxState) => {