Skip to content

Commit

Permalink
Merge pull request input-output-hk#2927 from input-output-hk/feat/ddw…
Browse files Browse the repository at this point in the history
…-1025-matomo-poc

[DDW-809] Analytics - part 1 - analytics opt-in, settings and unique user ID generation
  • Loading branch information
danielmain authored Sep 21, 2022
2 parents 2b0e8d1 + 5da90e4 commit 8f04188
Show file tree
Hide file tree
Showing 64 changed files with 1,365 additions and 434 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,12 @@
"@types/node": "14.18.1",
"@types/qrcode.react": "1.0.2",
"@types/react": "16.9.56",
"@types/react-router": "5.1.18",
"@types/react-router-dom": "5.3.3",
"@types/react-svg-inline": "2.1.3",
"@types/react-table": "^7.7.9",
"@types/trezor-connect": "8.1.18",
"@types/uuid": "8.3.4",
"@typescript-eslint/eslint-plugin": "5.20.0",
"@typescript-eslint/parser": "5.20.0",
"@xarc/run": "1.1.1",
Expand Down Expand Up @@ -291,6 +294,7 @@
"url": "0.11.0",
"usb-detection": "4.13.0",
"util": "0.12.4",
"uuid": "8.3.2",
"validator": "13.7.0"
},
"devEngines": {
Expand Down
2 changes: 2 additions & 0 deletions source/common/config/electron-store.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const STORAGE_KEYS: Record<string, StorageKey> = {
STAKE_POOLS_LIST_VIEW_TOOLTIP: 'STAKE-POOLS-LIST-VIEW-TOOLTIP',
STAKING_INFO_WAS_OPEN: 'ALONZO-INFO-WAS-OPEN',
TERMS_OF_USE_ACCEPTANCE: 'TERMS-OF-USE-ACCEPTANCE',
ANALYTICS_ACCEPTANCE: 'ANALYTICS-ACCEPTANCE',
USER_ID: 'USER-ID',
THEME: 'THEME',
TOKEN_FAVORITES: 'TOKEN-FAVORITES',
USER_DATE_FORMAT_ENGLISH: 'USER-DATE-FORMAT-ENGLISH',
Expand Down
4 changes: 3 additions & 1 deletion source/common/ipc/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const DIALOGS = {
import { ApplicationDialog } from '../../renderer/app/types/applicationDialogTypes';

export const DIALOGS: Record<string, ApplicationDialog> = {
ABOUT: 'ABOUT_DIALOG',
DAEDALUS_DIAGNOSTICS: 'DAEDALUS_DIAGNOSTICS_DIALOG',
ITN_REWARDS_REDEMPTION: 'ITN_REWARDS_REDEMPTION_DIALOG',
Expand Down
4 changes: 2 additions & 2 deletions source/common/ipc/lib/IpcConversation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isString } from 'lodash';
import uuid from 'uuid';
import { v4 as uuidv4 } from 'uuid';

export type IpcSender = {
send: (channel: string, conversationId: string, ...args: Array<any>) => void;
Expand Down Expand Up @@ -68,7 +68,7 @@ export class IpcConversation<Incoming, Outgoing> {
receiver: IpcReceiver
): Promise<Incoming> {
return new Promise((resolve, reject) => {
const conversationId = uuid();
const conversationId = uuidv4();

const handler = (
event,
Expand Down
2 changes: 2 additions & 0 deletions source/common/types/electron-store.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type StorageKey =
| 'USER-TIME-FORMAT'
| 'WALLET-MIGRATION-STATUS'
| 'WALLETS'
| 'ANALYTICS-ACCEPTANCE'
| 'USER-ID'
| 'WINDOW-BOUNDS';
export type StoreMessage = {
type: StorageType;
Expand Down
4 changes: 3 additions & 1 deletion source/common/types/environment.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export type Environment = {
mainProcessID: string;
rendererProcessID: string;
os: string;
cpu: string;
system: string;
cpu: Cpu;
ram: number;
hasMetHardwareRequirements: boolean;
installerVersion: string;
Expand All @@ -33,6 +34,7 @@ export type Environment = {
isLinux: boolean;
isBlankScreenFixActive: boolean;
keepLocalClusterRunning: boolean;
analyticsFeatureEnabled: boolean;
};
// constants
export const PRODUCTION = 'production';
Expand Down
2 changes: 2 additions & 0 deletions source/main/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const isPreview = checkIsPreview(NETWORK);
const isShelleyQA = checkIsShelleyQA(NETWORK);
const isSelfnode = checkIsSelfnode(NETWORK);
const isDevelopment = checkIsDevelopment(NETWORK);
const analyticsFeatureEnabled = isMainnet || isStaging || isTestnet;
const keepLocalClusterRunning = process.env.KEEP_LOCAL_CLUSTER_RUNNING;
const API_VERSION = process.env.API_VERSION || 'dev';
const NODE_VERSION = '1.35.3'; // TODO: pick up this value from process.env
Expand Down Expand Up @@ -111,6 +112,7 @@ export const environment: Environment = Object.assign(
isBlankScreenFixActive,
keepLocalClusterRunning,
hasMetHardwareRequirements,
analyticsFeatureEnabled,
},
process.env
);
5 changes: 5 additions & 0 deletions source/renderer/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import WalletSettingsPage from './containers/wallet/WalletSettingsPage';
import WalletUtxoPage from './containers/wallet/WalletUtxoPage';
import VotingRegistrationPage from './containers/voting/VotingRegistrationPage';
import { IS_STAKING_INFO_PAGE_AVAILABLE } from './config/stakingConfig';
import AnalyticsConsentPage from './containers/profile/AnalyticsConsentPage';

export const Routes = withRouter(() => (
<Route path={ROUTES.ROOT}>
Expand All @@ -49,6 +50,10 @@ export const Routes = withRouter(() => (
component={InitialSettingsPage}
/>
<Route path={ROUTES.PROFILE.TERMS_OF_USE} component={TermsOfUsePage} />
<Route
path={ROUTES.PROFILE.ANALYTICS}
component={AnalyticsConsentPage}
/>
<Route
path={ROUTES.PROFILE.DATA_LAYER_MIGRATION}
component={DataLayerMigrationPage}
Expand Down
6 changes: 3 additions & 3 deletions source/renderer/app/actions/lib/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { bindAll } from 'lodash';
/**
* Listener type as Function that takes specific params <P>
*/
export type Listener<P> = (params: P) => any;
export type Listener<P> = (params: P) => void;
/**
* Action class with typed params
*/
Expand All @@ -30,8 +30,8 @@ export default class Action<Params> {
this.listeners.push(listener);
}

trigger(params?: Params) {
this.listeners.forEach((listener) => listener(params));
async trigger(params?: Params): Promise<void> {
await Promise.all(this.listeners.map((listener) => listener(params)));
}

remove(listener: Listener<Params>) {
Expand Down
4 changes: 3 additions & 1 deletion source/renderer/app/actions/profile-actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Action from './lib/Action'; // ======= PROFILE ACTIONS =======
import Action from './lib/Action';
import { AnalyticsAcceptanceStatus } from '../analytics/types';

export default class ProfileActions {
acceptAnalytics: Action<AnalyticsAcceptanceStatus> = new Action();
acceptTermsOfUse: Action<any> = new Action();
acceptDataLayerMigration: Action<any> = new Action();
getLogs: Action<any> = new Action();
Expand Down
1 change: 1 addition & 0 deletions source/renderer/app/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AnalyticsAcceptanceStatus } from './types';
5 changes: 5 additions & 0 deletions source/renderer/app/analytics/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum AnalyticsAcceptanceStatus {
PENDING = 'PENDING',
ACCEPTED = 'ACCEPTED',
REJECTED = 'REJECTED',
}
30 changes: 27 additions & 3 deletions source/renderer/app/api/utils/localStorage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable consistent-return */
import { includes, without, get } from 'lodash';
import { get, includes, without } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { toJS } from '../../../../common/utils/helper';
import { electronStoreConversation } from '../../ipc/electronStoreConversation';
import type { WalletMigrationStatus } from '../../stores/WalletMigrationStore';
import { WalletMigrationStatuses } from '../../stores/WalletMigrationStore';
import {
STORAGE_TYPES as types,
STORAGE_KEYS as keys,
STORAGE_TYPES as types,
} from '../../../../common/config/electron-store.config';
import type { NewsTimestamp } from '../news/types';
import type {
Expand All @@ -16,8 +17,8 @@ import type {
import type { StorageKey } from '../../../../common/types/electron-store.types';
import type { Currency, DeprecatedCurrency } from '../../types/currencyTypes';
import {
CURRENCY_IS_ACTIVE_BY_DEFAULT,
CURRENCY_DEFAULT_SELECTED,
CURRENCY_IS_ACTIVE_BY_DEFAULT,
} from '../../config/currencyConfig';
import {
AssetLocalData,
Expand All @@ -27,6 +28,7 @@ import {
UnpairedHardwareWalletData,
WalletLocalData,
} from '../../types/localDataTypes';
import { AnalyticsAcceptanceStatus } from '../../analytics';

export type SetHardwareWalletLocalDataRequestType = {
walletId: string;
Expand Down Expand Up @@ -121,6 +123,28 @@ export default class LocalStorageApi {
LocalStorageApi.set(keys.TERMS_OF_USE_ACCEPTANCE, true);
unsetTermsOfUseAcceptance = (): Promise<void> =>
LocalStorageApi.unset(keys.TERMS_OF_USE_ACCEPTANCE);
getAnalyticsAcceptance = (): Promise<AnalyticsAcceptanceStatus> =>
LocalStorageApi.get(
keys.ANALYTICS_ACCEPTANCE,
AnalyticsAcceptanceStatus.PENDING
);
setAnalyticsAcceptance = (status: AnalyticsAcceptanceStatus): Promise<void> =>
LocalStorageApi.set(keys.ANALYTICS_ACCEPTANCE, status);
unsetAnalyticsAcceptance = (): Promise<void> =>
LocalStorageApi.set(
keys.ANALYTICS_ACCEPTANCE,
AnalyticsAcceptanceStatus.PENDING
);
getUserID = async (): Promise<string> => {
let userId: string = await LocalStorageApi.get(keys.USER_ID, null);

if (!userId) {
userId = uuidv4();
await LocalStorageApi.set(keys.USER_ID, userId);
}

return userId;
};
getUserTheme = (): Promise<string> => LocalStorageApi.get(keys.THEME);
setUserTheme = (theme: string): Promise<void> =>
LocalStorageApi.set(keys.THEME, theme);
Expand Down
44 changes: 0 additions & 44 deletions source/renderer/app/components/dapp/DappTransactionRequest.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,53 +44,9 @@
}
}
}
hr {
border: 0;
border-top: 1px solid var(--theme-dapp-transaction-request-separator);
margin: 20px 0;
opacity: 0.3;
}
.transactionFee {
color: var(--theme-dapp-transaction-request-fees-text-color);
font-family: var(--font-regular);
margin-bottom: 20px;
}
.additionalData,
.metadata {
background-color: var(
--theme-dapp-transaction-request-code-background-color
);
pre {
font-family: var(--font-mono);
font-size: 14px;
line-height: 1.38;
max-height: 250px;
overflow: auto;
padding: 9px 12px;
user-select: text;
white-space: pre-wrap;
word-wrap: break-word;
&::-webkit-scrollbar-corner {
background: transparent !important;
}
&::-webkit-scrollbar-button {
height: 2px !important;
}
}
}
.toggleButton {
background-color: var(
--theme-dapp-transaction-request-toggle-button-background-color
);
border-radius: 4px;
color: var(--theme-label-button-color);
cursor: pointer;
font-family: var(--font-medium);
font-size: 10px;
letter-spacing: 0.5px;
line-height: 1.2;
margin-left: 10px;
padding: 4px 8px;
text-transform: uppercase;
}
}
59 changes: 16 additions & 43 deletions source/renderer/app/components/dapp/DappTransactionRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useState, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import BigNumber from 'bignumber.js';
import classnames from 'classnames';
import { Select } from 'react-polymorph/lib/components/Select';
import {
defineMessages,
intlShape,
injectIntl,
FormattedHTMLMessage,
injectIntl,
intlShape,
} from 'react-intl';
import { observer } from 'mobx-react';
import styles from './DappTransactionRequest.scss';
Expand All @@ -18,6 +18,9 @@ import AssetsTransactionConfirmation from '../assets/AssetsTransactionConfirmati
import { formattedWalletAmount } from '../../utils/formatters';
import { isTokenMissingInWallet, tokenHasBalance } from '../../utils/assets';
import type { AssetToken } from '../../api/assets/types';
import { MonospaceTextBlock } from '../widgets/monospace-text-block/MonospaceTextBlock';
import { CollapsibleSection } from '../widgets/collapsible-section/CollapsibleSection';
import { Separator } from '../widgets/separator/Separator';

const messages = defineMessages({
title: {
Expand Down Expand Up @@ -92,16 +95,6 @@ type Props = {
wallets: Array<Wallet>;
};
const DappTransactionRequest = observer((props: Props) => {
const [isAdditionalDataVisible, toggleAdditionalData] = useState<boolean>(
false
);
const [isMetadataVisible, toggleMetadata] = useState<boolean>(false);
// @ts-ignore ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
const getToggleLabel = useCallback((isVisible: boolean) =>
isVisible
? intl.formatMessage(globalMessages.hide)
: intl.formatMessage(globalMessages.view)
);
const {
adaAmount,
address,
Expand Down Expand Up @@ -144,6 +137,7 @@ const DappTransactionRequest = observer((props: Props) => {
}}
/>
) : null;

const walletsDropdownStyles = classnames([
styles.walletsDropdown,
walletsDropdownHasError || hasTokenError ? styles.error : null,
Expand Down Expand Up @@ -200,8 +194,7 @@ const DappTransactionRequest = observer((props: Props) => {
className={styles.addWalletSelect}
/>
)}

<hr />
<Separator />
<p className={styles.label}>
{intl.formatMessage(messages.receiverLabel)}
</p>
Expand All @@ -219,34 +212,14 @@ const DappTransactionRequest = observer((props: Props) => {
<div className={styles.transactionFee}>
+{formattedWalletAmount(transactionFee)}
</div>
<p className={styles.labelData}>
{intl.formatMessage(messages.additionalDataLabel)}
<button
className={styles.toggleButton}
onClick={() => toggleAdditionalData(!isAdditionalDataVisible)}
>
{getToggleLabel(isAdditionalDataVisible)}
</button>
</p>
{isAdditionalDataVisible && (
<div className={styles.additionalData}>
<pre>{additionalData}</pre>
</div>
)}
<p className={styles.labelData}>
{intl.formatMessage(messages.metaDataLabel)}
<button
className={styles.toggleButton}
onClick={() => toggleMetadata(!isMetadataVisible)}
>
{getToggleLabel(isMetadataVisible)}
</button>
</p>
{isMetadataVisible && (
<div className={styles.metadata}>
<pre>{metadata}</pre>
</div>
)}
<CollapsibleSection
header={intl.formatMessage(messages.additionalDataLabel)}
>
<MonospaceTextBlock>{additionalData}</MonospaceTextBlock>
</CollapsibleSection>
<CollapsibleSection header={intl.formatMessage(messages.metaDataLabel)}>
<MonospaceTextBlock>{metadata}</MonospaceTextBlock>
</CollapsibleSection>
</Dialog>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import AnalyticsConsentForm from './AnalyticsConsentForm';
import StoryDecorator from '../../../../../../storybook/stories/_support/StoryDecorator';

storiesOf('Analytics', module)
.addDecorator(withKnobs)
.addDecorator((story) => <StoryDecorator>{story()}</StoryDecorator>)
.add('Analytics Consent Form', () => <AnalyticsConsentForm />);
Loading

0 comments on commit 8f04188

Please sign in to comment.