mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Wired new analytics settings for tracking email and sources
refs https://github.com/TryGhost/Team/issues/2168 - wires new source tracking setting for members to UI - splits the new analytics page settings to its own component, cleaned up the files and wired up the settings upstream
This commit is contained in:
parent
cca0f7d7dc
commit
9503ca1c7b
6 changed files with 129 additions and 238 deletions
78
ghost/admin/app/components/settings/analytics.hbs
Normal file
78
ghost/admin/app/components/settings/analytics.hbs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<div class="gh-main-section">
|
||||||
|
<h4 class="gh-main-section-header small bn">Newsletters</h4>
|
||||||
|
<section class="gh-expandable">
|
||||||
|
<div class="gh-expandable-block">
|
||||||
|
<div class="gh-expandable-header">
|
||||||
|
<div>
|
||||||
|
<h4 class="gh-expandable-title">Track newsletter opens</h4>
|
||||||
|
<p class="gh-expandable-description">
|
||||||
|
Record when member opens a newsletter email
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="for-switch">
|
||||||
|
<label class="switch" for="email-track-opens">
|
||||||
|
<input
|
||||||
|
id="email-track-opens"
|
||||||
|
type="checkbox"
|
||||||
|
checked={{this.settings.emailTrackOpens}}
|
||||||
|
{{on "change" this.toggleEmailTrackOpens}}
|
||||||
|
data-test-checkbox="email-track-opens"
|
||||||
|
>
|
||||||
|
<span class="input-toggle-component"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gh-expandable-block">
|
||||||
|
<div class="gh-expandable-header">
|
||||||
|
<div>
|
||||||
|
<h4 class="gh-expandable-title">Track newsletter clicks</h4>
|
||||||
|
<p class="gh-expandable-description">
|
||||||
|
Record when a member clicks on any link in a newsletter email
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="for-switch">
|
||||||
|
<label class="switch" for="email-track-clicks">
|
||||||
|
<input
|
||||||
|
id="email-track-clicks"
|
||||||
|
type="checkbox"
|
||||||
|
{{on "change" this.toggleEmailTrackClicks}}
|
||||||
|
checked={{this.settings.emailTrackClicks}}
|
||||||
|
data-test-checkbox="email-track-clicks"
|
||||||
|
>
|
||||||
|
<span class="input-toggle-component"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gh-main-section">
|
||||||
|
<h4 class="gh-main-section-header small bn">Sources</h4>
|
||||||
|
<section class="gh-expandable">
|
||||||
|
<div class="gh-expandable-block">
|
||||||
|
<div class="gh-expandable-header">
|
||||||
|
<div>
|
||||||
|
<h4 class="gh-expandable-title">Track member sources</h4>
|
||||||
|
<p class="gh-expandable-description">
|
||||||
|
Record the post/page and referring domain for signups and subscriptions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="for-switch">
|
||||||
|
<label class="switch" for="members-track-sources">
|
||||||
|
<input
|
||||||
|
id="members-track-sources"
|
||||||
|
type="checkbox"
|
||||||
|
checked={{this.settings.membersTrackSources}}
|
||||||
|
data-test-checkbox="members-track-sources"
|
||||||
|
{{on "change" this.toggleMembersTrackSources}}
|
||||||
|
>
|
||||||
|
<span class="input-toggle-component"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
33
ghost/admin/app/components/settings/analytics.js
Normal file
33
ghost/admin/app/components/settings/analytics.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
|
||||||
|
export default class Analytics extends Component {
|
||||||
|
@service config;
|
||||||
|
@service settings;
|
||||||
|
@service feature;
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleEmailTrackOpens(event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this.settings.emailTrackOpens = !this.settings.emailTrackOpens;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleEmailTrackClicks(event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this.settings.emailTrackClicks = !this.settings.emailTrackClicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleMembersTrackSources(event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this.settings.membersTrackSources = !this.settings.membersTrackSources;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,170 +1,13 @@
|
||||||
import classic from 'ember-classic-decorator';
|
|
||||||
import {action, computed} from '@ember/object';
|
|
||||||
import {inject as service} from '@ember/service';
|
|
||||||
/* eslint-disable ghost/ember/alias-model-in-controller */
|
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import generatePassword from 'ghost-admin/utils/password-generator';
|
import {inject as service} from '@ember/service';
|
||||||
import {
|
|
||||||
IMAGE_EXTENSIONS,
|
|
||||||
IMAGE_MIME_TYPES
|
|
||||||
} from 'ghost-admin/components/gh-image-uploader';
|
|
||||||
import {TrackedObject} from 'tracked-built-ins';
|
|
||||||
import {run} from '@ember/runloop';
|
|
||||||
import {task} from 'ember-concurrency';
|
import {task} from 'ember-concurrency';
|
||||||
import {tracked} from '@glimmer/tracking';
|
|
||||||
|
|
||||||
function randomPassword() {
|
|
||||||
let word = generatePassword(6);
|
|
||||||
let randomN = Math.floor(Math.random() * 1000);
|
|
||||||
|
|
||||||
return word + randomN;
|
|
||||||
}
|
|
||||||
|
|
||||||
@classic
|
|
||||||
export default class AnalyticsController extends Controller {
|
export default class AnalyticsController extends Controller {
|
||||||
@service config;
|
|
||||||
@service ghostPaths;
|
|
||||||
@service notifications;
|
|
||||||
@service session;
|
|
||||||
@service settings;
|
@service settings;
|
||||||
@service frontend;
|
|
||||||
@service ui;
|
|
||||||
|
|
||||||
@tracked scratchValues = new TrackedObject();
|
@task({drop: true})
|
||||||
|
*saveSettings() {
|
||||||
availableTimezones = this.config.availableTimezones;
|
const response = yield this.settings.save();
|
||||||
imageExtensions = IMAGE_EXTENSIONS;
|
return response;
|
||||||
imageMimeTypes = IMAGE_MIME_TYPES;
|
|
||||||
|
|
||||||
@computed('config.blogUrl', 'settings.publicHash')
|
|
||||||
get privateRSSUrl() {
|
|
||||||
let blogUrl = this.config.blogUrl;
|
|
||||||
let publicHash = this.settings.publicHash;
|
|
||||||
|
|
||||||
return `${blogUrl}/${publicHash}/rss`;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
save() {
|
|
||||||
this.saveTask.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
setTimezone(timezone) {
|
|
||||||
this.settings.timezone = timezone.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
removeImage(image) {
|
|
||||||
// setting `null` here will error as the server treats it as "null"
|
|
||||||
this.settings[image] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a file selection dialog - Triggered by "Upload Image" buttons,
|
|
||||||
* searches for the hidden file input within the .gh-setting element
|
|
||||||
* containing the clicked button then simulates a click
|
|
||||||
* @param {MouseEvent} event - MouseEvent fired by the button click
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
triggerFileDialog(event) {
|
|
||||||
event?.target.closest('.gh-setting-action')?.querySelector('input[type="file"]')?.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired after an image upload completes
|
|
||||||
* @param {string} property - Property name to be set on `this.settings`
|
|
||||||
* @param {UploadResult[]} results - Array of UploadResult objects
|
|
||||||
* @return {string} The URL that was set on `this.settings.property`
|
|
||||||
*/
|
|
||||||
@action
|
|
||||||
imageUploaded(property, results) {
|
|
||||||
if (results[0]) {
|
|
||||||
return this.settings[property] = results[0].url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
toggleIsPrivate(isPrivate) {
|
|
||||||
let settings = this.settings;
|
|
||||||
|
|
||||||
settings.isPrivate = isPrivate;
|
|
||||||
settings.errors.remove('password');
|
|
||||||
|
|
||||||
let changedAttrs = settings.changedAttributes();
|
|
||||||
|
|
||||||
// set a new random password when isPrivate is enabled
|
|
||||||
if (isPrivate && changedAttrs.isPrivate) {
|
|
||||||
settings.password = randomPassword();
|
|
||||||
|
|
||||||
// reset the password when isPrivate is disabled
|
|
||||||
} else if (changedAttrs.password) {
|
|
||||||
settings.password = changedAttrs.password[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
setScratchValue(property, value) {
|
|
||||||
this.scratchValues[property] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearScratchValues() {
|
|
||||||
this.scratchValues = new TrackedObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
_deleteTheme() {
|
|
||||||
let theme = this.store.peekRecord('theme', this.themeToDelete.name);
|
|
||||||
|
|
||||||
if (!theme) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return theme.destroyRecord().catch((error) => {
|
|
||||||
this.notifications.showAPIError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@task
|
|
||||||
*saveTask() {
|
|
||||||
let notifications = this.notifications;
|
|
||||||
let config = this.config;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let changedAttrs = this.settings.changedAttributes();
|
|
||||||
let settings = yield this.settings.save();
|
|
||||||
|
|
||||||
this.clearScratchValues();
|
|
||||||
|
|
||||||
config.set('blogTitle', settings.title);
|
|
||||||
|
|
||||||
if (changedAttrs.password) {
|
|
||||||
this.frontend.loginIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
// this forces the document title to recompute after a blog title change
|
|
||||||
this.ui.updateDocumentTitle();
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
} catch (error) {
|
|
||||||
if (error) {
|
|
||||||
notifications.showAPIError(error, {key: 'settings.save'});
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
saveViaKeyboard(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// trigger any set-on-blur actions
|
|
||||||
const focusedElement = document.activeElement;
|
|
||||||
focusedElement?.blur();
|
|
||||||
|
|
||||||
// schedule save for when set-on-blur actions have finished
|
|
||||||
run.schedule('actions', this, function () {
|
|
||||||
focusedElement?.focus();
|
|
||||||
this.saveTask.perform();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ export default Model.extend(ValidationEngine, {
|
||||||
membersSupportAddress: attr('string'),
|
membersSupportAddress: attr('string'),
|
||||||
membersMonthlyPriceId: attr('string'),
|
membersMonthlyPriceId: attr('string'),
|
||||||
membersYearlyPriceId: attr('string'),
|
membersYearlyPriceId: attr('string'),
|
||||||
|
membersTrackSources: attr('boolean'),
|
||||||
stripeSecretKey: attr('string'),
|
stripeSecretKey: attr('string'),
|
||||||
stripePublishableKey: attr('string'),
|
stripePublishableKey: attr('string'),
|
||||||
stripePlans: attr('json-string'),
|
stripePlans: attr('json-string'),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<section class="gh-canvas" {{on-key "cmd+s" this.saveViaKeyboard}}>
|
<section class="gh-canvas">
|
||||||
<GhCanvasHeader class="gh-canvas-header">
|
<GhCanvasHeader class="gh-canvas-header">
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<div class="gh-canvas-breadcrumb">
|
<div class="gh-canvas-breadcrumb">
|
||||||
|
@ -12,85 +12,20 @@
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<section class="view-actions">
|
<section class="view-actions">
|
||||||
<GhTaskButton @buttonText="Save" @task={{this.saveTask}} @class="gh-btn gh-btn-primary gh-btn-icon" data-test-button="save" />
|
<GhTaskButton
|
||||||
|
@buttonText="Save"
|
||||||
|
@task={{this.saveSettings}}
|
||||||
|
@successText="Saved"
|
||||||
|
@runningText="Saving"
|
||||||
|
{{on-key "cmd+s"}}
|
||||||
|
@class="gh-btn gh-btn-primary gh-btn-icon"
|
||||||
|
data-test-button="save-analytics-settings"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</GhCanvasHeader>
|
</GhCanvasHeader>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="gh-main-section">
|
<Settings::Analytics />
|
||||||
<h4 class="gh-main-section-header small bn">Newsletters</h4>
|
|
||||||
<section class="gh-expandable">
|
|
||||||
<div class="gh-expandable-block">
|
|
||||||
<div class="gh-expandable-header">
|
|
||||||
<div>
|
|
||||||
<h4 class="gh-expandable-title">Track newsletter opens</h4>
|
|
||||||
<p class="gh-expandable-description">
|
|
||||||
Record when member opens a newsletter email
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="for-switch">
|
|
||||||
<label class="switch" for="settings-private">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={{this.settings.isPrivate}}
|
|
||||||
id="settings-private"
|
|
||||||
data-test-private-checkbox
|
|
||||||
>
|
|
||||||
<span class="input-toggle-component"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gh-expandable-block">
|
|
||||||
<div class="gh-expandable-header">
|
|
||||||
<div>
|
|
||||||
<h4 class="gh-expandable-title">Track newsletter clicks</h4>
|
|
||||||
<p class="gh-expandable-description">
|
|
||||||
Record when a member clicks on any link in a newsletter email
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="for-switch">
|
|
||||||
<label class="switch" for="settings-private">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={{this.settings.isPrivate}}
|
|
||||||
data-test-private-checkbox
|
|
||||||
>
|
|
||||||
<span class="input-toggle-component"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gh-main-section">
|
|
||||||
<h4 class="gh-main-section-header small bn">Sources</h4>
|
|
||||||
<section class="gh-expandable">
|
|
||||||
<div class="gh-expandable-block">
|
|
||||||
<div class="gh-expandable-header">
|
|
||||||
<div>
|
|
||||||
<h4 class="gh-expandable-title">Track member sources</h4>
|
|
||||||
<p class="gh-expandable-description">
|
|
||||||
Record the post/page and referring domain for signups and subscriptions
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="for-switch">
|
|
||||||
<label class="switch" for="settings-private">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={{this.settings.isPrivate}}
|
|
||||||
data-test-private-checkbox
|
|
||||||
>
|
|
||||||
<span class="input-toggle-component"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{outlet}}
|
{{outlet}}
|
|
@ -71,6 +71,7 @@ export default [
|
||||||
setting('members', 'stripe_connect_account_id', null),
|
setting('members', 'stripe_connect_account_id', null),
|
||||||
setting('members', 'members_monthly_price_id', null),
|
setting('members', 'members_monthly_price_id', null),
|
||||||
setting('members', 'members_yearly_price_id', null),
|
setting('members', 'members_yearly_price_id', null),
|
||||||
|
setting('members', 'members_track_sources', null),
|
||||||
|
|
||||||
// PORTAL
|
// PORTAL
|
||||||
setting('portal', 'portal_name', 'true'),
|
setting('portal', 'portal_name', 'true'),
|
||||||
|
|
Loading…
Add table
Reference in a new issue