mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-15 03:01:37 -05:00
Added technical details to stats (#20898)
[ANAL-1](https://linear.app/tryghost/issue/ANAL-32/add-stats-kpis-charts) The technical details section in Stats contains only the browser breakdown ATM. This PR adds the rest (devices, operating systems) and fixes a couple of minor UI details on the rest of the charts
This commit is contained in:
parent
47d1a3c451
commit
397342a910
9 changed files with 179 additions and 54 deletions
|
@ -60,16 +60,17 @@ export default class KpisComponent extends Component {
|
|||
params={params}
|
||||
options={{
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 10,
|
||||
left: '0%',
|
||||
right: '0%',
|
||||
top: '10%',
|
||||
bottom: 0,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
min: startDate.toISOString(),
|
||||
max: endDate.toISOString(),
|
||||
// min: startDate.toISOString(),
|
||||
// max: endDate.toISOString(),
|
||||
boundaryGap: ['0%', '0.5%'],
|
||||
axisLabel: {
|
||||
formatter: chartDays <= 7 ? '{ee}' : '{dd} {MMM}'
|
||||
},
|
||||
|
@ -87,8 +88,7 @@ export default class KpisComponent extends Component {
|
|||
lineStyle: {
|
||||
color: '#DDE1E5'
|
||||
}
|
||||
},
|
||||
boundaryGap: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
splitLine: {
|
||||
|
@ -109,7 +109,10 @@ export default class KpisComponent extends Component {
|
|||
type: 'line',
|
||||
z: 1
|
||||
},
|
||||
extraCssText: 'box-shadow: 0px 100px 80px 0px rgba(0, 0, 0, 0.07), 0px 41.778px 33.422px 0px rgba(0, 0, 0, 0.05), 0px 22.336px 17.869px 0px rgba(0, 0, 0, 0.04), 0px 12.522px 10.017px 0px rgba(0, 0, 0, 0.04), 0px 6.65px 5.32px 0px rgba(0, 0, 0, 0.03), 0px 2.767px 2.214px 0px rgba(0, 0, 0, 0.02);'
|
||||
extraCssText: 'box-shadow: 0px 100px 80px 0px rgba(0, 0, 0, 0.07), 0px 41.778px 33.422px 0px rgba(0, 0, 0, 0.05), 0px 22.336px 17.869px 0px rgba(0, 0, 0, 0.04), 0px 12.522px 10.017px 0px rgba(0, 0, 0, 0.04), 0px 6.65px 5.32px 0px rgba(0, 0, 0, 0.03), 0px 2.767px 2.214px 0px rgba(0, 0, 0, 0.02);',
|
||||
formatter: function (fparams) {
|
||||
return `<div><div>${moment(fparams[0].value[0]).format('DD MMM, YYYY')}</div><div><span style="display: inline-block; margin-right: 16px; font-weight: 600;">Pageviews</span> ${fparams[0].value[1]}</div></div>`;
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
|
@ -148,7 +151,7 @@ export default class KpisComponent extends Component {
|
|||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
z: 8,
|
||||
smooth: true,
|
||||
smooth: false,
|
||||
name: props.selected,
|
||||
data: (data ?? []).map(row => [
|
||||
String(row[INDEX]),
|
||||
|
|
|
@ -28,45 +28,137 @@ export default class KpisComponent extends Component {
|
|||
site_uuid: this.config.stats.id,
|
||||
date_from: startDate.format('YYYY-MM-DD'),
|
||||
date_to: endDate.format('YYYY-MM-DD'),
|
||||
member_status: audience.length === 0 ? null : audience.join(',')
|
||||
member_status: audience.length === 0 ? null : audience.join(','),
|
||||
limit: 5
|
||||
};
|
||||
|
||||
let endpoint;
|
||||
|
||||
switch (props.selected) {
|
||||
case 'browsers':
|
||||
endpoint = `${this.config.stats.endpoint}/v0/pipes/top_browsers.json`;
|
||||
break;
|
||||
default:
|
||||
endpoint = `${this.config.stats.endpoint}/v0/pipes/top_devices.json`;
|
||||
}
|
||||
|
||||
const {data, meta, error, loading} = useQuery({
|
||||
endpoint: `${this.config.stats.endpoint}/v0/pipes/top_browsers.json`,
|
||||
endpoint: endpoint,
|
||||
token: this.config.stats.token,
|
||||
params
|
||||
});
|
||||
|
||||
const colorPalette = ['#B78AFB', '#7FDE8A', '#FBCE75', '#F97DB7', '#6ED0FB'];
|
||||
|
||||
let transformedData;
|
||||
let indexBy;
|
||||
let tableHead;
|
||||
|
||||
switch (props.selected) {
|
||||
case 'browsers':
|
||||
transformedData = (data ?? []).map((item, index) => ({
|
||||
name: item.browser.charAt(0).toUpperCase() + item.browser.slice(1),
|
||||
value: item.hits,
|
||||
color: colorPalette[index % colorPalette.length]
|
||||
}));
|
||||
indexBy = 'browser';
|
||||
tableHead = 'Browser';
|
||||
break;
|
||||
default:
|
||||
transformedData = (data ?? []).map((item, index) => ({
|
||||
name: item.device.charAt(0).toUpperCase() + item.device.slice(1),
|
||||
value: item.hits,
|
||||
color: colorPalette[index % colorPalette.length]
|
||||
}));
|
||||
indexBy = 'device';
|
||||
tableHead = 'Device';
|
||||
}
|
||||
|
||||
return (
|
||||
<DonutChart
|
||||
data={data}
|
||||
meta={meta}
|
||||
loading={loading}
|
||||
error={error}
|
||||
index="browser"
|
||||
categories={['hits']}
|
||||
colorPalette={['#B78AFB', '#7FDE8A', '#FBCE75', '#F97DB7', '#6ED0FB']}
|
||||
backgroundColor="transparent"
|
||||
fontSize="13px"
|
||||
textColor="#AEB7C1"
|
||||
showLegend={true}
|
||||
height="280px"
|
||||
params={params}
|
||||
options={{
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: '#fff',
|
||||
textStyle: {
|
||||
color: '#15171A'
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
z: 1
|
||||
},
|
||||
extraCssText: 'border: none !important; box-shadow: 0px 100px 80px 0px rgba(0, 0, 0, 0.07), 0px 41.778px 33.422px 0px rgba(0, 0, 0, 0.05), 0px 22.336px 17.869px 0px rgba(0, 0, 0, 0.04), 0px 12.522px 10.017px 0px rgba(0, 0, 0, 0.04), 0px 6.65px 5.32px 0px rgba(0, 0, 0, 0.03), 0px 2.767px 2.214px 0px rgba(0, 0, 0, 0.02);'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="gh-stats-piechart-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{tableHead}</th>
|
||||
<th>Hits</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{transformedData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<span style={{backgroundColor: item.color, display: 'inline-block', width: '10px', height: '10px', marginRight: '5px', borderRadius: '2px'}}></span>
|
||||
{item.name}
|
||||
</td>
|
||||
<td>{item.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="gh-stats-piechart">
|
||||
<DonutChart
|
||||
data={data}
|
||||
meta={meta}
|
||||
loading={loading}
|
||||
error={error}
|
||||
index={indexBy}
|
||||
categories={['hits']}
|
||||
colorPalette={colorPalette}
|
||||
backgroundColor="transparent"
|
||||
fontSize="13px"
|
||||
textColor="#AEB7C1"
|
||||
showLegend={true}
|
||||
params={params}
|
||||
height="210px"
|
||||
options={{
|
||||
color: colorPalette,
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'item',
|
||||
backgroundColor: '#fff',
|
||||
textStyle: {
|
||||
color: '#15171A'
|
||||
},
|
||||
extraCssText: 'border: none !important; box-shadow: 0px 100px 80px 0px rgba(0, 0, 0, 0.07), 0px 41.778px 33.422px 0px rgba(0, 0, 0, 0.05), 0px 22.336px 17.869px 0px rgba(0, 0, 0, 0.04), 0px 12.522px 10.017px 0px rgba(0, 0, 0, 0.04), 0px 6.65px 5.32px 0px rgba(0, 0, 0, 0.03), 0px 2.767px 2.214px 0px rgba(0, 0, 0, 0.02);',
|
||||
formatter: function (fparams) {
|
||||
return `<span style="background-color: ${fparams.color}; display: inline-block; width: 10px; height: 10px; margin-right: 5px; border-radius: 2px;"></span> ${fparams.name}: ${fparams.value}`;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
textStyle: {
|
||||
color: '#AEB7C1'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
animation: true,
|
||||
name: 'Browser',
|
||||
type: 'pie',
|
||||
radius: ['60%', '90%'],
|
||||
center: ['50%', '50%'], // Adjusted to align the chart to the top
|
||||
data: transformedData,
|
||||
label: {
|
||||
show: false,
|
||||
formatter: '{b}: {c}'
|
||||
},
|
||||
labelLine: {
|
||||
lineStyle: {
|
||||
color: '#DDE1E5'
|
||||
},
|
||||
smooth: 0.2,
|
||||
length: 10,
|
||||
length2: 20
|
||||
},
|
||||
padding: 0
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ export default class TopLocations extends Component {
|
|||
site_uuid: this.config.stats.id,
|
||||
date_from: startDate.format('YYYY-MM-DD'),
|
||||
date_to: endDate.format('YYYY-MM-DD'),
|
||||
member_status: audience.length === 0 ? null : audience.join(',')
|
||||
member_status: audience.length === 0 ? null : audience.join(','),
|
||||
limit: 6
|
||||
};
|
||||
|
||||
const {data, meta, error, loading} = useQuery({
|
||||
|
@ -45,7 +46,6 @@ export default class TopLocations extends Component {
|
|||
index="location"
|
||||
categories={['hits']}
|
||||
colorPalette={['#E8D9FF']}
|
||||
height="300px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,11 +29,12 @@ export default class TopPages extends Component {
|
|||
site_uuid: this.config.stats.id,
|
||||
date_from: startDate.format('YYYY-MM-DD'),
|
||||
date_to: endDate.format('YYYY-MM-DD'),
|
||||
member_status: audience.length === 0 ? null : audience.join(',')
|
||||
member_status: audience.length === 0 ? null : audience.join(','),
|
||||
limit: 6
|
||||
};
|
||||
|
||||
const {data, meta, error, loading} = useQuery({
|
||||
endpoint: `${this.config.stats.endpoint}/v0/pipes/top_locations.json`,
|
||||
endpoint: `${this.config.stats.endpoint}/v0/pipes/top_pages.json`,
|
||||
token: this.config.stats.token,
|
||||
params
|
||||
});
|
||||
|
|
|
@ -35,7 +35,8 @@ export default class TopPages extends Component {
|
|||
const {data, meta, error, loading} = useQuery({
|
||||
endpoint: `${this.config.stats.endpoint}/v0/pipes/top_sources.json`,
|
||||
token: this.config.stats.token,
|
||||
params
|
||||
params,
|
||||
limit: 6
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -26,7 +26,7 @@ export default class KpisOverview extends Component {
|
|||
@task
|
||||
*fetchData() {
|
||||
try {
|
||||
const response = yield fetch(`${this.config.stats.endpoint}/v0/pipes/kpis.json`, {
|
||||
const response = yield fetch(`${this.config.stats.endpoint}/v0/pipes/kpis.json?site_uuid=${this.config.stats.id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -39,6 +39,7 @@ export default class KpisOverview extends Component {
|
|||
}
|
||||
|
||||
const rawData = yield response.json();
|
||||
|
||||
this.totals = this.processData(rawData.data);
|
||||
} catch (error) {
|
||||
// console.error('Error fetching KPI data:', error);
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
@label="Browsers" />
|
||||
</button>
|
||||
|
||||
<button type="button" class="gh-stats-tab {{if this.osTabSelected 'is-selected'}}" {{on "click" this.changeTabToOSs}}>
|
||||
{{!-- <button type="button" class="gh-stats-tab {{if this.osTabSelected 'is-selected'}}" {{on "click" this.changeTabToOSs}}>
|
||||
<Stats::Parts::Metric
|
||||
@label="Operating systems" />
|
||||
</button>
|
||||
</button> --}}
|
||||
</div>
|
||||
|
||||
<Stats::Charts::Technical @chartDays={{@chartDays}} @audience={{@audience}} @selected={{this.selected}} />
|
|
@ -73,7 +73,7 @@
|
|||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
height: 2px;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -107,4 +107,31 @@
|
|||
letter-spacing: -0.05em;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.gh-stats-piechart-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gh-stats-piechart-container table {
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.gh-stats-piechart-container .table td,
|
||||
.gh-stats-piechart-container.table th,
|
||||
.gh-stats-piechart-container table td,
|
||||
.gh-stats-piechart-container table th {
|
||||
padding: 6px 6px 6px 0px;
|
||||
}
|
||||
|
||||
.gh-stats-piechart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
margin-right: -20px;
|
||||
}
|
|
@ -37,27 +37,27 @@
|
|||
|
||||
<section class="view-container gh-stats">
|
||||
<section class="gh-stats-container">
|
||||
<Stats::KpisOverview @chartDays={{this.days}} @audience={{this.audience}} />
|
||||
<Stats::KpisOverview @chartDays={{this.chartDays}} @audience={{this.audience}} />
|
||||
</section>
|
||||
|
||||
<section class="gh-stats-grid cols-2">
|
||||
<div class="gh-stats-container">
|
||||
<h5 class="gh-stats-metric-label">Top posts & pages</h5>
|
||||
<Stats::Charts::TopPages @chartDays={{this.days}} @audience={{this.audience}} />
|
||||
<Stats::Charts::TopPages @chartDays={{this.chartDays}} @audience={{this.audience}} />
|
||||
</div>
|
||||
<div class="gh-stats-container">
|
||||
<h5 class="gh-stats-metric-label">Sources</h5>
|
||||
<Stats::Charts::TopSources @chartDays={{this.days}} @audience={{this.audience}} />
|
||||
<Stats::Charts::TopSources @chartDays={{this.chartDays}} @audience={{this.audience}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="gh-stats-grid cols-2">
|
||||
<div class="gh-stats-container">
|
||||
<h5 class="gh-stats-metric-label">Top locations</h5>
|
||||
<Stats::Charts::TopLocations @chartDays={{this.days}} @audience={{this.audience}} />
|
||||
<Stats::Charts::TopLocations @chartDays={{this.chartDays}} @audience={{this.audience}} />
|
||||
</div>
|
||||
<div class="gh-stats-container">
|
||||
<Stats::TechnicalOverview @chartDays={{this.days}} @audience={{this.audience}} />
|
||||
<Stats::TechnicalOverview @chartDays={{this.chartDays}} @audience={{this.audience}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue