mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-22 11:16:01 +03:00
Added initial Stats page to Ghost Admin (#20877)
closes https://linear.app/tryghost/issue/ANAL-10/stats-page-in-ghost-admin - Adds all the structure, logic and permissions tests for the Stats page to check we're loading the right thing at the right time - Adds @tinybirdco/charts as a dependency, and has a single example of a chart setup using the right config
This commit is contained in:
parent
42009eb9ed
commit
9d121d8e9a
@ -32,6 +32,11 @@
|
||||
<span>{{svg-jar "external"}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{#if (and (gh-user-can-admin this.session.user) this.config.stats)}}
|
||||
<li class="relative">
|
||||
<LinkTo @route="stats">{{svg-jar "stats"}}Stats</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (gh-user-can-admin this.session.user)}}
|
||||
<li class="relative">
|
||||
<a href="javascript:void(0)" class={{if this.explore.exploreWindowOpen "active"}} {{on "click" (fn this.toggleExploreWindow "")}} data-test-nav="explore">
|
||||
|
@ -0,0 +1 @@
|
||||
<div {{react-render this.ReactComponent}}></div>
|
49
ghost/admin/app/components/stats/charts/top-locations.js
Normal file
49
ghost/admin/app/components/stats/charts/top-locations.js
Normal file
@ -0,0 +1,49 @@
|
||||
import Component from '@glimmer/component';
|
||||
import React from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import {BarList, useQuery} from '@tinybirdco/charts';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
|
||||
export default class TopPages extends Component {
|
||||
@inject config;
|
||||
|
||||
ReactComponent = (props) => {
|
||||
let chartDays = props.chartDays;
|
||||
|
||||
const endDate = moment().endOf('day');
|
||||
const startDate = moment().subtract(chartDays - 1, 'days').startOf('day');
|
||||
|
||||
/**
|
||||
* @typedef {Object} Params
|
||||
* @property {string} cid
|
||||
* @property {string} [date_from]
|
||||
* @property {string} [date_to]
|
||||
* @property {number} [limit]
|
||||
* @property {number} [skip]
|
||||
*/
|
||||
const params = {
|
||||
cid: this.config.stats.id,
|
||||
date_from: startDate.format('YYYY-MM-DD'),
|
||||
date_to: endDate.format('YYYY-MM-DD')
|
||||
};
|
||||
|
||||
const {data, meta, error, loading} = useQuery({
|
||||
endpoint: `${this.config.stats.endpoint}/v0/pipes/top_locations.json`,
|
||||
token: this.config.stats.token,
|
||||
params
|
||||
});
|
||||
|
||||
return (
|
||||
<BarList
|
||||
data={data}
|
||||
meta={meta}
|
||||
error={error}
|
||||
loading={loading}
|
||||
index="location"
|
||||
categories={['hits']}
|
||||
colorPalette={['#E8D9FF']}
|
||||
height="300px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
4
ghost/admin/app/controllers/stats.js
Normal file
4
ghost/admin/app/controllers/stats.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default class StatsController extends Controller {
|
||||
}
|
@ -23,6 +23,7 @@ Router.map(function () {
|
||||
this.route('site');
|
||||
this.route('dashboard');
|
||||
this.route('launch');
|
||||
this.route('stats');
|
||||
|
||||
this.route('pro', function () {
|
||||
this.route('pro-sub', {path: '/*sub'});
|
||||
|
28
ghost/admin/app/routes/stats.js
Normal file
28
ghost/admin/app/routes/stats.js
Normal file
@ -0,0 +1,28 @@
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
|
||||
export default class StatsRoute extends AuthenticatedRoute {
|
||||
@inject config;
|
||||
|
||||
beforeModel() {
|
||||
super.beforeModel(...arguments);
|
||||
|
||||
// This is based on the logic for the dashboard
|
||||
if (this.session.user.isContributor) {
|
||||
return this.transitionTo('posts');
|
||||
} else if (!this.session.user.isAdmin) {
|
||||
return this.transitionTo('site');
|
||||
}
|
||||
|
||||
// This ensures that we don't load this page if the stats config is not set
|
||||
if (!this.config.stats) {
|
||||
return this.transitionTo('home');
|
||||
}
|
||||
}
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
return {
|
||||
titleToken: 'Stats'
|
||||
};
|
||||
}
|
||||
}
|
@ -72,6 +72,7 @@
|
||||
@import "layouts/explore.css";
|
||||
@import "layouts/mentions.css";
|
||||
@import "layouts/migrate.css";
|
||||
@import "layouts/stats.css";
|
||||
|
||||
|
||||
/* ---------------------------✈️----------------------------- */
|
||||
|
0
ghost/admin/app/styles/layouts/stats.css
Normal file
0
ghost/admin/app/styles/layouts/stats.css
Normal file
8
ghost/admin/app/templates/stats.hbs
Normal file
8
ghost/admin/app/templates/stats.hbs
Normal file
@ -0,0 +1,8 @@
|
||||
<section class="gh-canvas gh-canvas-sticky">
|
||||
<GhCanvasHeader class="gh-canvas-header sticky break tablet post-header">
|
||||
<GhCustomViewTitle @title="Stats" />
|
||||
</GhCanvasHeader>
|
||||
|
||||
<Stats::Charts::TopLocations />
|
||||
|
||||
</section>
|
@ -44,6 +44,7 @@
|
||||
"@sentry/ember": "7.119.0",
|
||||
"@sentry/integrations": "7.114.0",
|
||||
"@sentry/replay": "7.116.0",
|
||||
"@tinybirdco/charts": "0.1.8",
|
||||
"@tryghost/color-utils": "0.2.2",
|
||||
"@tryghost/ember-promise-modals": "2.0.1",
|
||||
"@tryghost/helpers": "1.1.90",
|
||||
|
58
ghost/admin/tests/acceptance/stats-test.js
Normal file
58
ghost/admin/tests/acceptance/stats-test.js
Normal file
@ -0,0 +1,58 @@
|
||||
import loginAsRole from '../helpers/login-as-role';
|
||||
import {currentURL, find, visit} from '@ember/test-helpers';
|
||||
import {describe, it} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import {invalidateSession} from 'ember-simple-auth/test-support';
|
||||
import {setupApplicationTest} from 'ember-mocha';
|
||||
import {setupMirage} from 'ember-cli-mirage/test-support';
|
||||
|
||||
describe.only('Acceptance: Stats', function () {
|
||||
const hooks = setupApplicationTest();
|
||||
setupMirage(hooks);
|
||||
|
||||
beforeEach(function () {
|
||||
this.server.loadFixtures();
|
||||
});
|
||||
|
||||
describe('permissions', function () {
|
||||
it('redirects to signin when not authenticated', async function () {
|
||||
await invalidateSession();
|
||||
await visit('/stats');
|
||||
expect(currentURL()).to.equal('/signin');
|
||||
});
|
||||
|
||||
it('redirects to posts page when authenticated as contributor', async function () {
|
||||
await loginAsRole('Contributor', this.server);
|
||||
await visit('/stats');
|
||||
expect(currentURL(), 'currentURL').to.equal('/posts');
|
||||
});
|
||||
|
||||
it('redirects to site page when authenticated as author', async function () {
|
||||
await loginAsRole('Author', this.server);
|
||||
await visit('/stats');
|
||||
expect(currentURL(), 'currentURL').to.equal('/site');
|
||||
});
|
||||
|
||||
it('redirects to dashboard when logged in as admin with no stats config set', async function () {
|
||||
await loginAsRole('Administrator', this.server);
|
||||
|
||||
await visit('/stats');
|
||||
expect(currentURL()).to.equal('/dashboard');
|
||||
expect(find('[data-test-screen-title]')).to.have.rendered.trimmed.text('Dashboard');
|
||||
});
|
||||
|
||||
it('can visit /stats when logged in as admin AND stats config is set', async function () {
|
||||
await loginAsRole('Administrator', this.server);
|
||||
|
||||
const config = this.server.db.configs.find(1);
|
||||
config.stats = {
|
||||
endpoint: 'http://testing.com'
|
||||
};
|
||||
this.server.db.configs.update(1, config);
|
||||
|
||||
await visit('/stats');
|
||||
expect(currentURL()).to.equal('/stats');
|
||||
expect(find('[data-test-screen-title]')).to.have.rendered.trimmed.text('Stats');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user