mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -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 generatePassword from 'ghost-admin/utils/password-generator';
|
||||
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 {inject as service} from '@ember/service';
|
||||
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 {
|
||||
@service config;
|
||||
@service ghostPaths;
|
||||
@service notifications;
|
||||
@service session;
|
||||
@service settings;
|
||||
@service frontend;
|
||||
@service ui;
|
||||
|
||||
@tracked scratchValues = new TrackedObject();
|
||||
|
||||
availableTimezones = this.config.availableTimezones;
|
||||
imageExtensions = IMAGE_EXTENSIONS;
|
||||
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();
|
||||
});
|
||||
@task({drop: true})
|
||||
*saveSettings() {
|
||||
const response = yield this.settings.save();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ export default Model.extend(ValidationEngine, {
|
|||
membersSupportAddress: attr('string'),
|
||||
membersMonthlyPriceId: attr('string'),
|
||||
membersYearlyPriceId: attr('string'),
|
||||
membersTrackSources: attr('boolean'),
|
||||
stripeSecretKey: attr('string'),
|
||||
stripePublishableKey: attr('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">
|
||||
<div class="flex flex-column">
|
||||
<div class="gh-canvas-breadcrumb">
|
||||
|
@ -12,85 +12,20 @@
|
|||
</h2>
|
||||
</div>
|
||||
<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>
|
||||
</GhCanvasHeader>
|
||||
|
||||
<div>
|
||||
<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="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>
|
||||
|
||||
<Settings::Analytics />
|
||||
</div>
|
||||
</section>
|
||||
{{outlet}}
|
|
@ -71,6 +71,7 @@ export default [
|
|||
setting('members', 'stripe_connect_account_id', null),
|
||||
setting('members', 'members_monthly_price_id', null),
|
||||
setting('members', 'members_yearly_price_id', null),
|
||||
setting('members', 'members_track_sources', null),
|
||||
|
||||
// PORTAL
|
||||
setting('portal', 'portal_name', 'true'),
|
||||
|
|
Loading…
Add table
Reference in a new issue