Skip to content

Commit

Permalink
feat: manual install
Browse files Browse the repository at this point in the history
  • Loading branch information
mediremi committed Feb 17, 2021
1 parent e6a01e6 commit 7836858
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 211 deletions.
39 changes: 22 additions & 17 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2021-02-17T11:58:52.975Z\n"
"PO-Revision-Date: 2021-02-17T11:58:52.975Z\n"

msgid "App installed successfully"
msgstr ""

msgid "Failed to install app"
msgstr ""

msgid "App removed successfully"
msgstr ""

msgid "Installing app from the app hub..."
msgstr ""

msgid "Failed to install an app from the app hub"
msgstr ""
"POT-Creation-Date: 2021-02-17T13:54:07.268Z\n"
"PO-Revision-Date: 2021-02-17T13:54:07.268Z\n"

msgid "No apps found"
msgstr ""
Expand Down Expand Up @@ -71,6 +56,26 @@ msgstr ""
msgid "Search installed custom apps"
msgstr ""

msgid "App installed successfully"
msgstr ""

msgid "Failed to install app: {{errorMessage}}"
msgstr ""

msgid "Uploading..."
msgstr ""

msgid "Upload an app to install"
msgstr ""

msgid "Manually install an app"
msgstr ""

msgid ""
"Installed apps have access to your DHIS2 data and metadata. Make sure you "
"trust an app before installing it."
msgstr ""

