Added initial setup of members activity feed
refs https://github.com/TryGhost/Team/issues/1277 - added `/members-activity` route with associated controller, template, and components behind labs flag - table component currently renders some dummy rows - added navigation item to main menu - will use the currently set `?filter` query param unless clicked whilst already on the `/members-activity` screen in which case it will reset the query - added link to dashboard members activity panel - added link to member details activity panel - sets the filter param to `?filter=member:{member.id}` in preparation for the feed to be filtered to the member's activity - updated the labs-flag test helper file to export both `enableLabsFlag()` and the new `disableLabsFlag()` so it's easier to test for flag-disabled functionality
This commit is contained in:
parent
3f33c4177d
commit
62a5757ace
|
@ -14,22 +14,26 @@
|
|||
{{/each}}
|
||||
{{/liquid-if}}
|
||||
|
||||
{{#if (and this.remainingActivities (not this.isShowingAll))}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-member-btn-expandfeed"
|
||||
data-test-button="view-all-activity"
|
||||
{{on "click" this.showAll}}
|
||||
>
|
||||
<span>View all activity</span>
|
||||
</button>
|
||||
{{#if (feature "membersActivityFeed")}}
|
||||
<LinkTo class="gh-btn gh-member-btn-expandfeed" @route="members-activity" @query={{hash filter=(concat "member:" @member.id)}}><span>View all activity</span></LinkTo>
|
||||
{{else}}
|
||||
{{#if (and this.remainingActivities (not this.isShowingAll))}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-btn gh-member-btn-expandfeed"
|
||||
data-test-button="view-all-activity"
|
||||
{{on "click" this.showAll}}
|
||||
>
|
||||
<span>View all activity</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="gh-members-no-data gh-members-no-list">
|
||||
<div class="lightgrey">{{svg-jar "no-data-list"}}</div>
|
||||
<h4>Member activity</h4>
|
||||
<p>
|
||||
All events related to this member will be shown here.
|
||||
All events related to this member will be shown here.
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -349,7 +349,10 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
<GhMemberActivityFeed @emailRecipients={{this.member.emailRecipients}} />
|
||||
<GhMemberActivityFeed
|
||||
@member={{this.member}}
|
||||
@emailRecipients={{this.member.emailRecipients}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -82,6 +82,16 @@
|
|||
{{/if}}
|
||||
</li>
|
||||
|
||||
{{#if (feature "membersActivityFeed")}}
|
||||
<li>
|
||||
{{#if (eq this.router.currentRouteName "members-activity")}}
|
||||
<LinkTo @route="members-activity" @query={{reset-query-params "members-activity"}} data-test-nav="members-activity">{{svg-jar "members"}} Members activity</LinkTo>
|
||||
{{else}}
|
||||
<LinkTo @route="members-activity" data-test-nav="members-activity">{{svg-jar "members"}} Members activity</LinkTo>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.isStripeConnected}}
|
||||
<li>
|
||||
<LinkTo @route="offers" @alt="Offers">{{svg-jar "percentage"}}Offers</LinkTo>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<tr>
|
||||
<div class="gh-list-data">{{@activity.member.name}}</div>
|
||||
<div class="gh-list-data">{{@activity.event}}</div>
|
||||
<div class="gh-list-data">{{moment-format @activity.timestamp "D MMM YYYY HH:MM"}}</div>
|
||||
</tr>
|
|
@ -0,0 +1,41 @@
|
|||
<section class="view-container">
|
||||
{{#if this.activities}}
|
||||
<div class="gh-list-scrolling">
|
||||
<table class="gh-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Member</th>
|
||||
<th>Event</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<VerticalCollection
|
||||
@tagName="tbody"
|
||||
@items={{this.activities}}
|
||||
@key="id"
|
||||
@containerSelector=".gh-list-scrolling"
|
||||
@estimateHeight={{69}}
|
||||
@staticHeight={{true}}
|
||||
@bufferSize={{20}}
|
||||
as |activity|
|
||||
>
|
||||
<MembersActivity::TableItem @activity={{activity}} />
|
||||
</VerticalCollection>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="no-posts-box">
|
||||
<div class="no-posts">
|
||||
{{svg-jar "members-placeholder" class="gh-members-placeholder"}}
|
||||
{{#if this.showingAll}}
|
||||
<h3>No member activity yet</h3>
|
||||
{{else}}
|
||||
<h3>No activities match the current filter</h3>
|
||||
<LinkTo @route="members-activity" @query={{hash filter=null}} class="gh-btn gh-btn-lg">
|
||||
<span>Show all activity</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
|
@ -0,0 +1,17 @@
|
|||
import Component from '@glimmer/component';
|
||||
|
||||
export default class MembersActivityTableComponent extends Component {
|
||||
activities = (Array.from({length: 50}, () => {
|
||||
return {
|
||||
member: {
|
||||
name: 'Example member'
|
||||
},
|
||||
event: 'Opened email',
|
||||
timestamp: new Date()
|
||||
};
|
||||
}))
|
||||
|
||||
get showingAll() {
|
||||
return !this.args.filter;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import Controller from '@ember/controller';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class MembersActivityController extends Controller {
|
||||
queryParams = ['filter'];
|
||||
|
||||
@tracked filter = null;
|
||||
}
|
|
@ -21,6 +21,9 @@ export const DEFAULT_QUERY_PARAMS = {
|
|||
search: null,
|
||||
filter: null,
|
||||
order: null
|
||||
},
|
||||
'members-activity': {
|
||||
filter: null
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ Router.map(function () {
|
|||
});
|
||||
this.route('member.new', {path: '/members/new'});
|
||||
this.route('member', {path: '/members/:member_id'});
|
||||
this.route('members-activity');
|
||||
|
||||
this.route('offers');
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import AdminRoute from 'ghost-admin/routes/admin';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class MembersActivityRoute extends AdminRoute {
|
||||
@service feature;
|
||||
|
||||
beforeModel() {
|
||||
super.beforeModel(...arguments);
|
||||
|
||||
if (!this.feature.membersActivityFeed) {
|
||||
return this.transitionTo('home');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import Service from '@ember/service';
|
||||
|
||||
export default class MembersActivityService extends Service {
|
||||
|
||||
}
|
|
@ -295,6 +295,12 @@
|
|||
</p>
|
||||
{{else}}
|
||||
<GhEventTimeline @events={{this.eventsData}}/>
|
||||
|
||||
{{#if (feature "membersActivityFeed")}}
|
||||
<div class="gh-dashboard-top-members-footer">
|
||||
<LinkTo @route="members-activity">See all activity {{svg-jar "arrow-right"}}</LinkTo>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<section class="gh-canvas">
|
||||
<GhCanvasHeader class="gh-canvas-header break tablet members-header">
|
||||
<h2 class="gh-canvas-title" data-test-screen-title>Members Activity</h2>
|
||||
</GhCanvasHeader>
|
||||
|
||||
<MembersActivity::Table @filter={{this.filter}} />
|
||||
</section>
|
||||
|
||||
{{outlet}}
|
|
@ -0,0 +1,54 @@
|
|||
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
|
||||
import {currentURL} from '@ember/test-helpers';
|
||||
import {describe, it} from 'mocha';
|
||||
import {disableLabsFlag, enableLabsFlag} from '../helpers/labs-flag';
|
||||
import {expect} from 'chai';
|
||||
import {setupApplicationTest} from 'ember-mocha';
|
||||
import {setupMirage} from 'ember-cli-mirage/test-support';
|
||||
import {visit} from '../helpers/visit';
|
||||
|
||||
describe('Acceptance: Members activity', function () {
|
||||
const hooks = setupApplicationTest();
|
||||
setupMirage(hooks);
|
||||
|
||||
beforeEach(function () {
|
||||
enableLabsFlag(this.server, 'membersActivityFeed');
|
||||
});
|
||||
|
||||
it('redirects when not authenticated', async function () {
|
||||
await invalidateSession();
|
||||
await visit('/members-activity');
|
||||
expect(currentURL()).to.equal('/signin');
|
||||
});
|
||||
|
||||
it('redirects non-admins', async function () {
|
||||
await invalidateSession();
|
||||
|
||||
const role = this.server.create('role', {name: 'Editor'});
|
||||
this.server.create('user', {roles: [role], slug: 'test-user'});
|
||||
|
||||
await authenticateSession();
|
||||
await visit('/members-activity');
|
||||
expect(currentURL()).to.equal('/site');
|
||||
});
|
||||
|
||||
describe('as admin', function () {
|
||||
beforeEach(async function () {
|
||||
const role = this.server.create('role', {name: 'Administrator'});
|
||||
this.server.create('user', {roles: [role]});
|
||||
|
||||
await authenticateSession();
|
||||
});
|
||||
|
||||
it('renders', async function () {
|
||||
await visit('/members-activity');
|
||||
expect(currentURL()).to.equal('/members-activity');
|
||||
});
|
||||
|
||||
it('requires feature flag', async function () {
|
||||
disableLabsFlag(this.server, 'membersActivityFeed');
|
||||
await visit('/members-activity');
|
||||
expect(currentURL()).to.equal('/dashboard');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
export default function (server, flag) {
|
||||
if (!server.schema.configs.all().length) {
|
||||
server.loadFixtures('configs');
|
||||
}
|
||||
|
||||
if (!server.schema.settings.all().length) {
|
||||
server.loadFixtures('settings');
|
||||
}
|
||||
|
||||
const config = server.schema.configs.first();
|
||||
config.update({enableDeveloperExperiments: true});
|
||||
|
||||
const labsSetting = {};
|
||||
labsSetting[flag] = true;
|
||||
|
||||
server.db.settings.update({key: 'labs'}, {value: JSON.stringify(labsSetting)});
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
export function enableLabsFlag(server, flag) {
|
||||
if (!server.schema.configs.all().length) {
|
||||
server.loadFixtures('configs');
|
||||
}
|
||||
|
||||
if (!server.schema.settings.all().length) {
|
||||
server.loadFixtures('settings');
|
||||
}
|
||||
|
||||
const config = server.schema.configs.first();
|
||||
config.update({enableDeveloperExperiments: true});
|
||||
|
||||
const labsSetting = {};
|
||||
labsSetting[flag] = true;
|
||||
|
||||
server.db.settings.update({key: 'labs'}, {value: JSON.stringify(labsSetting)});
|
||||
}
|
||||
|
||||
export function disableLabsFlag(server, flag) {
|
||||
if (!server.schema.configs.all().length) {
|
||||
server.loadFixtures('configs');
|
||||
}
|
||||
|
||||
if (!server.schema.settings.all().length) {
|
||||
server.loadFixtures('settings');
|
||||
}
|
||||
|
||||
const config = server.schema.configs.first();
|
||||
config.update({enableDeveloperExperiments: true});
|
||||
|
||||
const labsSetting = {};
|
||||
labsSetting[flag] = false;
|
||||
|
||||
server.db.settings.update({key: 'labs'}, {value: JSON.stringify(labsSetting)});
|
||||
}
|
Loading…
Reference in New Issue