0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Removed GA feature flags (#14915)

refs https://github.com/TryGhost/Team/issues/1616

- Removed all GA feature flags
- Removed `tweetGridCard` alpha flag
- Changes to `members-api` and `members-importer` packages: https://github.com/TryGhost/Members/compare/%40tryghost/members-api%408.1.1...%40tryghost/members-api%408.1.2
This commit is contained in:
Simon Backx 2022-05-26 09:54:30 +02:00 committed by GitHub
parent 939496487d
commit ad349bb3a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 667 additions and 2080 deletions

View file

@ -3,12 +3,11 @@
// //
// Returns a string of the tiers with access to the post. // Returns a string of the tiers with access to the post.
// By default, tiers are separated by commas. // By default, tiers are separated by commas.
const {labs} = require('../services/proxy');
const {SafeString, escapeExpression} = require('../services/handlebars'); const {SafeString, escapeExpression} = require('../services/handlebars');
const isString = require('lodash/isString'); const isString = require('lodash/isString');
function tiers(options = {}) { module.exports = function tiers(options = {}) {
options = options || {}; options = options || {};
options.hash = options.hash || {}; options.hash = options.hash || {};
@ -42,18 +41,4 @@ function tiers(options = {}) {
} }
return new SafeString(output); return new SafeString(output);
}
module.exports = function productsLabsWrapper() {
let self = this;
let args = arguments;
return labs.enabledHelper({
flagKey: 'multipleProducts',
flagName: 'Tiers',
helperName: 'tiers',
helpUrl: 'https://ghost.org/docs/themes/'
}, () => {
return tiers.apply(self, args);
});
}; };

View file

@ -1,48 +1,24 @@
const debug = require('@tryghost/debug')('services:routing:controllers:unsubscribe'); const debug = require('@tryghost/debug')('services:routing:controllers:unsubscribe');
const path = require('path');
const url = require('url'); const url = require('url');
const urlUtils = require('../../../../shared/url-utils'); const urlUtils = require('../../../../shared/url-utils');
const megaService = require('../../../../server/services/mega');
const renderer = require('../../rendering');
const labs = require('../../../../shared/labs');
module.exports = async function unsubscribeController(req, res) { module.exports = async function unsubscribeController(req, res) {
debug('unsubscribeController'); debug('unsubscribeController');
if (labs.isSet('multipleNewslettersUI')) { const {query} = url.parse(req.url, true);
const {query} = url.parse(req.url, true);
if (!query || !query.uuid) { if (!query || !query.uuid) {
res.writeHead(400); res.writeHead(400);
return res.end('Email address not found.'); return res.end('Email address not found.');
}
const redirectUrl = new URL(urlUtils.urlFor('home', true));
redirectUrl.searchParams.append('uuid', query.uuid);
if (query.newsletter) {
redirectUrl.searchParams.append('newsletter', query.newsletter);
}
redirectUrl.searchParams.append('action', 'unsubscribe');
return res.redirect(302, redirectUrl.href);
} }
let data = {}; const redirectUrl = new URL(urlUtils.urlFor('home', true));
redirectUrl.searchParams.append('uuid', query.uuid);
try { if (query.newsletter) {
data.member = await megaService.mega.handleUnsubscribeRequest(req); redirectUrl.searchParams.append('newsletter', query.newsletter);
} catch (err) {
data.error = err.message;
} }
redirectUrl.searchParams.append('action', 'unsubscribe');
const templateName = 'unsubscribe'; return res.redirect(302, redirectUrl.href);
res.routerOptions = {
type: 'custom',
templates: templateName,
defaultTemplate: path.resolve(__dirname, '../../../views/', templateName)
};
return renderer.renderer(req, res, data);
}; };

View file

