Feature #200: Show version number (#370)

* Add `dotenv-expand` as a dependency

* Add app version to sidebar

* Add hardcoded latest safe version to env variables

* Add `semver` to compare current vs latest version

* Add Safe version to Safe Details

* Adjustments in version number
This commit is contained in:
Fernando 2019-12-18 07:18:58 -03:00 committed by Germán Martínez
parent b128cc41c0
commit 2dafa88337
11 changed files with 104 additions and 729 deletions

View File

@ -13,3 +13,7 @@ REACT_APP_PORTIS_ID=
REACT_APP_SQUARELINK_ID= REACT_APP_SQUARELINK_ID=
REACT_APP_FORTMATIC_KEY= REACT_APP_FORTMATIC_KEY=
# Versions
REACT_APP_LATEST_SAFE_VERSION=1.1.1
# Leave it untouched, version will set using dotenv-expand
REACT_APP_APP_VERSION=$npm_package_version

View File

@ -73,6 +73,7 @@
"redux-actions": "^2.6.5", "redux-actions": "^2.6.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"semver": "^7.1.1",
"squarelink": "^1.1.4", "squarelink": "^1.1.4",
"web3": "1.2.4", "web3": "1.2.4",
"web3connect": "^1.0.0-beta.23" "web3connect": "^1.0.0-beta.23"
@ -115,6 +116,7 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"css-loader": "3.4.0", "css-loader": "3.4.0",
"detect-port": "^1.3.0", "detect-port": "^1.3.0",
"dotenv-expand": "^5.1.0",
"eslint": "6.7.2", "eslint": "6.7.2",
"eslint-config-airbnb": "18.0.1", "eslint-config-airbnb": "18.0.1",
"eslint-plugin-flowtype": "4.5.2", "eslint-plugin-flowtype": "4.5.2",

View File

@ -6,7 +6,7 @@ process.env.NODE_ENV = 'production';
// if this file is missing. dotenv will never modify any environment variables // if this file is missing. dotenv will never modify any environment variables
// that have already been set. // that have already been set.
// https://github.com/motdotla/dotenv // https://github.com/motdotla/dotenv
require('dotenv').config({silent: true}); require('dotenv-expand')(require('dotenv').config({ silent: true }));
var chalk = require('chalk'); var chalk = require('chalk');
var fs = require('fs-extra'); var fs = require('fs-extra');
@ -128,11 +128,11 @@ function build(previousSizeMap) {
printErrors('Failed to compile A.', [err]); printErrors('Failed to compile A.', [err]);
process.exit(1); process.exit(1);
} }
if (stats.compilation.errors.length) { if (stats.compilation.errors.length) {
printErrors('Failed to compile B.', stats.compilation.errors); printErrors('Failed to compile B.', stats.compilation.errors);
process.exit(1); process.exit(1);
} }
console.log(chalk.green('Compiled successfully.')); console.log(chalk.green('Compiled successfully.'));
console.log(); console.log();

View File

@ -5,7 +5,7 @@ process.env.NODE_ENV = 'development'
// if this file is missing. dotenv will never modify any environment variables // if this file is missing. dotenv will never modify any environment variables
// that have already been set. // that have already been set.
// https://github.com/motdotla/dotenv // https://github.com/motdotla/dotenv
require('dotenv').config({ silent: true }) require('dotenv-expand')(require('dotenv').config({ silent: true }))
var chalk = require('chalk') var chalk = require('chalk')
var webpack = require('webpack') var webpack = require('webpack')

View File

@ -6,7 +6,7 @@ process.env.PUBLIC_URL = ''
// if this file is missing. dotenv will never modify any environment variables // if this file is missing. dotenv will never modify any environment variables
// that have already been set. // that have already been set.
// https://github.com/motdotla/dotenv // https://github.com/motdotla/dotenv
require('dotenv').config({ silent: true }) require('dotenv-expand')(require('dotenv').config({ silent: true }))
const jest = require('jest') const jest = require('jest')

View File

@ -25,6 +25,8 @@ type Props = {
toggleSidebar: Function, toggleSidebar: Function,
} }
const appVersion = `v${process.env.REACT_APP_APP_VERSION || ''}`
const LegalLinks = (props: Props) => { const LegalLinks = (props: Props) => {
const classes = useStyles() const classes = useStyles()
const dispatch = useDispatch() const dispatch = useDispatch()
@ -51,6 +53,9 @@ const LegalLinks = (props: Props) => {
<GnoButtonLink className={classes.buttonLink} onClick={openCookiesHandler}> <GnoButtonLink className={classes.buttonLink} onClick={openCookiesHandler}>
Cookies Cookies
</GnoButtonLink> </GnoButtonLink>
<Link className={classes.link} to="https://github.com/gnosis/safe-react/releases" target="_blank">
{appVersion}
</Link>
</Block> </Block>
) )
} }

View File

