From e6fe78cf618682ccb4a18b494a53e91d63cf295c Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Fri, 1 Dec 2017 14:44:51 -0800 Subject: [PATCH] Fix: properly implement Basic limits on read receipts / tracking --- .../activity/specs/activity-list-spec.jsx | 5 +- .../assets/ic-modal-image@2x.png | Bin 0 -> 6017 bytes .../lib/link-tracking-button.jsx | 7 -- .../onboarding/styles/onboarding.less | 23 +----- .../assets/ic-modal-image@2x.png | Bin 0 -> 7572 bytes .../lib/open-tracking-button.jsx | 7 -- .../lib/sidebar-participant-profile.jsx | 2 +- .../send-later/lib/send-later-button.jsx | 2 +- .../lib/send-reminders-utils.es6 | 2 +- .../thread-snooze/lib/snooze-store.es6 | 2 +- app/spec/stores/feature-usage-store-spec.es6 | 16 ++-- .../metadata-composer-toggle-button.jsx | 74 +++++++++++++----- app/src/flux/stores/feature-usage-store.jsx | 63 +++++++-------- app/src/flux/tasks/send-draft-task.es6 | 15 +++- .../images/toolbar/tiny-warning-sign@2x.png | Bin 0 -> 1083 bytes mailsync | 2 +- 16 files changed, 112 insertions(+), 108 deletions(-) create mode 100644 app/internal_packages/link-tracking/assets/ic-modal-image@2x.png create mode 100644 app/internal_packages/open-tracking/assets/ic-modal-image@2x.png create mode 100644 app/static/images/toolbar/tiny-warning-sign@2x.png diff --git a/app/internal_packages/activity/specs/activity-list-spec.jsx b/app/internal_packages/activity/specs/activity-list-spec.jsx index 112087fb6..a93ef3438 100644 --- a/app/internal_packages/activity/specs/activity-list-spec.jsx +++ b/app/internal_packages/activity/specs/activity-list-spec.jsx @@ -103,10 +103,7 @@ pluginValue = { ], }; messages[1].directlyAttachMetadata(OPEN_TRACKING_ID, pluginValue); -pluginValue = { - links: [], - tracked: false, -}; +pluginValue = {}; messages[1].directlyAttachMetadata(LINK_TRACKING_ID, pluginValue); pluginValue = { open_count: 0, diff --git a/app/internal_packages/link-tracking/assets/ic-modal-image@2x.png b/app/internal_packages/link-tracking/assets/ic-modal-image@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..670d0f913c2168b7c6701e35038af3521432361d GIT binary patch literal 6017 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 literal 0 HcmV?d00001 diff --git a/app/internal_packages/link-tracking/lib/link-tracking-button.jsx b/app/internal_packages/link-tracking/lib/link-tracking-button.jsx index c6cea9771..5909dde41 100644 --- a/app/internal_packages/link-tracking/lib/link-tracking-button.jsx +++ b/app/internal_packages/link-tracking/lib/link-tracking-button.jsx @@ -17,11 +17,6 @@ export default class LinkTrackingButton extends React.Component { ); } - _title(enabled) { - const dir = enabled ? 'Disable' : 'Enable'; - return `${dir} link tracking`; - } - _errorMessage(error) { if ( error instanceof APIError && @@ -35,12 +30,10 @@ export default class LinkTrackingButton extends React.Component { render() { return ( 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 LO|JU6dEoy57UB(| literal 0 HcmV?d00001 diff --git a/app/internal_packages/open-tracking/lib/open-tracking-button.jsx b/app/internal_packages/open-tracking/lib/open-tracking-button.jsx index e7a0e18bd..6ba440e0e 100644 --- a/app/internal_packages/open-tracking/lib/open-tracking-button.jsx +++ b/app/internal_packages/open-tracking/lib/open-tracking-button.jsx @@ -17,11 +17,6 @@ export default class OpenTrackingButton extends React.Component { ); } - _title(enabled) { - const dir = enabled ? 'Disable' : 'Enable'; - return `${dir} open tracking`; - } - _errorMessage(error) { if ( error instanceof APIError && @@ -40,12 +35,10 @@ export default class OpenTrackingButton extends React.Component { return ( { try { - await FeatureUsageStore.asyncUseFeature('contact-profiles', { + await FeatureUsageStore.markUsedOrUpgrade('contact-profiles', { usedUpHeader: 'All Contact Previews Used', usagePhrase: 'view contact profiles for', iconUrl: 'mailspring://participant-profile/assets/ic-contact-profile-modal@2x.png', diff --git a/app/internal_packages/send-later/lib/send-later-button.jsx b/app/internal_packages/send-later/lib/send-later-button.jsx index dadb32d2f..893488a08 100644 --- a/app/internal_packages/send-later/lib/send-later-button.jsx +++ b/app/internal_packages/send-later/lib/send-later-button.jsx @@ -63,7 +63,7 @@ class SendLaterButton extends Component { // already set to send later. if (!currentSendLaterDate) { try { - await FeatureUsageStore.asyncUseFeature('send-later', { + await FeatureUsageStore.markUsedOrUpgrade('send-later', { usedUpHeader: 'All Scheduled Sends Used', usagePhrase: 'schedule sending of', iconUrl: 'mailspring://send-later/assets/ic-send-later-modal@2x.png', diff --git a/app/internal_packages/send-reminders/lib/send-reminders-utils.es6 b/app/internal_packages/send-reminders/lib/send-reminders-utils.es6 index a5d9d251d..7a08da4c6 100644 --- a/app/internal_packages/send-reminders/lib/send-reminders-utils.es6 +++ b/app/internal_packages/send-reminders/lib/send-reminders-utils.es6 @@ -24,7 +24,7 @@ async function incrementMetadataUse(model, expiration) { return true; } try { - await FeatureUsageStore.asyncUseFeature(PLUGIN_ID, FEATURE_LEXICON); + await FeatureUsageStore.markUsedOrUpgrade(PLUGIN_ID, FEATURE_LEXICON); } catch (error) { if (error instanceof FeatureUsageStore.NoProAccessError) { return false; diff --git a/app/internal_packages/thread-snooze/lib/snooze-store.es6 b/app/internal_packages/thread-snooze/lib/snooze-store.es6 index 2c55694b3..eaafc2c4c 100644 --- a/app/internal_packages/thread-snooze/lib/snooze-store.es6 +++ b/app/internal_packages/thread-snooze/lib/snooze-store.es6 @@ -46,7 +46,7 @@ class SnoozeStore extends MailspringStore { _onSnoozeThreads = async (threads, snoozeDate, label) => { try { // ensure the user is authorized to use this feature - await FeatureUsageStore.asyncUseFeature('snooze', { + await FeatureUsageStore.markUsedOrUpgrade('snooze', { usedUpHeader: 'All Snoozes Used', usagePhrase: 'snooze', iconUrl: 'mailspring://thread-snooze/assets/ic-snooze-modal@2x.png', diff --git a/app/spec/stores/feature-usage-store-spec.es6 b/app/spec/stores/feature-usage-store-spec.es6 index 94338b12e..6f3f83a30 100644 --- a/app/spec/stores/feature-usage-store-spec.es6 +++ b/app/spec/stores/feature-usage-store-spec.es6 @@ -26,18 +26,18 @@ describe('FeatureUsageStore', function featureUsageStoreSpec() { IdentityStore._identity = this.oldIdent; }); - describe('_isUsable', () => { + describe('isUsable', () => { it("returns true if a feature hasn't met it's quota", () => { - expect(FeatureUsageStore._isUsable('is-usable')).toBe(true); + expect(FeatureUsageStore.isUsable('is-usable')).toBe(true); }); it('returns false if a feature is at its quota', () => { - expect(FeatureUsageStore._isUsable('not-usable')).toBe(false); + expect(FeatureUsageStore.isUsable('not-usable')).toBe(false); }); it('returns true if no quota is present for the feature', () => { spyOn(AppEnv, 'reportError'); - expect(FeatureUsageStore._isUsable('unsupported')).toBe(true); + expect(FeatureUsageStore.isUsable('unsupported')).toBe(true); expect(AppEnv.reportError).toHaveBeenCalled(); }); }); @@ -74,7 +74,7 @@ describe('FeatureUsageStore', function featureUsageStoreSpec() { }); it("marks the feature used if it's usable", async () => { - await FeatureUsageStore.asyncUseFeature('is-usable'); + await FeatureUsageStore.markUsedOrUpgrade('is-usable'); expect(FeatureUsageStore._markFeatureUsed).toHaveBeenCalled(); expect(FeatureUsageStore._markFeatureUsed.callCount).toBe(1); }); @@ -93,7 +93,7 @@ describe('FeatureUsageStore', function featureUsageStoreSpec() { IdentityStore._identity.featureUsage['not-usable'].quota = 10000; FeatureUsageStore._onModalClose(); }); - await FeatureUsageStore.asyncUseFeature('not-usable', this.lexicon); + await FeatureUsageStore.markUsedOrUpgrade('not-usable', this.lexicon); expect(Actions.openModal).toHaveBeenCalled(); expect(Actions.openModal.calls.length).toBe(1); }); @@ -103,7 +103,7 @@ describe('FeatureUsageStore', function featureUsageStoreSpec() { IdentityStore._identity.featureUsage['not-usable'].quota = 10000; FeatureUsageStore._onModalClose(); }); - await FeatureUsageStore.asyncUseFeature('not-usable', this.lexicon); + await FeatureUsageStore.markUsedOrUpgrade('not-usable', this.lexicon); expect(Actions.openModal).toHaveBeenCalled(); expect(Actions.openModal.calls.length).toBe(1); const component = Actions.openModal.calls[0].args[0].component; @@ -122,7 +122,7 @@ describe('FeatureUsageStore', function featureUsageStoreSpec() { FeatureUsageStore._onModalClose(); }); try { - await FeatureUsageStore.asyncUseFeature('not-usable', this.lexicon); + await FeatureUsageStore.markUsedOrUpgrade('not-usable', this.lexicon); } catch (err) { expect(err instanceof FeatureUsageStore.NoProAccessError).toBe(true); caughtError = true; diff --git a/app/src/components/metadata-composer-toggle-button.jsx b/app/src/components/metadata-composer-toggle-button.jsx index 605152c14..a27a487a5 100644 --- a/app/src/components/metadata-composer-toggle-button.jsx +++ b/app/src/components/metadata-composer-toggle-button.jsx @@ -1,4 +1,11 @@ -import { React, PropTypes, Actions, MailspringAPIRequest, APIError } from 'mailspring-exports'; +import { + React, + PropTypes, + Actions, + MailspringAPIRequest, + APIError, + FeatureUsageStore, +} from 'mailspring-exports'; import { RetinaImg } from 'mailspring-component-kit'; import classnames from 'classnames'; import _ from 'underscore'; @@ -7,34 +14,33 @@ export default class MetadataComposerToggleButton extends React.Component { static displayName = 'MetadataComposerToggleButton'; static propTypes = { - title: PropTypes.func.isRequired, iconUrl: PropTypes.string, iconName: PropTypes.string, pluginId: PropTypes.string.isRequired, pluginName: PropTypes.string.isRequired, metadataEnabledValue: PropTypes.object.isRequired, - stickyToggle: PropTypes.bool, errorMessage: PropTypes.func.isRequired, draft: PropTypes.object.isRequired, session: PropTypes.object.isRequired, }; - static defaultProps = { - stickyToggle: false, - }; - constructor(props) { super(props); this.state = { pending: false, + onByDefaultButUsedUp: false, }; } componentWillMount() { if (this._isEnabledByDefault() && !this._isEnabled()) { - this._setEnabled(true); + if (FeatureUsageStore.isUsable(this.props.pluginId)) { + this._setEnabled(true); + } else { + this.setState({ onByDefaultButUsedUp: true }); + } } } @@ -61,11 +67,9 @@ export default class MetadataComposerToggleButton extends React.Component { try { session.changes.addPluginMetadata(pluginId, metadataValue); } catch (error) { - const { stickyToggle, errorMessage } = this.props; + const { errorMessage } = this.props; - if (stickyToggle) { - AppEnv.config.set(this._configKey(), false); - } + AppEnv.config.set(this._configKey(), false); let title = 'Error'; if (!(error instanceof APIError)) { @@ -82,23 +86,41 @@ export default class MetadataComposerToggleButton extends React.Component { this.setState({ pending: false }); } - _onClick = () => { + _onClick = async () => { + const { pluginName, pluginId } = this.props; + let nextEnabled = !this._isEnabled(); + const dir = nextEnabled ? 'Enabled' : 'Disabled'; + if (this.state.pending) { return; } - const enabled = this._isEnabled(); - const dir = enabled ? 'Disabled' : 'Enabled'; - Actions.recordUserEvent(`${this.props.pluginName} ${dir}`); - if (this.props.stickyToggle) { - AppEnv.config.set(this._configKey(), !enabled); + // note: we don't actually increment the usage counters until you /send/ + // the message with link and open tracking, we just display the notice + + if (nextEnabled && !FeatureUsageStore.isUsable(pluginId)) { + try { + await FeatureUsageStore.displayUpgradeModal(pluginId, { + usedUpHeader: `All used up!`, + usagePhrase: 'get open and click notifications for', + iconUrl: `mailspring://${pluginId}/assets/ic-modal-image@2x.png`, + }); + } catch (err) { + // user does not have access to this feature + if (this.state.onByDefaultButUsedUp) { + this.setState({ onByDefaultButUsedUp: false }); + } + nextEnabled = false; + } } - this._setEnabled(!enabled); + + Actions.recordUserEvent(`${pluginName} ${dir}`); + AppEnv.config.set(this._configKey(), nextEnabled); + this._setEnabled(nextEnabled); }; render() { const enabled = this._isEnabled(); - const title = this.props.title(enabled); const className = classnames({ btn: true, @@ -115,7 +137,17 @@ export default class MetadataComposerToggleButton extends React.Component { } return ( - ); diff --git a/app/src/flux/stores/feature-usage-store.jsx b/app/src/flux/stores/feature-usage-store.jsx index c15c42149..5879b7fdc 100644 --- a/app/src/flux/stores/feature-usage-store.jsx +++ b/app/src/flux/stores/feature-usage-store.jsx @@ -68,7 +68,7 @@ class FeatureUsageStore extends MailspringStore { this._usub(); } - displayUpgradeModal(feature, { lexicon }) { + displayUpgradeModal(feature, lexicon) { const { headerText, rechargeText } = this._modalText(feature, lexicon); Actions.openModal({ @@ -83,25 +83,45 @@ class FeatureUsageStore extends MailspringStore { /> ), }); - } - - async asyncUseFeature(feature, lexicon = {}) { - if (this._isUsable(feature)) { - this._markFeatureUsed(feature); - return true; - } - - this.displayUpgradeModal(feature, { lexicon }); return new Promise((resolve, reject) => { this._waitForModalClose.push({ resolve, reject, feature }); }); } + isUsable(feature) { + const { usedInPeriod, quota } = this._dataForFeature(feature); + if (!quota) { + return true; + } + return usedInPeriod < quota; + } + + async markUsedOrUpgrade(feature, lexicon = {}) { + if (!this.isUsable(feature)) { + // throws if the user declines + await this.displayUpgradeModal(feature, lexicon); + } + this.markUsed(feature); + } + + markUsed(feature) { + const next = JSON.parse(JSON.stringify(IdentityStore.identity())); + console.log('Next:'); + console.log(JSON.stringify(next)); + + if (next.featureUsage[feature]) { + next.featureUsage[feature].usedInPeriod += 1; + IdentityStore.saveIdentity(next); + } + if (!UsageRecordedServerSide.includes(feature)) { + Actions.queueTask(new SendFeatureUsageEventTask({ feature })); + } + } + _onModalClose = async () => { for (const { feature, resolve, reject } of this._waitForModalClose) { - if (this._isUsable(feature)) { - this._markFeatureUsed(feature); + if (this.isUsable(feature)) { resolve(); } else { reject(new NoProAccessError(feature)); @@ -145,25 +165,6 @@ class FeatureUsageStore extends MailspringStore { } return usage[feature]; } - - _isUsable(feature) { - const { usedInPeriod, quota } = this._dataForFeature(feature); - if (!quota) { - return true; - } - return usedInPeriod < quota; - } - - _markFeatureUsed(feature) { - const next = JSON.parse(JSON.stringify(IdentityStore.identity())); - if (next.featureUsage[feature]) { - next.featureUsage[feature].usedInPeriod += 1; - IdentityStore.saveIdentity(next); - } - if (!UsageRecordedServerSide.includes(feature)) { - Actions.queueTask(new SendFeatureUsageEventTask({ feature })); - } - } } export default new FeatureUsageStore(); diff --git a/app/src/flux/tasks/send-draft-task.es6 b/app/src/flux/tasks/send-draft-task.es6 index 0688bf56a..b9876cf39 100644 --- a/app/src/flux/tasks/send-draft-task.es6 +++ b/app/src/flux/tasks/send-draft-task.es6 @@ -1,6 +1,7 @@ /* eslint global-require: 0 */ import url from 'url'; import AccountStore from '../stores/account-store'; +import FeatureUsageStore from '../stores/feature-usage-store'; import Task from './task'; import Actions from '../actions'; import Attributes from '../attributes'; @@ -57,13 +58,11 @@ export default class SendDraftTask extends Task { } isOpenTrackingEnabled() { - const metadata = this.draft.metadataForPluginId('open-tracking'); - return metadata && Object.keys(metadata).length > 0; + return !!this.draft.metadataForPluginId('open-tracking'); } isLinkTrackingEnabled() { - const metadata = this.draft.metadataForPluginId('link-tracking'); - return metadata && Object.keys(metadata).length > 0; + return !!this.draft.metadataForPluginId('link-tracking'); } label() { @@ -97,6 +96,14 @@ export default class SendDraftTask extends Task { if (AppEnv.config.get('core.sending.sounds') && !this.silent) { SoundRegistry.playSound('send'); } + + // Fire off events to record the usage of open and link tracking + if (this.isOpenTrackingEnabled()) { + FeatureUsageStore.markUsed('open-tracking'); + } + if (this.isLinkTrackingEnabled()) { + FeatureUsageStore.markUsed('link-tracking'); + } } onError({ key, debuginfo }) { diff --git a/app/static/images/toolbar/tiny-warning-sign@2x.png b/app/static/images/toolbar/tiny-warning-sign@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..de6ee6acd2a17e7aabec22e5f3b3cfe6e8d85ea0 GIT binary patch literal 1083 zcmV-B1jPG^P)00004XF*Lt006O% z3;baP000B>NklQmppYX6BG*5&0lsTIk=$4u)Qic$bstgd^H(MHNh!LL&4IWNw zZw&jxNm;25kNW=&csk11(D&SUc-&{Ql~}S}mIVy#{*vfSM$#&%E1A>?CX;+;)YWO- z;Gm2*6`9x4E{3PUj{`hP;Kjoicr*fDsIPba?Cf+#-@bM6*4C}g8R$9iIf&N}KF*@I znh>}DLg83Q?}0bhu64rDy>zL|GZ~Ym2)`+3ylinHaD0)(=^@GBl;z7KIvd;CT%Iu& zB3mmRiwK1XB@N^43J&@bD%J%nh?<6IT^lXs$z;a9EqLAHkzzk34Z}Q&2leH;Od>HZ zVWS09HePqeTJ(rT4O=rHXJ0od?@TE@1-xd~P;8s7N_5kBC~BCUAHitS4`KZBc&epj z4#+9^K3<|o=j!1|a(4I5mnIZjSdPxxdSnhsp;r=C+&|cfDkeKn^a4ff264F`YQnPrWD;O3`V0KBET`6OsYEIjaQ#1718oY z+DYq6xd%6-Y6|naCr;o;zzpLj-Q6-?7|>&Jp2*GiU6>b{(;n|X8&hrSBX};AaxOME zJI{N2UA(qtjgw8MBXa)Q$LK)kUtvQ9A)sMwq$0XDc%3RNY;*JV_u5)LNm~}BnL|Tb zHEdT$ak$Q7fn2wJE8X#>3PQ~!-yVH|Pw}Opq&Z?ogSzsD$-^=jEndqD$`p*dQYk&m zWVHIm4Hx@bTXn_rzCbWA;Y=qf%ymCYa|7hDA$hpK?K9%%ChmKmNT20ebaJ`z3ySgO z#fu%|#jWva|48rXhNpbPuE%(rVMp!FW!j_NPG225#Nh~(2II`;&GzUk3G--1*x1wK zz8Ac6jG7;sP-m^K6j%#-)!Q2pd7RCj7zd z4M|RO#>z!GzY*?P?abvwzNFDnB{5nJPz%#YZ937zEy50l$iWD;UPG8Z3s^Zm