Added filtering of announcement bar content

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

-  We need to show the announcement_content to specific audiences based on the announcement_visibility filter
This commit is contained in:
Naz 2023-04-20 17:23:06 +02:00
parent 3cf6800e3e
commit cddf786424
No known key found for this signature in database
16 changed files with 261 additions and 16 deletions

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/node'
]
};

View File

@ -0,0 +1,23 @@
# Announcement Bar Settings
Announcement Bar settings logic
## Usage
## Develop
This is a monorepo package.
Follow the instructions for the top-level repo.
1. `git clone` this repo & `cd` into it as usual
2. Run `yarn` to install top-level dependencies.
## Test
- `yarn lint` run just eslint
- `yarn test` run lint and tests

View File

@ -0,0 +1 @@
module.exports = require('./lib/AnnouncementBarSettings');

View File

@ -0,0 +1,54 @@
class AnnouncementBarSettings {
#getAnnouncementSettings;
/**
*
* @param {Object} deps
* @param {() => {announcement: string, announcement_visibility: string[], announcement_background: string}} deps.getAnnouncementSettings
*/
constructor(deps) {
this.#getAnnouncementSettings = deps.getAnnouncementSettings;
}
/**
* @param {Object} [member]
* @param {string} member.status
* @returns {{announcement: string, announcement_background: string}}
*/
getAnnouncementSettings(member) {
let announcement = undefined;
// NOTE: combination of 'free_members' & 'paid_members' makes just a 'members' filter
const announcementSettings = this.#getAnnouncementSettings();
if (announcementSettings.announcement) {
const visibilities = announcementSettings.announcement_visibility;
const announcementContent = announcementSettings.announcement;
// Available visibilities:
// 'visitors', // Logged out visitors
// 'free_members', // Free members
// 'paid_members' // Paid members (aka non-free members)
if (visibilities.length === 0) {
announcement = undefined;
} else {
if (visibilities.includes('visitors') && !member) {
announcement = announcementContent;
} else if (visibilities.includes('free_members') && (member?.status === 'free')) {
announcement = announcementContent;
} else if (visibilities.includes('paid_members') && (member?.status !== 'free')) {
announcement = announcementContent;
}
}
}
if (announcement !== undefined) {
return {
announcement,
announcement_background: announcementSettings.announcement_background
};
}
}
}
module.exports = AnnouncementBarSettings;

View File

@ -0,0 +1,26 @@
{
"name": "@tryghost/announcement-bar-settings",
"version": "0.0.0",
"repository": "https://github.com/TryGhost/Ghost/tree/main/packages/announcement-bar-settings",
"author": "Ghost Foundation",
"private": true,
"main": "index.js",
"scripts": {
"dev": "echo \"Implement me!\"",
"test:unit": "NODE_ENV=testing c8 --all --check-coverage --100 --reporter text --reporter cobertura mocha './test/**/*.test.js'",
"test": "yarn test:unit",
"lint:code": "eslint *.js lib/ --ext .js --cache",
"lint": "yarn lint:code && yarn lint:test",
"lint:test": "eslint -c test/.eslintrc.js test/ --ext .js --cache"
},
"files": [
"index.js",
"lib"
],
"devDependencies": {
"c8": "7.13.0",
"mocha": "10.2.0",
"sinon": "15.0.4"
},
"dependencies": {}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/test'
]
};

View File

@ -0,0 +1,105 @@
const assert = require('assert');
const AnnouncementBarSettings = require('../index');
describe('AnnouncementBarSettings', function () {
it('can initialize', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: 'Hello world',
announcement_visibility: ['visitors'],
announcement_background: 'dark'
})
});
assert.ok(announcementBarSettings);
});
describe('getAnnouncementSettings', function () {
it('returns undefined if there is no announcement content', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: null,
announcement_visibility: [],
announcement_background: 'dark'
})
});
const settings = announcementBarSettings.getAnnouncementSettings();
assert.equal(settings, undefined);
});
it('returns undefined announcement settings if there is no announcement visibility', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: 'Hello world',
announcement_visibility: [],
announcement_background: 'dark'
})
});
const settings = announcementBarSettings.getAnnouncementSettings();
assert.equal(settings, undefined);
});
it('returns announcement if visibility is set to visitors and there is no logged in member', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: 'Hello world',
announcement_visibility: ['visitors'],
announcement_background: 'dark'
})
});
const settings = announcementBarSettings.getAnnouncementSettings();
assert.equal(settings.announcement, 'Hello world');
});
it('returns announcement if visibility is set to free members and member is free', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: 'Hello world',
announcement_visibility: ['free_members'],
announcement_background: 'dark'
})
});
const settings = announcementBarSettings.getAnnouncementSettings({
status: 'free'
});
assert.equal(settings.announcement, 'Hello world');
});
it('returns announcement if visibility is set to paid members and member is paid', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: 'Hello world',
announcement_visibility: ['paid_members'],
announcement_background: 'dark'
})
});
const settings = announcementBarSettings.getAnnouncementSettings({
status: 'paid'
});
assert.equal(settings.announcement, 'Hello world');
});
it('returns announcement if visibility is set to paid and paid members and member is comped', function () {
const announcementBarSettings = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: 'Hello world',
announcement_visibility: ['paid_members'],
announcement_background: 'dark'
})
});
const settings = announcementBarSettings.getAnnouncementSettings({
status: 'comped'
});
assert.equal(settings.announcement, 'Hello world');
});
});
});

