Skip to content

Commit

Permalink
client uninstall app (#69)
Browse files Browse the repository at this point in the history
* [CLD-27]: Install App

* Revert app-detail sagas: Remove clientId from select, Remove dev utility npm script

* Fix Code Review Comments

* Fix Code Review Comments

* Feature UI: Fix Review

* feature/cld-83-implement-client-uninstall-app
  • Loading branch information
dannd4 authored Aug 13, 2019
1 parent e78a7d1 commit 5bf10f2
Show file tree
Hide file tree
Showing 20 changed files with 426 additions and 59 deletions.
28 changes: 28 additions & 0 deletions src/actions/__tests__/app-uninstall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
appUninstallRequestSuccess,
appUninstallRequestDataFailure,
appUninstallRequestData,
appUninstallLoading,
appUninstallDone
} from '../app-uninstall'
import ActionTypes from '@/constants/action-types'

describe('app install actions', () => {
it('should create a appUninstallRequestData action', () => {
expect(appUninstallDone.type).toEqual(ActionTypes.APP_UNINSTALL_DONE)
})
it('should create a appUninstallRequestData action', () => {
expect(appUninstallRequestData.type).toEqual(ActionTypes.APP_UNINSTALL_REQUEST_DATA)
})

it('should create a appUninstallRequestData action', () => {
expect(appUninstallRequestSuccess.type).toEqual(ActionTypes.APP_UNINSTALL_REQUEST_DATA_SUCCESS)
})
it('should create a appUninstallRequestDataFailure action', () => {
expect(appUninstallRequestDataFailure.type).toEqual(ActionTypes.APP_UNINSTALL_REQUEST_DATA_FAILURE)
})

it('should create a appUninstallLoading action', () => {
expect(appUninstallLoading.type).toEqual(ActionTypes.APP_UNINSTALL_LOADING)
})
})
8 changes: 8 additions & 0 deletions src/actions/app-uninstall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { actionCreator } from '../utils/actions'
import ActionTypes from '../constants/action-types'

export const appUninstallRequestData = actionCreator<void>(ActionTypes.APP_UNINSTALL_REQUEST_DATA)
export const appUninstallLoading = actionCreator<void>(ActionTypes.APP_UNINSTALL_LOADING)
export const appUninstallRequestDataFailure = actionCreator<void>(ActionTypes.APP_UNINSTALL_REQUEST_DATA_FAILURE)
export const appUninstallRequestSuccess = actionCreator<void>(ActionTypes.APP_UNINSTALL_REQUEST_DATA_SUCCESS)
export const appUninstallDone = actionCreator<void>(ActionTypes.APP_UNINSTALL_DONE)
33 changes: 29 additions & 4 deletions src/components/pages/my-apps.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react'
import { connect } from 'react-redux'
import { withRouter, RouteComponentProps } from 'react-router'
import { ReduxState } from '@/types/core'
import { ReduxState, FormState } from '@/types/core'
import { MyAppsState } from '@/reducers/my-apps'
import Loader from '@/components/ui/loader'
import ErrorBoundary from '@/components/hocs/error-boundary'
Expand All @@ -12,25 +12,47 @@ import AppList from '@/components/ui/app-list'
import { appDetailRequestData } from '@/actions/app-detail'
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'

export interface MyAppsMappedActions {
fetchAppDetail: (id: string) => void
fetchMyApp: (page: number) => void
appUninstallDone: () => void
}

export interface MyAppsMappedProps {
myAppsState: MyAppsState
appDetail: AppDetailState
appUninstallFormState: FormState
}

export type MyAppsProps = MyAppsMappedActions & MyAppsMappedProps & RouteComponentProps<{ page?: any }>