msgid "Preinstalled core apps"
msgstr ""

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
"dependencies": {
"@dhis2/app-runtime": "^2.7.0",
"@dhis2/prop-types": "^2.0.3",
"d2": "^31.8.2",
"moment": "2.24",
"query-string": "^6.14.0",
"react-router-dom": "^5.2.0",
Expand Down
149 changes: 2 additions & 147 deletions src/actions.js
Original file line number Diff line number Diff line change
@@ -1,150 +1,7 @@
import { getInstance as getD2 } from 'd2'

const actions = {}

/*
* Install app from zip file
*/
actions.installApp.subscribe(params => {
const [zipFile, progressCallback] = Array.isArray(params.data)
? params.data
: [params.data, undefined]

getD2().then(d2 => {
d2.system
.uploadApp(zipFile, progressCallback)
.then(() => d2.system.reloadApps())
.then(apps => {
installedAppHub.setState(apps)

actions.showSnackbarMessage(
i18n.t('App installed successfully')
)
actions.appInstalled(
zipFile.name.substring(0, zipFile.name.lastIndexOf('.'))
)
actions.refreshApps()
params.complete()
})
.catch(err => {
let message = i18n.t('Failed to install app')
if (err.message) {
message += `: ${err.message}`
}
actions.showSnackbarMessage(message)
log.error('Failed to install app:', err.message || err)
actions.refreshApps()
})
})
})

/*
* Uninstall app
*/
actions.uninstallApp.subscribe(params => {
const appKey = params.data[0]
getD2().then(d2 => {
d2.system.uninstallApp(appKey).then(() => {
actions.showSnackbarMessage(i18n.t('App removed successfully'))
actions.refreshApps()
})
})
})

/*
* Refresh the list of installed apps
*/
actions.refreshApps.subscribe(() => {
getD2().then(d2 => {
d2.system.reloadApps().then(apps => {
installedAppHub.setState(apps)
})
})
})

/*
* Load the app hub
*/
actions.loadAppHub.subscribe(async () => {
const d2 = await getD2()
const baseUrl = d2.Api.getApi().baseUrl

const fetchOptions = {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
}

const getDhisVersion = async () => {
const response = await fetch(`${baseUrl}/system/info`, fetchOptions)
const json = await response.json()
//if we're running a dev version remove the snapshot suffix to just keep the dhis version
return json.version.replace('-SNAPSHOT', '')
}

const version = await getDhisVersion()

const response = await fetch(
`${baseUrl}/appHub/v1/apps?dhis_version=${version}`,
fetchOptions
)

const apps = await response.json()
appHubStore.setState(Object.assign(appHubStore.getState() || {}, { apps }))
})

/*
* Install app version from the app hub
*/
actions.installAppVersion.subscribe(params => {
const versionId = params.data[0]
const appHubState = appHubStore.getState()
appHubStore.setState(
Object.assign(appHubState, {
installing: appHubState.installing ? appHubState.installing + 1 : 1,
})
)

getD2().then(d2 => {
actions.showSnackbarMessage(
i18n.t('Installing app from the app hub...')
)
installAppVersion(versionId, d2)
.then(() => d2.system.reloadApps())
.then(apps => {
actions.showSnackbarMessage(
i18n.t('App installed successfully')
)
const appHubState2 = appHubStore.getState()
appHubStore.setState(
Object.assign(appHubState2, {
installing: appHubState2.installing - 1,
})
)
installedAppHub.setState(apps)
params.complete(apps)
})
.catch(err => {
actions.showSnackbarMessage(
`${i18n.t('Failed to install an app from the app hub')}: ${
err.message
}`
)
appHubStore.setState(
Object.assign(appHubStore.getState(), {
installing: appHubStore.getState().installing - 1,
})
)
log.error(err)
})
})
})

function installAppVersion(uid, d2) {
function installAppVersion(versionId, d2) {
const api = d2.Api.getApi()
return new Promise((resolve, reject) => {
api.post(['appHub', uid].join('/'), '', { dataType: 'text' })
api.post(['appHub', versionId].join('/'), '', { dataType: 'text' })
.then(() => {
resolve()
})
Expand All @@ -153,5 +10,3 @@ function installAppVersion(uid, d2) {
})
})
}

export default actions
7 changes: 7 additions & 0 deletions src/components/AppList/AppList.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
.header {
font-size: 20px;
font-weight: 500;
margin-top: var(--spacers-dp24);
margin-bottom: var(--spacers-dp16);
}

.searchField {
max-width: 420px;
}
Expand Down
7 changes: 3 additions & 4 deletions src/components/AppList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import React from 'react'
import { useHistory } from 'react-router-dom'
import { useQueryParam, StringParam, withDefault } from 'use-query-params'
import commonStyles from '../common.module.css'
import AppIcon from './AppIcon'
import styles from './AppList.module.css'

Expand Down Expand Up @@ -56,7 +55,7 @@ const AppsWithUpdates = ({ label, apps }) => {
}
return (
<div className={styles.appsWithUpdates}>
<h1 className={commonStyles.h1}>{label}</h1>
<h1 className={styles.header}>{label}</h1>
<AppCards apps={apps} />
</div>
)
Expand All @@ -71,14 +70,14 @@ const AllApps = ({ label, apps }) => {
if (apps.length === 0) {
return (
<>
<h1 className={commonStyles.h1}>{i18n.t('No apps found')}</h1>
<h1 className={styles.header}>{i18n.t('No apps found')}</h1>
<p>No apps match your criteria</p>
</>
)
}
return (
<>
<h1 className={commonStyles.h1}>{label}</h1>
<h1 className={styles.header}>{label}</h1>
<AppCards apps={apps} />
</>
)
Expand Down
19 changes: 19 additions & 0 deletions src/components/ManualInstall/ManualInstall.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.header {
font-size: 20px;
font-weight: 500;
margin-top: 0;
margin-bottom: var(--spacers-dp16);
}

.warning {
font-size: 16px;
color: var(--colors-grey700);
}

.uploadBtn {
margin-top: var(--spacers-dp16);
}

.hiddenForm {
display: none;
}
86 changes: 84 additions & 2 deletions src/components/ManualInstall/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,87 @@
import React from 'react'
import { useAlert, useConfig } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { Button } from '@dhis2/ui'
import React, { useState, useRef } from 'react'
import styles from './ManualInstall.module.css'

const ManualInstall = () => null
const uploadApp = (baseUrl, file) => {
const data = new FormData()
data.append('file', file)
return fetch(`${baseUrl}/api/apps`, {
method: 'post',
body: data,
credentials: 'include',
}).then(res => {
if (status >= 300) {
throw new Error(res.statusText)
}
})
}

const UploadButton = () => {
const { baseUrl } = useConfig()
const [isUploading, setIsUploading] = useState(false)
const successAlert = useAlert(i18n.t('App installed successfully'), {
success: true,
})
const errorAlert = useAlert(
({ error }) =>
i18n.t('Failed to install app: {{errorMessage}}', {
errorMessage: error.message,
nsSeparator: '|',
}),
{ critical: true }
)
const formEl = useRef(null)
const inputEl = useRef(null)
const handleClick = () => {
inputEl.current.click()
}
const handleUpload = async event => {
setIsUploading(true)
try {
await uploadApp(baseUrl, event.target.files[0])
formEl.current.reset()
successAlert.show()
} catch (error) {
errorAlert.show({ error })
}
setIsUploading(false)
}

return (
<>
<form className={styles.hiddenForm} ref={formEl}>
<input
type="file"
accept="application/zip"
ref={inputEl}
onChange={handleUpload}
/>
</form>
<Button
className={styles.uploadBtn}
onClick={handleClick}
disabled={isUploading}
>
{isUploading
? i18n.t('Uploading...')
: i18n.t('Upload an app to install')}
</Button>
</>
)
}

const ManualInstall = () => (
<>
<h1 className={styles.header}>{i18n.t('Manually install an app')}</h1>
<p className={styles.warning}>
{i18n.t(
'Installed apps have access to your DHIS2 data and metadata. Make sure you trust an app before installing it.'
)}
</p>
<UploadButton />
</>
)

export default ManualInstall
6 changes: 0 additions & 6 deletions src/components/common.module.css

This file was deleted.

Loading

0 comments on commit 7836858

Please sign in to comment.