@ -13,7 +13,6 @@ const url = require('../utils/url');
const utils = require('../../../index'); const utils = require('../../../index');
const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta; const postsMetaSchema = require('../../../../../../data/schema').tables.posts_meta;
const labsService = require('../../../../../../../shared/labs');
const getPostServiceInstance = require('../../../../../../services/posts/posts-service'); const getPostServiceInstance = require('../../../../../../services/posts/posts-service');
const postsService = getPostServiceInstance(); const postsService = getPostServiceInstance();
@ -35,9 +34,7 @@ module.exports = async (model, frame, options = {}) => {
extraAttrs.forPost(frame, model, jsonModel); extraAttrs.forPost(frame, model, jsonModel);
// Attach tiers to custom nql visibility filter // Attach tiers to custom nql visibility filter
if (labsService.isSet('multipleProducts') if (jsonModel.visibility) {
&& jsonModel.visibility
) {
if (['members', 'public'].includes(jsonModel.visibility) && jsonModel.tiers) { if (['members', 'public'].includes(jsonModel.visibility) && jsonModel.tiers) {
jsonModel.tiers = tiersData || []; jsonModel.tiers = tiersData || [];
} }

View file

@ -1,7 +1,6 @@
//@ts-check //@ts-check
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members'); const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members');
const {unparse} = require('@tryghost/members-csv'); const {unparse} = require('@tryghost/members-csv');
const labsService = require('../../../../../../shared/labs');
module.exports = { module.exports = {
browse: createSerializer('browse', paginatedMembers), browse: createSerializer('browse', paginatedMembers),
@ -142,19 +141,17 @@ function serializeMember(member, options) {
delete subscription.price.product; delete subscription.price.product;
} }
if (labsService.isSet('multipleNewsletters')) { if (json.newsletters) {
if (json.newsletters) { serialized.newsletters = json.newsletters
serialized.newsletters = json.newsletters .filter(newsletter => newsletter.status === 'active')
.filter(newsletter => newsletter.status === 'active') .sort((a, b) => {
.sort((a, b) => { return a.sort_order - b.sort_order;
return a.sort_order - b.sort_order; });
}); }
} // override the `subscribed` param to mean "subscribed to any active newsletter"
// override the `subscribed` param to mean "subscribed to any active newsletter" serialized.subscribed = false;
serialized.subscribed = false; if (Array.isArray(serialized.newsletters) && serialized.newsletters.length > 0) {
if (Array.isArray(serialized.newsletters) && serialized.newsletters.length > 0) { serialized.subscribed = true;
serialized.subscribed = true;
}
} }
return serialized; return serialized;

View file

@ -2,7 +2,6 @@ const _ = require('lodash');
const Promise = require('bluebird'); const Promise = require('bluebird');
const debug = require('@tryghost/debug')('mega'); const debug = require('@tryghost/debug')('mega');
const tpl = require('@tryghost/tpl'); const tpl = require('@tryghost/tpl');
const url = require('url');
const moment = require('moment'); const moment = require('moment');
const ObjectID = require('bson-objectid'); const ObjectID = require('bson-objectid');
const errors = require('@tryghost/errors'); const errors = require('@tryghost/errors');
@ -15,7 +14,6 @@ const jobsService = require('../jobs');
const db = require('../../data/db'); const db = require('../../data/db');
const models = require('../../models'); const models = require('../../models');
const postEmailSerializer = require('./post-email-serializer'); const postEmailSerializer = require('./post-email-serializer');
const labs = require('../../../shared/labs');
const {getSegmentsFromHtml} = require('./segment-parser'); const {getSegmentsFromHtml} = require('./segment-parser');
// Used to listen to email.added and email.edited model events originally, I think to offload this - ideally would just use jobs now if possible // Used to listen to email.added and email.edited model events originally, I think to offload this - ideally would just use jobs now if possible
@ -267,58 +265,6 @@ const retryFailedEmail = async (emailModel) => {
}); });
}; };
/**
* handleUnsubscribeRequest
*
* Takes a request/response pair and reads the `unsubscribe` query parameter,
* using the content to update the members service to set the `subscribed` flag
* to false on the member
*
* If any operation fails, or the request is invalid the function will error - so using
* as middleware should consider wrapping with `try/catch`
*
* @param {Request} req
* @returns {Promise<void>}
*/
async function handleUnsubscribeRequest(req) {
if (!req.url) {
throw new errors.BadRequestError({
message: 'Email address not found.'
});
}
const {query} = url.parse(req.url, true);
if (!query || !query.uuid) {
throw new errors.BadRequestError({
message: (query.preview ? 'Unsubscribe preview' : 'Email address not found.')
});
}
const member = await membersService.api.members.get({
uuid: query.uuid
});
if (!member) {
throw new errors.BadRequestError({
message: 'Email address not found.'
});
}
try {
let memberData = {subscribed: false};
if (labs.isSet('multipleNewsletters')) {
memberData.newsletters = [];
}
const memberModel = await membersService.api.members.update(memberData, {id: member.id});
return memberModel.toJSON();
} catch (err) {
throw new errors.InternalServerError({
err,
message: 'Failed to unsubscribe this email address'
});
}
}
async function pendingEmailHandler(emailModel, options) { async function pendingEmailHandler(emailModel, options) {
// CASE: do not send email if we import a database // CASE: do not send email if we import a database
// TODO: refactor post.published events to never fire on importing // TODO: refactor post.published events to never fire on importing
@ -590,7 +536,6 @@ module.exports = {
addEmail, addEmail,
retryFailedEmail, retryFailedEmail,
sendTestEmail, sendTestEmail,
handleUnsubscribeRequest,
// NOTE: below are only exposed for testing purposes // NOTE: below are only exposed for testing purposes
_transformEmailRecipientFilter: transformEmailRecipientFilter, _transformEmailRecipientFilter: transformEmailRecipientFilter,
_partitionMembersBySegment: partitionMembersBySegment, _partitionMembersBySegment: partitionMembersBySegment,

View file

@ -1,5 +1,3 @@
const labsService = require('../../../shared/labs');
function formatNewsletterResponse(newsletters) { function formatNewsletterResponse(newsletters) {
return newsletters.map(({id, name, description, sort_order: sortOrder}) => { return newsletters.map(({id, name, description, sort_order: sortOrder}) => {
return {id, name, description, sort_order: sortOrder}; return {id, name, description, sort_order: sortOrder};
@ -20,7 +18,7 @@ module.exports.formattedMemberResponse = function formattedMemberResponse(member
subscriptions: member.subscriptions || [], subscriptions: member.subscriptions || [],
paid: member.status !== 'free' paid: member.status !== 'free'
}; };
if (member.newsletters && labsService.isSet('multipleNewsletters')) { if (member.newsletters) {
data.newsletters = formatNewsletterResponse(member.newsletters); data.newsletters = formatNewsletterResponse(member.newsletters);
} }
return data; return data;

View file

@ -14,17 +14,7 @@ const messages = {
}; };
// flags in this list always return `true`, allows quick global enable prior to full flag removal // flags in this list always return `true`, allows quick global enable prior to full flag removal
const GA_FEATURES = [ const GA_FEATURES = [];
'multipleProducts',
'tierWelcomePages',
'tierName',
'selectablePortalLinks',
'membersTableStatus',
'multipleNewsletters',
'multipleNewslettersUI',
'membersActivityFeed',
'dashboardV5'
];
// NOTE: this allowlist is meant to be used to filter out any unexpected // NOTE: this allowlist is meant to be used to filter out any unexpected
// input for the "labs" setting value // input for the "labs" setting value
@ -34,8 +24,7 @@ const BETA_FEATURES = [
const ALPHA_FEATURES = [ const ALPHA_FEATURES = [
'urlCache', 'urlCache',
'beforeAfterCard', 'beforeAfterCard'
'tweetGridCard'
]; ];
module.exports.GA_KEYS = [...GA_FEATURES]; module.exports.GA_KEYS = [...GA_FEATURES];

View file

@ -85,9 +85,9 @@
"@tryghost/logging": "2.1.8", "@tryghost/logging": "2.1.8",
"@tryghost/magic-link": "1.0.26", "@tryghost/magic-link": "1.0.26",
"@tryghost/member-events": "0.4.6", "@tryghost/member-events": "0.4.6",
"@tryghost/members-api": "8.1.1", "@tryghost/members-api": "8.1.2",
"@tryghost/members-events-service": "0.4.3", "@tryghost/members-events-service": "0.4.3",
"@tryghost/members-importer": "0.5.15", "@tryghost/members-importer": "0.5.16",
"@tryghost/members-offers": "0.11.6", "@tryghost/members-offers": "0.11.6",
"@tryghost/members-ssr": "1.0.28", "@tryghost/members-ssr": "1.0.28",
"@tryghost/members-stripe-service": "0.10.5", "@tryghost/members-stripe-service": "0.10.5",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -192,10 +192,6 @@ describe('Front-end members behaviour', function () {
}); });
describe('Unsubscribe', function () { describe('Unsubscribe', function () {
beforeEach(function () {
mockManager.mockLabsEnabled('multipleNewslettersUI');
});
afterEach(function () { afterEach(function () {
mockManager.restore(); mockManager.restore();
}); });

View file

@ -18,7 +18,7 @@ describe('Unit: canary/utils/serializers/output/members', function () {
sinon.restore(); sinon.restore();
}); });
it('browse: includes newsletter data when flag is enabled', function () { it('browse: includes newsletter data', function () {
const apiConfig = {docName: 'members'}; const apiConfig = {docName: 'members'};
const frame = { const frame = {
options: { options: {
@ -51,24 +51,7 @@ describe('Unit: canary/utils/serializers/output/members', function () {
should.exist(frame.response.members[0].tiers); should.exist(frame.response.members[0].tiers);
}); });
it('browse: removes newsletter data when flag is disabled', function () { it('read: includes newsletter data', function () {
labsStub.returns(false);
const apiConfig = {docName: 'members'};
const frame = {
options: {
context: {}
}
};
const ctrlResponse = memberModel(testUtils.DataGenerator.forKnex.createMemberWithNewsletter());
memberSerializer.browse({
data: [ctrlResponse],
meta: null
}, apiConfig, frame);
should.not.exist(frame.response.members[0].newsletters);
});
it('read: includes newsletter data when flag is enabled', function () {
const apiConfig = {docName: 'members'}; const apiConfig = {docName: 'members'};
const frame = { const frame = {
options: { options: {
@ -81,20 +64,6 @@ describe('Unit: canary/utils/serializers/output/members', function () {
should.exist(frame.response.members[0].newsletters); should.exist(frame.response.members[0].newsletters);
}); });
it('read: removes newsletter data when flag is disabled', function () {
labsStub.returns(false);
const apiConfig = {docName: 'members'};
const frame = {
options: {
context: {}
}
};
const ctrlResponse = memberModel(testUtils.DataGenerator.forKnex.createMemberWithNewsletter());
memberSerializer.read(ctrlResponse, apiConfig, frame);
should.not.exist(frame.response.members[0].newsletters);
});
it('read: includes tiers data', function () { it('read: includes tiers data', function () {
const apiConfig = {docName: 'members'}; const apiConfig = {docName: 'members'};
const frame = { const frame = {

View file

@ -3,7 +3,7 @@ const sinon = require('sinon');
const errors = require('@tryghost/errors'); const errors = require('@tryghost/errors');
const labs = require('../../../../../core/shared/labs'); const labs = require('../../../../../core/shared/labs');
const {addEmail, _partitionMembersBySegment, _getEmailMemberRows, _transformEmailRecipientFilter, handleUnsubscribeRequest, _getFromAddress, _getReplyToAddress} = require('../../../../../core/server/services/mega/mega'); const {addEmail, _partitionMembersBySegment, _getEmailMemberRows, _transformEmailRecipientFilter, _getFromAddress, _getReplyToAddress} = require('../../../../../core/server/services/mega/mega');
const membersService = require('../../../../../core/server/services/members'); const membersService = require('../../../../../core/server/services/members');
describe('MEGA', function () { describe('MEGA', function () {
@ -100,44 +100,6 @@ describe('MEGA', function () {
}); });
}); });
describe('handleUnsubscribeRequest', function () {
const updateStub = sinon.stub();
beforeEach(function () {
updateStub.returns({
toJSON: () => {
return {};
}
});
sinon.stub(membersService, 'api').get(() => {
return {
members: {
get: () => {
return {
id: 'id-1',
name: 'Jamie'
};
},
update: updateStub
}
};
});
});
it('unsubscribes from all newsletters', async function () {
sinon.stub(labs, 'isSet').withArgs('multipleNewsletters').returns(true);
const req = {
url: 'https://example.com?uuid=abc'
};
await handleUnsubscribeRequest(req);
updateStub.calledWith({
subscribed: false,
newsletters: []
}, {
id: 'id-1'
}).should.be.true();
});
});
describe('getEmailMemberRows', function () { describe('getEmailMemberRows', function () {
it('getEmailMemberRows throws when "none" is used as a recipient_filter', async function () { it('getEmailMemberRows throws when "none" is used as a recipient_filter', async function () {
const newsletterGetter = sinon.stub(); const newsletterGetter = sinon.stub();

View file

@ -71,36 +71,5 @@ describe('Members Service - utils', function () {
}] }]
}); });
}); });
it('removes newsletter data if flag is disabled', async function () {
labsStub.returns(false);
const member1 = formattedMemberResponse({
uuid: 'uuid-1',
email: 'jamie+1@example.com',
name: 'Jamie Larson',
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
subscribed: true,
status: 'paid',
extra: 'property',
newsletters: [{
id: 'newsletter-1',
name: 'Daily brief',
description: 'One email daily',
sender_name: 'Jamie',
sender_email: 'jamie@example.com',
sort_order: 0
}]
});
should(member1).deepEqual({
uuid: 'uuid-1',
email: 'jamie+1@example.com',
name: 'Jamie Larson',
firstname: 'Jamie',
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
subscribed: true,
subscriptions: [],
paid: true
});
});
}); });
}); });

View file

@ -1862,10 +1862,10 @@
"@tryghost/domain-events" "^0.1.14" "@tryghost/domain-events" "^0.1.14"
"@tryghost/member-events" "^0.4.6" "@tryghost/member-events" "^0.4.6"
"@tryghost/members-api@8.1.1": "@tryghost/members-api@8.1.2":
version "8.1.1" version "8.1.2"
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-8.1.1.tgz#9078ea61717a0c4dab1cff1ef0a0ae505389731c" resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-8.1.2.tgz#dd4191ad7cbf0e6687c69153b06b8b5f9ef4709e"
integrity sha512-gnNmK9Bw8xpdWm/KsXmYKXdE6h52pzI+QOYMTrCHvKkdOk643OBYwkDmssXfSZAxO31offkE0j/q3xTIstlTUw== integrity sha512-cD1NrGgPJQfaZBkW0GfTJRq5pcOABu13Tf/BFa4koFi03JUbK3QM92mZPmboks2cpDzkku6EzwFS4D7nbgcS5Q==
dependencies: dependencies:
"@nexes/nql" "^0.6.0" "@nexes/nql" "^0.6.0"
"@tryghost/debug" "^0.1.2" "@tryghost/debug" "^0.1.2"
@ -1910,10 +1910,10 @@
"@tryghost/member-events" "^0.4.6" "@tryghost/member-events" "^0.4.6"
moment-timezone "^0.5.34" moment-timezone "^0.5.34"
"@tryghost/members-importer@0.5.15": "@tryghost/members-importer@0.5.16":
version "0.5.15" version "0.5.16"
resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.5.15.tgz#a28d4c14d0f43608b9363892faf442b4afb49e5f" resolved "https://registry.yarnpkg.com/@tryghost/members-importer/-/members-importer-0.5.16.tgz#d39f25d88515c9c386b3b9c574034b533aa21d96"
integrity sha512-rHig0CUBRAtInn/iXHaiR4QTGvnOMvRh6UmZ9oGn4oxUMuYo8wRhA1xyKOhgxOx/rFFIT2i27d21/m0QzXX1zQ== integrity sha512-1D+9s9lWg72gc40p4IrxxcNpWjU5dClqnv9P7CJwKk5WUJV28WlyBwZuuqn0r0S+igjVyX3eKLKkp8W1tinulw==
dependencies: dependencies:
"@tryghost/errors" "^1.0.0" "@tryghost/errors" "^1.0.0"
"@tryghost/members-csv" "^1.2.16" "@tryghost/members-csv" "^1.2.16"