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

Add config table and analytics opt out on first load #39

Merged
merged 2 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 5 additions & 0 deletions assets/migrations/0002_config.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE config (
id INTEGER PRIMARY KEY,
key TEXT NOT NULL,
val TEXT NOT NULL
);
10 changes: 9 additions & 1 deletion src/common/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import amplitude from 'amplitude-js';
import { ConfigKey } from 'types/types';

// TODO(nathanleclaire): Not the largest fan of this spaghetti-ish import
// renderer is really supposed to import common not vice versa
import store from '../renderer/store';

const AMPLITUDE_KEY = 'f1cde3642f7e0f483afbb7ac15ae8277';
const AMPLITUDE_HEARTBEAT_INTERVAL = 3600000;

amplitude.getInstance().init(AMPLITUDE_KEY);

const analytics = (event: string, metadata: any) => {
if (process.env.NODE_ENV !== 'development' /* and user has not opted out */) {
if (
process.env.NODE_ENV !== 'development' &&
store.getState().config.values[ConfigKey.AnalyticsEnabled]
) {
amplitude.getInstance().logEvent(event, metadata);
}
};
Expand Down
1 change: 0 additions & 1 deletion src/main/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ async function accounts(msg: AccountsRequest): Promise<AccountsResponse> {
await addKeypair(KEY_PATH);
}
const kp = await localKeypair(KEY_PATH);
logger.info('accounts', { net, pubKey: kp.publicKey });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not relevant to PR but I don't think this adds much any more

const solConn = new sol.Connection(netToURL(net));
const existingAccounts = await db.all(
'SELECT * FROM account WHERE net = ? ORDER BY created_at DESC, humanName ASC',
Expand Down
30 changes: 30 additions & 0 deletions src/main/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
ConfigAction,
ConfigMap,
WBConfigRequest,
WBConfigResponse,
} from '../types/types';
import { db } from './db';

async function wbConfig(msg: WBConfigRequest): Promise<WBConfigResponse> {
const { action, key } = msg;
if (action === ConfigAction.Set) {
const { val } = msg;
const existingRow = await db.get('SELECT * FROM config WHERE key = ?', key);
if (existingRow) {
await db.run('UPDATE config SET val = ? WHERE key = ?', val, key);
} else {
await db.run('INSERT INTO config (key, val) VALUES (?, ?)', key, val);
}
}
const cfgVals = await db.all('SELECT * FROM config');
const values: ConfigMap = {};
if (cfgVals) {
cfgVals.forEach((setting: any) => {
values[setting.key] = setting.val;
});
}
return { values };
}

export default wbConfig;
4 changes: 4 additions & 0 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from './programChanges';
import { RESOURCES_PATH } from './const';
import { db, initDB } from './db';
import wbConfig from './config';

export default class AppUpdater {
constructor() {
Expand Down Expand Up @@ -74,6 +75,9 @@ ipcMain.on(
case 'unsubscribe-program-changes':
await unsubscribeProgramChanges(msg);
break;
case 'config':
res = await wbConfig(msg);
break;
default:
}
logger.info('OK', { method, ...res });
Expand Down
3 changes: 3 additions & 0 deletions src/main/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ contextBridge.exposeInMainWorld('electron', {
unsubscribeProgramChanges(msg) {
send('unsubscribe-program-changes', msg);
},
config(msg) {
send('config', msg);
},
on(method, func) {
ipcRenderer.on(method, (event, ...args) => func(...args));
},
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,10 @@ $top-nav-height: 47px; // hack -- def better way to compute somehow
.dropdown-menu {
z-index: 1020; // hack to fix other items being on top of dropdown
}

.btn-outline-dark {
&:hover {
color: $black;
background-color: $white;
}
}
196 changes: 141 additions & 55 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ import {
faNetworkWired,
faCircle,
} from '@fortawesome/free-solid-svg-icons';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, validatorActions } from './slices/mainSlice';
import { Net } from '../types/types';
import { configActions, RootState, validatorActions } from './slices/mainSlice';
import { ConfigAction, ConfigKey, Net } from '../types/types';
import analytics from 'common/analytics';
import Toast from './components/Toast';
import Accounts from './nav/Accounts';
import Anchor from './nav/Anchor';
import Validator from './nav/Validator';
import { Button, ToggleButton, ToggleButtonGroup } from 'react-bootstrap';

declare global {
interface Window {
Expand Down Expand Up @@ -111,8 +112,11 @@ export default function App() {
const dispatch = useDispatch();
const { toasts } = useSelector((state: RootState) => state.toast);
const validator = useSelector((state: RootState) => state.validator);
const config = useSelector((state: RootState) => state.config);
const { net } = validator;

const [analyticsEnabled, setAnalyticsEnabled] = useState('yes');

useEffect(() => {
const listener = (resp: any) => {
const { method, res } = resp;
Expand All @@ -126,13 +130,24 @@ export default function App() {
dispatch(validatorActions.setWaitingForRun(false));
}
break;
case 'config':
dispatch(
configActions.set({
loading: false,
values: res.values,
})
);
break;
default:
}
};
window.electron.ipcRenderer.on('main', listener);
window.electron.ipcRenderer.validatorState({
net: Net.Localhost,
});
window.electron.ipcRenderer.config({
action: ConfigAction.Get,
});

return () => {
window.electron.ipcRenderer.removeListener('main', listener);
Expand Down Expand Up @@ -169,61 +184,132 @@ export default function App() {
);
}

return (
<Router>
<Switch>
<div className="row flex-nowrap g-0">
<div className="col-auto">
<Nav />
{toasts.map((t) => (
<Toast {...t} />
))}
</div>
<div className="col-10 bg-white ms-4">
<div className="row sticky-top sticky-nav bg-white-translucent">
<div>
<Header />
<span className="float-end">
{statusDisplay}
<DropdownButton
size="sm"
id="dropdown-basic-button"
title={netDropdownTitle}
onSelect={netDropdownSelect}
className="ms-2 float-end"
variant="light"
align="end"
>
<Dropdown.Item eventKey={Net.Localhost} href="#">
{Net.Localhost}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Dev} href="#">
{Net.Dev}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Test} href="#">
{Net.Test}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.MainnetBeta} href="#">
{Net.MainnetBeta}
</Dropdown.Item>
</DropdownButton>
</span>
</div>
</div>
<div className="row flex-nowrap">
<Route exact path="/">
<Accounts />
</Route>
<Route path="/validator">
<Validator />
</Route>
<Route path="/anchor">
<Anchor />
</Route>
let mainDisplay = <></>;

if (!config.loading && !(`${ConfigKey.AnalyticsEnabled}` in config.values)) {
mainDisplay = (
<div className="container">
<div className="mt-2">
<h3>Will you help us out?</h3>
Workbench collects usage analytics. You can audit this code on{' '}
<a
href="https://github.com/workbenchapp/solana-workbench"
target="_blank"
rel="noreferrer"
>
Github
</a>
. You can opt out below.
</div>
<div className="mt-2 mb-2">
<h5>What We Collect</h5>
<ul>
<li>Which features are popular</li>
<li>System properties like OS version</li>
<li>How often people are using Workbench</li>
</ul>
We do not collect addresses or private keys.
</div>
<ToggleButtonGroup type="radio" name="options" defaultValue={1}>
<ToggleButton
checked={analyticsEnabled === 'yes'}
key="yes"
type="radio"
value="yes"
variant={analyticsEnabled === 'yes' ? 'dark' : 'outline-dark'}
onClick={() => {
setAnalyticsEnabled('yes');
}}
>
Sure, I'll Help
</ToggleButton>
<ToggleButton
checked={analyticsEnabled === 'no'}
key="no"
value="no"
type="radio"
variant={analyticsEnabled === 'no' ? 'dark' : 'outline-dark'}
onClick={() => {
setAnalyticsEnabled('no');
}}
>
No Thanks
</ToggleButton>
</ToggleButtonGroup>
<Button
className="ms-2"
variant="primary"
onClick={() => {
window.electron.ipcRenderer.config({
action: ConfigAction.Set,
key: ConfigKey.AnalyticsEnabled,
val: analyticsEnabled,
});
}}
>
<span className="ms-1 text-white">Start</span>
</Button>
</div>
);
} else {
mainDisplay = (
<div className="row flex-nowrap g-0">
<div className="col-auto">
<Nav />
{toasts.map((t) => (
<Toast {...t} />
))}
</div>
<div className="col-10 bg-white ms-4">
<div className="row sticky-top sticky-nav bg-white-translucent">
<div>
<Header />
<span className="float-end">
{statusDisplay}
<DropdownButton
size="sm"
id="dropdown-basic-button"
title={netDropdownTitle}
onSelect={netDropdownSelect}
className="ms-2 float-end"
variant="light"
align="end"
>
<Dropdown.Item eventKey={Net.Localhost} href="#">
{Net.Localhost}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Dev} href="#">
{Net.Dev}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Test} href="#">
{Net.Test}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.MainnetBeta} href="#">
{Net.MainnetBeta}
</Dropdown.Item>
</DropdownButton>
</span>
</div>
</div>
<div className="row flex-nowrap">
<Route exact path="/">
<Accounts />
</Route>
<Route path="/validator">
<Validator />
</Route>
<Route path="/anchor">
<Anchor />
</Route>
</div>
</div>
</Switch>
</div>
);
}

return (
<Router>
<Switch>{mainDisplay}</Switch>
</Router>
);
}
29 changes: 27 additions & 2 deletions src/renderer/slices/mainSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TOAST_BOTTOM_OFFSET,
ToastProps,
Net,
ConfigState,
} from 'types/types';

const validatorState: ValidatorState = {
Expand Down Expand Up @@ -126,16 +127,40 @@ export const accountsSlice = createSlice({
},
});

const configState: ConfigState = {
loading: true,
values: {},
};

export const configSlice = createSlice({
name: 'config',
initialState: configState,
reducers: {
set: (state, action: PayloadAction<ConfigState>) => {
state.loading = action.payload.loading;
state.values = action.payload.values;
},
},
});

const mainReducer = combineReducers({
toast: toastSlice.reducer,
validator: validatorSlice.reducer,
accounts: accountsSlice.reducer,
config: configSlice.reducer,
});

export type RootState = ReturnType<typeof mainReducer>;
const [toastActions, accountsActions, validatorActions] = [
const [toastActions, accountsActions, validatorActions, configActions] = [
toastSlice.actions,
accountsSlice.actions,
validatorSlice.actions,
configSlice.actions,
];
export { toastActions, accountsActions, validatorActions, mainReducer };
export {
toastActions,
accountsActions,
validatorActions,
configActions,
mainReducer,
};
Loading