View File

@ -1,19 +1,25 @@
const settingsCache = require('../../../shared/settings-cache');
const urlUtils = require('../../../shared/url-utils');
const ghostVersion = require('@tryghost/version');
const announcementBarSettings = require('../../services/announcement-bar-service');
module.exports = {
docName: 'settings',
browse: {
permissions: true,
query() {
query(frame) {
const announcementSettings = announcementBarSettings.getAnnouncementSettings(frame.options.context?.member);
// @TODO: decouple settings cache from API knowledge
// The controller fetches models (or cached models) and the API frame for the target API version formats the response.
return Object.assign({}, settingsCache.getPublic(), {
url: urlUtils.urlFor('home', true),
version: ghostVersion.safe
});
return Object.assign({},
settingsCache.getPublic(),
announcementSettings, {
url: urlUtils.urlFor('home', true),
version: ghostVersion.safe
}
);
}
}
};

View File

@ -0,0 +1,12 @@
const settingsCache = require('../../../shared/settings-cache');
const AnnouncementBarSettings = require('@tryghost/announcement-bar-settings');
const announcementBarService = new AnnouncementBarSettings({
getAnnouncementSettings: () => ({
announcement: settingsCache.get('announcement_content'),
announcement_background: settingsCache.get('announcement_background'),
announcement_visibility: settingsCache.get('announcement_visibility')
})
});
module.exports = announcementBarService;

View File

@ -4,6 +4,7 @@ const api = require('../../../../api').endpoints;
const {http} = require('@tryghost/api-framework');
const mw = require('./middleware');
const config = require('../../../../../shared/config');
const membersService = require('../../../../../server/services/members');
module.exports = function apiRoutes() {
const router = express.Router('content api');
@ -31,7 +32,7 @@ module.exports = function apiRoutes() {
router.get('/tags/slug/:slug', mw.authenticatePublic, http(api.tagsPublic.read));
// ## Settings
router.get('/settings', mw.authenticatePublic, http(api.publicSettings.browse));
router.get('/settings', mw.authenticatePublic, membersService.middleware.loadMemberSession, http(api.publicSettings.browse));
// ## Members
router.get('/newsletters', mw.authenticatePublic, http(api.newslettersPublic.browse));

View File

@ -135,7 +135,7 @@ class CacheManager {
}
/**
* Get all the publically accessible cache entries with their correct names
* Get all the publicly accessible cache entries with their correct names
* Uses clone to prevent modifications from being reflected
* @return {object} cache
*/

View File

@ -39,7 +39,5 @@ module.exports = {
portal_plans: 'portal_plans',
portal_name: 'portal_name',
portal_button: 'portal_button',
comments_enabled: 'comments_enabled',
announcement: 'announcement_content',
announcement_background: 'announcement_background'
comments_enabled: 'comments_enabled'
};

View File

@ -63,6 +63,7 @@
"@tryghost/adapter-cache-redis": "0.0.0",
"@tryghost/adapter-manager": "0.0.0",
"@tryghost/admin-api-schema": "4.3.0",
"@tryghost/announcement-bar-settings": "0.0.0",
"@tryghost/api-framework": "0.0.0",
"@tryghost/api-version-compatibility-service": "0.0.0",
"@tryghost/audience-feedback": "0.0.0",

View File

@ -5,8 +5,6 @@ Object {
"meta": Object {},
"settings": Object {
"accent_color": "#FF1A75",
"announcement": null,
"announcement_background": "dark",
"codeinjection_foot": null,
"codeinjection_head": null,
"comments_enabled": "off",

View File

@ -1339,8 +1339,6 @@ Object {
"meta": Object {},
"settings": Object {
"accent_color": "#FF1A75",
"announcement": null,
"announcement_background": "dark",
"codeinjection_foot": null,
"codeinjection_head": null,
"comments_enabled": "off",
@ -1437,8 +1435,6 @@ Object {
"meta": Object {},
"settings": Object {
"accent_color": "#FF1A75",
"announcement": null,
"announcement_background": "dark",
"codeinjection_foot": null,
"codeinjection_head": null,
"comments_enabled": "off",

View File

@ -27498,6 +27498,18 @@ sinon@15.0.3:
nise "^5.1.4"
supports-color "^7.2.0"
sinon@15.0.4:
version "15.0.4"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-15.0.4.tgz#bcca6fef19b14feccc96473f0d7adc81e0bc5268"
integrity sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==
dependencies:
"@sinonjs/commons" "^3.0.0"
"@sinonjs/fake-timers" "^10.0.2"
"@sinonjs/samsam" "^8.0.0"
diff "^5.1.0"
nise "^5.1.4"
supports-color "^7.2.0"
sinon@^9.0.0:
version "9.2.4"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b"