Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: IPFS experiments UI #1048

Merged
merged 43 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
db9a7bf
create experiments bundle
cwaring May 24, 2019
d023f57
add experimentsPanel
cwaring May 24, 2019
728682b
fix whitespace
cwaring May 24, 2019
482cfbc
refactor action effects
cwaring May 27, 2019
31d0dc9
build ui and filter based on app context
cwaring May 27, 2019
60cc858
add issueUrl
cwaring May 27, 2019
0f9d9ab
add initial translation strings
cwaring May 27, 2019
d2a16e6
wire in actionUrls and improve translations
cwaring May 27, 2019
255c564
handle null desktop value
cwaring May 27, 2019
94abb93
ui tweaks
cwaring May 27, 2019
880dc15
refactor defaultState and persistence
cwaring May 27, 2019
9031768
simplify state mgmt
cwaring May 27, 2019
763a0fc
tidy helpers
cwaring May 27, 2019
0c960fe
experiments -> experimentsBundle
cwaring May 28, 2019
7265eac
add description
cwaring May 28, 2019
cdc6b98
more positive description
cwaring May 28, 2019
bddc33f
experimentsPanel -> ExperimentsPanel
cwaring May 29, 2019
611e096
rm experimentsPanel
cwaring May 29, 2019
e819b3a
add ExperimentsPanel
cwaring May 29, 2019
33a17e2
remove unused select and improve namespacing
cwaring Jun 5, 2019
f8d1a43
refactor actions/reducer + wire in desktop method
cwaring Jun 5, 2019
ce54572
Merge branch 'master' into feat/experiments-actions
hacdias Jun 6, 2019
2a3c834
experiments and desktop
hacdias Jun 6, 2019
6943f66
standard style
hacdias Jun 6, 2019
41821f5
block checkbox while changing
hacdias Jun 6, 2019
c724d61
feat: add experiments actions and work with desktop (#1056)
hacdias Jun 6, 2019
8dc6384
feat: show notification on error
hacdias Jun 6, 2019
5c5eab0
update npm on ipfs error msg
hacdias Jun 6, 2019
0447c53
reflect real package name
hacdias Jun 6, 2019
4b2af7e
open on different tab
hacdias Jun 6, 2019
9cc4feb
fix security warning
hacdias Jun 6, 2019
6dfa3ed
remove console.log
hacdias Jun 6, 2019
b1fa845
check if experiment exists on desktop first
hacdias Jun 6, 2019
3328b74
do not persist EXPERIMENTS_TOGGLE
hacdias Jun 6, 2019
b97601a
revert checking
hacdias Jun 6, 2019
cbbf1c7
enable analytics for EXPERIMENTS_TOGGLE
hacdias Jun 6, 2019
ff81fd3
fix: action type
hacdias Jun 6, 2019
998091b
desktop -> desktopOnly
cwaring Jun 6, 2019
ad26a0a
EXP_TOGGLE_FINISH -> EXP_TOGGLE_FINISHED
cwaring Jun 6, 2019
1a814aa
rm async
cwaring Jun 6, 2019
6b0932e
pass experiment name
hacdias Jun 6, 2019
559e421
revert parenthesis
hacdias Jun 6, 2019
35d5b68
wip: update feedback url
hacdias Jun 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion public/locales/en/notify.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"filesEventFailed": "Failed to add files to IPFS. Please try again.",
"folderExists": "A folder with that name already exists. Please choose another.",
"couldntConnectToPeer": "Could not connect to the provided peer.",
"connectedToPeer": "Successfully connected to the provided peer."
"connectedToPeer": "Successfully connected to the provided peer.",
"experimentsErrors": {
"npmOnIpfs": "We couldn't make changes to your system. Please install or uninstall 'ipfs-npm' package manually."
}
}
11 changes: 11 additions & 0 deletions public/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@
"details": "Records JavaScript error messages and stack traces that occur while using the app, where possible. It is very helpful to know when the app is not working for you, but <1>error messages may include identifiable information</1> like CIDs or file paths, so only enable this if you are comfortable sharing that information with us."
}
},
"Experiments": {
"description": "Here you can get an early preview into new IPFS features by enabling options below. We are testing these ideas, and they may not be perfect yet so we'd love to hear your feedback.",
"issueUrl": "Open an issue",
"feedbackUrl": "💌Leave feedback",
"readMoreUrl": "Read More",
"npmOnIpfs": {
"title": "NPM on IPFS",
"label": "Enable ipfs-npm",
"description": "Install npm modules through IPFS with the 'ipfs-npm' command line tool!"
}
},
"ipfsCmdTools": "IPFS command line tools",
"ipfsCmdToolsDescription": "Add <0>ipfs</0> binary to your system <0>PATH</0> so you can use it in the command line."
}
11 changes: 9 additions & 2 deletions src/bundles/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const ASYNC_ACTIONS_TO_RECORD = [
'FILES_ADDBYPATH',
'FILES_MOVE',
'FILES_DELETE',
'FILES_DOWNLOADLINK'
'FILES_DOWNLOADLINK',
'EXPERIMENTS_TOGGLE'
]