@ -1,7 +1,9 @@
// @flow // @flow
import React from 'react' import React, { useEffect } from 'react'
import { withStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { withSnackbar } from 'notistack' import { withSnackbar } from 'notistack'
import semverLessThan from 'semver/functions/lt'
import semverValid from 'semver/functions/valid'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
@ -15,12 +17,12 @@ import Button from '~/components/layout/Button'
import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications' import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { styles } from './style' import { styles } from './style'
import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input' export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn' export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
type Props = { type Props = {
classes: Object,
safeAddress: string, safeAddress: string,
safeName: string, safeName: string,
updateSafe: Function, updateSafe: Function,
@ -28,9 +30,13 @@ type Props = {
closeSnackbar: Function, closeSnackbar: Function,
} }
const ChangeSafeName = (props: Props) => { const useStyles = makeStyles(styles)
const SafeDetails = (props: Props) => {
const classes = useStyles()
const [safeVersions, setSafeVersions] = React.useState({ current: null, latest: null, needUpdate: false })
const { const {
classes, safeAddress, safeName, updateSafe, enqueueSnackbar, closeSnackbar, safeAddress, safeName, updateSafe, enqueueSnackbar, closeSnackbar,
} = props } = props
const handleSubmit = (values) => { const handleSubmit = (values) => {
@ -40,11 +46,40 @@ const ChangeSafeName = (props: Props) => {
showSnackbar(notification.afterExecution.noMoreConfirmationsNeeded, enqueueSnackbar, closeSnackbar) showSnackbar(notification.afterExecution.noMoreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
} }
useEffect(() => {
const getVersion = async () => {
let current
let latest
try {
const safeMaster = await getSafeMasterContract()
const safeMasterVersion = await safeMaster.VERSION()
current = semverValid(safeMasterVersion)
latest = semverValid(process.env.REACT_APP_LATEST_SAFE_VERSION)
const needUpdate = semverLessThan(current, latest)
setSafeVersions({ current, latest, needUpdate })
} catch (err) {
setSafeVersions({ current: current || 'Version not defined' })
console.error(err)
}
}
getVersion()
}, [])
return ( return (
<> <>
<GnoForm onSubmit={handleSubmit}> <GnoForm onSubmit={handleSubmit}>
{() => ( {() => (
<> <>
<Block className={classes.formContainer}>
<Heading tag="h2">Safe Version</Heading>
<Row align="end" grow>
<Paragraph className={classes.versionNumber}>
{safeVersions.current}
{safeVersions.needUpdate && ` (there's a newer version: ${safeVersions.latest})`}
</Paragraph>
</Row>
</Block>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
<Heading tag="h2">Modify Safe name</Heading> <Heading tag="h2">Modify Safe name</Heading>
<Paragraph> <Paragraph>
@ -85,4 +120,4 @@ const ChangeSafeName = (props: Props) => {
) )
} }
export default withStyles(styles)(withSnackbar(ChangeSafeName)) export default withSnackbar(SafeDetails)

View File

@ -23,4 +23,7 @@ export const styles = () => ({
width: '100%', width: '100%',
borderTop: `2px solid ${border}`, borderTop: `2px solid ${border}`,
}, },
versionNumber: {
height: '21px',
},
}) })

View File

@ -13,7 +13,7 @@ import ButtonLink from '~/components/layout/ButtonLink'
import RemoveSafeModal from './RemoveSafeModal' import RemoveSafeModal from './RemoveSafeModal'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import { type Owner } from '~/routes/safe/store/models/owner' import { type Owner } from '~/routes/safe/store/models/owner'
import ChangeSafeName from './ChangeSafeName' import SafeDetails from './SafeDetails'
import ThresholdSettings from './ThresholdSettings' import ThresholdSettings from './ThresholdSettings'
import ManageOwners from './ManageOwners' import ManageOwners from './ManageOwners'
import actions, { type Actions } from './actions' import actions, { type Actions } from './actions'
@ -114,7 +114,7 @@ class Settings extends React.Component<Props, State> {
className={cn(classes.menuOption, menuOptionIndex === 1 && classes.active)} className={cn(classes.menuOption, menuOptionIndex === 1 && classes.active)}
onClick={this.handleChange(1)} onClick={this.handleChange(1)}
> >
Safe name Safe details
</Row> </Row>
<Hairline /> <Hairline />
<Row <Row
@ -139,7 +139,7 @@ class Settings extends React.Component<Props, State> {
<Col xs={9} layout="column"> <Col xs={9} layout="column">
<Block className={classes.container}> <Block className={classes.container}>
{menuOptionIndex === 1 && ( {menuOptionIndex === 1 && (
<ChangeSafeName safeAddress={safeAddress} safeName={safeName} updateSafe={updateSafe} /> <SafeDetails safeAddress={safeAddress} safeName={safeName} updateSafe={updateSafe} />
)} )}
{menuOptionIndex === 2 && ( {menuOptionIndex === 2 && (
<ManageOwners <ManageOwners

View File

@ -6,7 +6,7 @@ import { renderSafeView } from '~/test/builder/safe.dom.utils'
import { sleep } from '~/utils/timer' import { sleep } from '~/utils/timer'
import '@testing-library/jest-dom/extend-expect' import '@testing-library/jest-dom/extend-expect'
import { SETTINGS_TAB_BTN_TEST_ID, SAFE_VIEW_NAME_HEADING_TEST_ID } from '~/routes/safe/components/Layout' import { SETTINGS_TAB_BTN_TEST_ID, SAFE_VIEW_NAME_HEADING_TEST_ID } from '~/routes/safe/components/Layout'
import { SAFE_NAME_INPUT_TEST_ID, SAFE_NAME_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Settings/ChangeSafeName' import { SAFE_NAME_INPUT_TEST_ID, SAFE_NAME_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Settings/SafeDetails'
describe('DOM > Feature > Settings - Name', () => { describe('DOM > Feature > Settings - Name', () => {
let store let store

754
yarn.lock

File diff suppressed because it is too large Load Diff