Skip to content

Commit

Permalink
[CLD-182] Fix install button display in developer detail modal (#73)
Browse files Browse the repository at this point in the history
* [CLD-182] Fix button install display in developer detail modal

* Fix review
  • Loading branch information
dannd4 authored Aug 14, 2019
1 parent ed60ec5 commit 3872910
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 85 deletions.
2 changes: 1 addition & 1 deletion src/actions/__tests__/app-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
7 changes: 6 additions & 1 deletion src/actions/app-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(ActionTypes.APP_DETAIL_REQUEST_DATA)
export interface AppDetailParams {
id: string
clientId?: string
}

export const appDetailRequestData = actionCreator<AppDetailParams>(ActionTypes.APP_DETAIL_REQUEST_DATA)
export const appDetailLoading = actionCreator<boolean>(ActionTypes.APP_DETAIL_LOADING)
export const appDetailReceiveData = actionCreator<AppDetailItem | undefined>(ActionTypes.APP_DETAIL_RECEIVE_DATA)
export const appDetailFailure = actionCreator<void>(ActionTypes.APP_DETAIL_REQUEST_DATA_FAILURE)
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/admin-approvals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
19 changes: 14 additions & 5 deletions src/components/pages/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClientProps> = ({ clientState, match, fetchAppDetail, appDetail }) => {
export const Client: React.FunctionComponent<ClientProps> = ({
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
Expand All @@ -44,7 +52,7 @@ export const Client: React.FunctionComponent<ClientProps> = ({ clientState, matc
onCardClick={app => {
setVisible(true)
if (app.id && (!appDetail.appDetailData || appDetail.appDetailData.data.id !== app.id)) {
fetchAppDetail(app.id)
fetchAppDetail(app.id, clientId)
}
}}
/>
Expand All @@ -56,11 +64,12 @@ export const Client: React.FunctionComponent<ClientProps> = ({ 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(
Expand Down
2 changes: 1 addition & 1 deletion src/components/pages/developer-home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 7 additions & 3 deletions src/components/pages/my-apps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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<MyAppsProps> = ({
myAppsState,
clientId,
match,
fetchMyApp,
fetchAppDetail,
Expand Down Expand Up @@ -67,7 +70,7 @@ export const MyApps: React.FunctionComponent<MyAppsProps> = ({
onCardClick={app => {
setVisible(true)
if (app.id && (!appDetail.appDetailData || appDetail.appDetailData.data.id !== app.id)) {
fetchAppDetail(app.id)
fetchAppDetail(app.id, clientId)
}
}}
/>
Expand All @@ -80,11 +83,12 @@ export const MyApps: React.FunctionComponent<MyAppsProps> = ({
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())
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
50 changes: 26 additions & 24 deletions src/components/ui/app-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export const AppDetail: React.FunctionComponent<AppDetailProps> = ({
setAppDetailModalStatePermission,
fetchAppPermission,
requestUninstall,
appUninstallFormState
appUninstallFormState,
isCurrentLoggedUserClient
}) => {
if (!data) {
return null
Expand Down Expand Up @@ -88,29 +89,30 @@ export const AppDetail: React.FunctionComponent<AppDetailProps> = ({
</p>
</div>
<p>{summary}</p>
{installedOn ? (
<Button
type="button"
variant="primary"
loading={Boolean(isLoadingUninstall)}
disabled={Boolean(isLoadingUninstall)}
onClick={requestUninstall}
>
Uninstall App
</Button>
) : (
<a
onClick={() => {
if (!id) {
return
}
fetchAppPermission(id)
setAppDetailModalStatePermission()
}}
>
Install App
</a>
)}
{isCurrentLoggedUserClient &&
(installedOn ? (
<Button
type="button"
variant="primary"
loading={Boolean(isLoadingUninstall)}
disabled={Boolean(isLoadingUninstall)}
onClick={requestUninstall}
>
Uninstall App
</Button>
) : (
<a
onClick={() => {
if (!id) {
return
}
fetchAppPermission(id)
setAppDetailModalStatePermission()
}}
>
Install App
</a>
))}
</div>
</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/constants/action-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 0 additions & 1 deletion src/sagas/__stubs__/app-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
75 changes: 40 additions & 35 deletions src/sagas/__tests__/app-detail.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
Expand All @@ -66,7 +71,7 @@ describe('app-detail thunks', () => {
it('should trigger request data when called', () => {
const gen = appDetailDataListen()
expect(gen.next().value).toEqual(
takeLatest<Action<String>>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch)
takeLatest<Action<AppDetailParams>>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch)
)
expect(gen.next().done).toBe(true)
})
Expand Down
15 changes: 5 additions & 10 deletions src/sagas/app-detail.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -39,7 +34,7 @@ export const appDetailDataFetch = function*({ data: id }) {
}

export const appDetailDataListen = function*() {
yield takeLatest<Action<String>>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch)
yield takeLatest<Action<AppDetailParams>>(ActionTypes.APP_DETAIL_REQUEST_DATA, appDetailDataFetch)
}

const appDetailSagas = function*() {
Expand Down
Loading

0 comments on commit 3872910

Please sign in to comment.