const ASYNC_ACTION_RE = new RegExp(`^${ASYNC_ACTIONS_TO_RECORD.join('_|')}`)
Expand Down Expand Up @@ -106,8 +107,14 @@ const createAnalyticsBundle = ({
EventMap.delete(name)
} else {
const durationInSeconds = (root.performance.now() - start) / 1000
let key = state === 'FAILED' ? action.type : name

if (name === 'EXPERIMENTS_TOGGLE') {
key += `_${action.payload.key}`
}

root.Countly.q.push(['add_event', {
key: state === 'FAILED' ? action.type : name,
key: key,
count: 1,
dur: durationInSeconds
}])
Expand Down
125 changes: 125 additions & 0 deletions src/bundles/experiments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { createSelector } from 'redux-bundler'

export const ACTIONS = {
EXP_TOGGLE_STARTED: 'EXPERIMENTS_TOGGLE_STARTED',
EXP_TOGGLE_FINISHED: 'EXPERIMENTS_TOGGLE_FINISHED',
EXP_TOGGLE_FAILED: 'EXPERIMENTS_TOGGLE_FAILED',
EXP_UPDATE_STATE: 'EXPERIMENTS_UPDATE_STATE'
}

const EXPERIMENTS = [
{
key: 'npmOnIpfs',
actionUrls: [
{
url: 'https://github.com/ipfs-shipyard/npm-on-ipfs',
key: 'readMoreUrl'
},
{
url: 'https://github.com/ipfs-shipyard/npm-on-ipfs/issues',
key: 'issueUrl'
},
{
url: 'https://github.com/ipfs-shipyard/ipfs-desktop/issues/957',
key: 'feedbackUrl'
}
],
desktopOnly: true
}
]

const mergeState = (state, payload) =>
Object.keys(payload).reduce(
(all, key) => ({
...all,
[key]: {
...state[key],
...payload[key]
}
}),
state
)

const toggleEnabled = (state, key) => {
return unblock(
{
...state,
[key]: {
...state[key],
enabled: !(state && state[key] && state[key].enabled)
}
},
key
)
}

const unblock = (state, key) => {
return {
...state,
[key]: {
...state[key],
blocked: false
}
}
}

const block = (state, key) => {
return {
...state,
[key]: {
...state[key],
blocked: true
}
}
}

export default {
name: 'experiments',

persistActions: [
ACTIONS.EXP_TOGGLE_FINISHED,
ACTIONS.EXP_TOGGLE_FAILED,
ACTIONS.EXP_UPDATE_STATE
],

reducer: (state = {}, action) => {
if (action.type === ACTIONS.EXP_TOGGLE_STARTED) {
return block(state, action.payload.key)
hacdias marked this conversation as resolved.
Show resolved Hide resolved
}

if (action.type === ACTIONS.EXP_UPDATE_STATE) {
return mergeState(state, action.payload)
}

if (action.type === ACTIONS.EXP_TOGGLE_FINISHED) {
return toggleEnabled(state, action.payload.key)
}

if (action.type === ACTIONS.EXP_TOGGLE_FAILED) {
return unblock(state, action.payload.key)
}

return state
},

doExpToggleAction: key => ({ dispatch }) => {
if (!key) return

dispatch({
type: ACTIONS.EXP_TOGGLE_STARTED,
payload: { key }
})
},

selectExperimentsState: state => state.experiments,

selectExperiments: createSelector(
hacdias marked this conversation as resolved.
Show resolved Hide resolved
'selectIsIpfsDesktop',
'selectExperimentsState',
(isDesktop, state) =>
EXPERIMENTS.filter(e => !!e.desktopOnly === isDesktop).map(e => ({
...e,
...state[e.key]
}))
)
}
2 changes: 2 additions & 0 deletions src/bundles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import bundleCache from '../lib/bundle-cache'
import ipfsDesktop from './ipfs-desktop'
import repoStats from './repo-stats'
import createAnalyticsBundle from './analytics'
import experimentsBundle from './experiments'

export default composeBundles(
createCacheBundle(bundleCache.set),
Expand Down Expand Up @@ -76,6 +77,7 @@ export default composeBundles(
notifyBundle,
connectedBundle,
retryInitBundle,
experimentsBundle,
ipfsDesktop,
repoStats,
createAnalyticsBundle({})
Expand Down
39 changes: 36 additions & 3 deletions src/bundles/ipfs-desktop.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ACTIONS } from './experiments'

let bundle = {
name: 'ipfsDesktop',
reducer: (state = {}) => state,
Expand All @@ -8,6 +10,10 @@ if (window.ipfsDesktop) {
bundle = {
...bundle,
reducer: (state = {}, action) => {
if (action.type === ACTIONS.EXP_TOGGLE_STARTED) {
window.ipfsDesktop.toggleSetting(`experiments.${action.payload.key}`)
hacdias marked this conversation as resolved.
Show resolved Hide resolved
}

if (!action.type.startsWith('DESKTOP_')) {
return state
}
Expand All @@ -23,16 +29,43 @@ if (window.ipfsDesktop) {

selectDesktopVersion: () => window.ipfsDesktop.version,

doDesktopStartListening: () => async ({ dispatch }) => {
window.ipfsDesktop.onConfigChanged(config => {
doDesktopStartListening: () => async ({ dispatch, store }) => {
window.ipfsDesktop.onConfigChanged(({ config, changed, success }) => {
const prevConfig = store.selectDesktopSettings()

if (Object.keys(prevConfig).length === 0) {
dispatch({
type: ACTIONS.EXP_UPDATE_STATE,
payload: Object.keys(config.experiments).reduce(
(all, key) => ({
...all,
[key]: {
enabled: config.experiments[key]
}
}),
{}
)
})
}

if (changed && changed.startsWith('experiments.')) {
const key = changed.replace('experiments.', '')

if (success) {
dispatch({ type: ACTIONS.EXP_TOGGLE_FINISHED, payload: { key } })
} else {
dispatch({ type: ACTIONS.EXP_TOGGLE_FAILED, payload: { key } })
}
}

dispatch({
type: 'DESKTOP_SETTINGS_CHANGED',
payload: config
})
})
},

doDesktopSettingsToggle: (setting) => () => {
doDesktopSettingsToggle: setting => () => {
window.ipfsDesktop.toggleSetting(setting)
},

Expand Down
10 changes: 10 additions & 0 deletions src/bundles/notify.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSelector } from 'redux-bundler'
import { ACTIONS as EXP_ACTIONS } from './experiments'

/*
# Notify
Expand Down Expand Up @@ -59,6 +60,15 @@ const notify = {
}
}

if (action.type === EXP_ACTIONS.EXP_TOGGLE_FAILED) {
return {
...state,
show: true,
error: true,
eventId: `experimentsErrors.${action.payload.key}`
}
}

return state
},

Expand Down
63 changes: 63 additions & 0 deletions src/components/experiments/ExperimentsPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react'
import { connect } from 'redux-bundler-react'
import Checkbox from '../checkbox/Checkbox'
import Box from '../box/Box'
import Title from '../../settings/Title'

const Experiments = ({ doExpToggleAction, experiments, t }) => {
// if there are no experiments to show don't render
if (experiments && experiments.length > 0) {
const tkey = (selector, key) =>
t(`Experiments.${key ? `${key}.${selector}` : `${selector}`}`)
return (
<Box className='mb3 pa4 lh-copy'>
<Title>{t('experiments')}</Title>
<p className='db mv4 f6 charcoal mw7'>{tkey('description')}</p>
<div className='flex flex-wrap pb3'>
{experiments.map(({ key, actionUrls, enabled, blocked }) => {
return (
<div
key={key}
className='pa3 mr3 mb3 mw6 br3 bg-white dib f6 ba b1 b--light-gray'
>
<h3>{tkey('title', key)}</h3>
<p className='charcoal'>{tkey('description', key)}</p>
<Checkbox
className='dib'
disabled={blocked}
onChange={() => doExpToggleAction(key, enabled)}
checked={enabled}
label={<span className='fw5 f6'>{tkey('label', key)}</span>}
/>
{actionUrls && (
<div className='mv3'>
{actionUrls.map((action, i) => (
<a
target='_blank'
rel='noopener noreferrer'
key={action.key}
className={`link blue pr2 ${i > 0 &&
'bl b1 pl2 b--light-gray'}`}
href={action.url}
>
{tkey(action.key)}
</a>
))}
</div>
)}
</div>
)
})}
</div>
</Box>
)
} else {
return null
}
}

export default connect(
'selectExperiments',
'doExpToggleAction',
Experiments
)
3 changes: 3 additions & 0 deletions src/settings/SettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import LanguageSelector from '../components/language-selector/LanguageSelector'
import AnalyticsToggle from '../components/analytics-toggle/AnalyticsToggle'
import JsonEditor from './editor/JsonEditor'
import DesktopSettings from './DesktopSettings'
import Experiments from '../components/experiments/ExperimentsPanel'
import Title from './Title'

const PAUSE_AFTER_SAVE_MS = 3000
Expand Down Expand Up @@ -40,6 +41,8 @@ export const SettingsPage = ({
</div>
</Box>

<Experiments t={t} />

<Box>
<Title>{t('config')}</Title>
<div className='flex pb3'>
Expand Down