export const MyApps: React.FunctionComponent<MyAppsProps> = ({ myAppsState, match, fetchAppDetail, appDetail }) => {
export const MyApps: React.FunctionComponent<MyAppsProps> = ({
myAppsState,
match,
fetchMyApp,
fetchAppDetail,
appDetail,
appUninstallDone,
appUninstallFormState
}) => {
const pageNumber = match.params && !isNaN(match.params.page) ? Number(match.params.page) : 1
const unfetched = !myAppsState.myAppsData
const loading = myAppsState.loading
const list = oc<MyAppsState>(myAppsState).myAppsData.data.data([])
const { totalCount, pageSize } = oc<MyAppsState>(myAppsState).myAppsData.data({})
const [visible, setVisible] = React.useState(false)
const isSuccessed = appUninstallFormState === 'SUCCESS'

React.useEffect(() => {
if (isSuccessed) {
setVisible(false)
appUninstallDone()
fetchMyApp(pageNumber)
}
}, [isSuccessed])

if (unfetched && loading) {
return <Loader />
Expand All @@ -57,11 +79,14 @@ export const MyApps: React.FunctionComponent<MyAppsProps> = ({ myAppsState, matc

const mapStateToProps = (state: ReduxState): MyAppsMappedProps => ({
myAppsState: state.myApps,
appDetail: state.appDetail
appDetail: state.appDetail,
appUninstallFormState: state.appUninstall.formState
})

const mapDispatchToProps = (dispatch: any): MyAppsMappedActions => ({
fetchAppDetail: (id: string) => dispatch(appDetailRequestData(id))
fetchAppDetail: (id: string) => dispatch(appDetailRequestData(id)),
fetchMyApp: (page: number) => dispatch(myAppsRequestData(page)),
appUninstallDone: () => dispatch(appUninstallDone())
})

export default withRouter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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
4 changes: 3 additions & 1 deletion src/components/ui/__tests__/app-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ const props: AppDetailProps = {
data: appDetailDataStub.data,
fetchAppPermission: jest.fn(),
setAppDetailModalStatePermission: jest.fn(),
isCurrentLoggedUserClient: true
requestUninstall: jest.fn(),
isCurrentLoggedUserClient: true,
appUninstallFormState: 'PENDING'
}

describe('AppDetailModalInner', () => {
Expand Down
50 changes: 25 additions & 25 deletions src/components/ui/__tests__/app-permission-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@ describe('AppDetailModal', () => {
}

expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
})
it('should match a snapshot when appInstallFormState = PENDING', () => {
const props: AppPermissionInnerProps = {
requestInstall: jest.fn(),
permissions: [{ description: 'test', name: 'tets' }],
appInstallFormState: 'PENDING'
}
}),
it('should match a snapshot when appInstallFormState = PENDING', () => {
const props: AppPermissionInnerProps = {
requestInstall: jest.fn(),
permissions: [{ description: 'test', name: 'tets' }],
appInstallFormState: 'PENDING'
}

expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
})
it('should match a snapshot when appInstallFormState = SUBMITTING', () => {
const props: AppPermissionInnerProps = {
requestInstall: jest.fn(),
permissions: [{ description: 'test', name: 'tets' }],
appInstallFormState: 'SUBMITTING'
}
expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
}),
it('should match a snapshot when appInstallFormState = SUBMITTING', () => {
const props: AppPermissionInnerProps = {
requestInstall: jest.fn(),
permissions: [{ description: 'test', name: 'tets' }],
appInstallFormState: 'SUBMITTING'
}

expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
})
it('should match a snapshot when appInstallFormState = SUCCESS', () => {
const props: AppPermissionInnerProps = {
requestInstall: jest.fn(),
permissions: [{ description: 'test', name: 'tets' }],
appInstallFormState: 'SUCCESS'
}
expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
}),
it('should match a snapshot when appInstallFormState = SUCCESS', () => {
const props: AppPermissionInnerProps = {
requestInstall: jest.fn(),
permissions: [{ description: 'test', name: 'tets' }],
appInstallFormState: 'SUCCESS'
}

expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
})
expect(toJson(shallow(<AppPermissionContent {...props} />))).toMatchSnapshot()
})
})
30 changes: 25 additions & 5 deletions src/components/ui/app-detail.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import bulma from '@/styles/vendor/bulma'
import { ReduxState } from '@/types/core'
import { ReduxState, FormState } from '@/types/core'
import Slider, { Settings } from 'react-slick'
import carouselStyles from '../../styles/elements/carousel.scss?mod'
import ChevronLeftIcon from '@/components/svg/chevron-left'
Expand All @@ -10,6 +10,8 @@ import { setAppDetailModalStatePermission } from '@/actions/app-detail-modal'
import { appPermissionRequestData } from '@/actions/app-permission'
import { AppDetailModel } from '@/types/marketplace-api-schema'
import { withRouter } from 'react-router'
import Button from '../form/button'
import { appUninstallRequestData } from '@/actions/app-uninstall'

const { useState } = React

Expand All @@ -19,11 +21,13 @@ export interface AppDetailModalInnerProps {

export interface AppDetailModalMappedProps {
isCurrentLoggedUserClient: boolean
appUninstallFormState: FormState
}

export interface AppDetailModalMappedActions {
setAppDetailModalStatePermission: () => void
fetchAppPermission: (id: string) => void
requestUninstall: () => void
}

export type AppDetailProps = AppDetailModalMappedActions & AppDetailModalMappedProps & AppDetailModalInnerProps
Expand All @@ -35,7 +39,9 @@ const SlickButtonNav = ({ currentSlide, setAppDetailModalStatePermission, slideC
export const AppDetail: React.FunctionComponent<AppDetailProps> = ({
data,
setAppDetailModalStatePermission,
fetchAppPermission
fetchAppPermission,
requestUninstall,
appUninstallFormState
}) => {
if (!data) {
return null
Expand All @@ -45,6 +51,8 @@ export const AppDetail: React.FunctionComponent<AppDetailProps> = ({
const icon = media.filter(({ type }) => type === 'icon')[0]
const carouselImages = media.filter(({ type }) => type === 'image')

const isLoadingUninstall = appUninstallFormState === 'SUBMITTING'

const settings: Settings = {
dots: false,
speed: 500,
Expand Down Expand Up @@ -80,7 +88,17 @@ export const AppDetail: React.FunctionComponent<AppDetailProps> = ({
</p>
</div>
<p>{summary}</p>
{!installedOn && (
{installedOn ? (
<Button
type="button"
variant="primary"
loading={Boolean(isLoadingUninstall)}
disabled={Boolean(isLoadingUninstall)}
onClick={requestUninstall}
>
Uninstall App
</Button>
) : (
<a
onClick={() => {
if (!id) {
Expand Down Expand Up @@ -115,13 +133,15 @@ export const AppDetail: React.FunctionComponent<AppDetailProps> = ({

const mapStateToProps = (state: ReduxState): AppDetailModalMappedProps => {
return {
isCurrentLoggedUserClient: state.auth.loginType === 'CLIENT'
isCurrentLoggedUserClient: state.auth.loginType === 'CLIENT',
appUninstallFormState: state.appUninstall.formState
}
}

const mapDispatchToProps = (dispatch: any): AppDetailModalMappedActions => ({
setAppDetailModalStatePermission: () => dispatch(setAppDetailModalStatePermission()),
fetchAppPermission: appId => dispatch(appPermissionRequestData(appId))
fetchAppPermission: appId => dispatch(appPermissionRequestData(appId)),
requestUninstall: () => dispatch(appUninstallRequestData())
})

const AppDetailWithConnect = connect(
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 @@ -9,6 +9,13 @@ const ActionTypes = {
APP_INSTALL_REQUEST_DATA_FAILURE: 'APP_INSTALL_REQUEST_DATA_FAILURE',
APP_INSTALL_DONE: 'APP_INSTALL_DONE',

// Uninstall
APP_UNINSTALL_REQUEST_DATA: 'APP_UNINSTALL_REQUEST_DATA',
APP_UNINSTALL_LOADING: 'APP_UNINSTALL_LOADING',
APP_UNINSTALL_REQUEST_DATA_SUCCESS: 'APP_UNINSTALL_REQUEST_DATA_SUCCESS',
APP_UNINSTALL_REQUEST_DATA_FAILURE: 'APP_UNINSTALL_REQUEST_DATA_FAILURE',
APP_UNINSTALL_DONE: 'APP_UNINSTALL_DONE',

// Auth actions
AUTH_LOGIN: 'AUTH_LOGIN',
AUTH_LOGIN_SUCCESS: 'AUTH_LOGIN_SUCCESS',
Expand Down
8 changes: 6 additions & 2 deletions src/core/store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createStore, applyMiddleware, compose, combineReducers, Store as ReduxStore, Dispatch } from 'redux'
import auth from '../reducers/auth'
import appInstall from '../reducers/app-install'
import appUninstall from '../reducers/app-uninstall'
import client from '../reducers/client'
import myApps from '../reducers/my-apps'
import developer from '../reducers/developer'
Expand Down Expand Up @@ -28,6 +29,7 @@ import adminApprovalSagas from '../sagas/admin-approvals'
import revisionDetailSagas from '../sagas/revision-detail'
import appPermissionSagas from '../sagas/app-permission'
import appInstallSagas from '../sagas/app-install'
import appUninstallSagas from '../sagas/app-uninstall'

export class Store {
static _instance: Store
Expand Down Expand Up @@ -58,7 +60,8 @@ export class Store {
revisionDetail,
appPermission,
appDetailModal,
appInstall
appInstall,
appUninstall
})

static sagas = function*() {
Expand All @@ -74,7 +77,8 @@ export class Store {
fork(adminApprovalSagas),
fork(revisionDetailSagas),
fork(appPermissionSagas),
fork(appInstallSagas)
fork(appInstallSagas),
fork(appUninstallSagas)
])
}

Expand Down
50 changes: 50 additions & 0 deletions src/reducers/__tests__/app-uninstall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import appUninstallReducer, { defaultState, AppUninstallState } from '../app-uninstall'
import { ActionType } from '../../types/core'
import ActionTypes from '../../constants/action-types'

const testData: Record<string, AppUninstallState> = {
loading: { formState: 'SUBMITTING' }
}

describe('app-uninstall reducer', () => {
it('should return fomState = PENDING if action not matched', () => {
const newState = appUninstallReducer(undefined, { type: 'UNKNOWN' as ActionType, data: undefined })
expect(newState).toEqual(defaultState)
})

it('should return formState = "SUBMITTING" when APP_UNINSTALL_LOADING action is called', () => {
const newState = appUninstallReducer(defaultState, {
type: ActionTypes.APP_UNINSTALL_LOADING as ActionType,
data: true
})
expect(newState).toEqual(testData.loading)
})

it('should set formState = "SUCCESS" data when APP_UNINSTALL_REQUEST_DATA_SUCCESS action is called', () => {
const newState = appUninstallReducer(
{ formState: 'SUCCESS' },
{
type: ActionTypes.APP_UNINSTALL_REQUEST_DATA_SUCCESS as ActionType,
data: undefined
}
)
const expected = {
...defaultState,
formState: 'SUCCESS'
}
expect(newState).toEqual(expected)
})

it('should set formState="ERROR" when ', () => {
const newState = appUninstallReducer(testData.loading, {
type: ActionTypes.APP_UNINSTALL_REQUEST_DATA_FAILURE as ActionType,
data: undefined
})
const expected = {
...defaultState,
formState: 'ERROR'
}

expect(newState).toEqual(expected)
})
})
Loading

0 comments on commit 5bf10f2

Please sign in to comment.