Skip to content

Commit

Permalink
✨ show average daily pageviews in charts list and references tab (#4267)
Browse files Browse the repository at this point in the history
This was a small request by authors - surfacing pageviews more prominently is useful prioritizing work on charts.
This PR shows the average daily pageviews in the chart index page and allows users to sort by these pageviews.
An index on analytics_pageviews is added so that joins can be more performant
  • Loading branch information
danyx23 authored Dec 13, 2024
1 parent 5a48228 commit 6439abc
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 8 deletions.
15 changes: 15 additions & 0 deletions 1733503871571-AddPageviewsUrlIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class AddPageviewsUrlIndex1733503871571 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// add an index on url of the analtyics_pageviews table
// we already have one on (day, url) but we never join that way
await queryRunner.query(
`CREATE INDEX analytics_pageviews_url_index ON analytics_pageviews (url)`
)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX analytics_pageviews_url_index`)
}
}
31 changes: 25 additions & 6 deletions adminSiteClient/ChartIndexPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { observable, computed, action, runInAction } from "mobx"

import { TextField } from "./Forms.js"
import { AdminLayout } from "./AdminLayout.js"
import { ChartList, ChartListItem } from "./ChartList.js"
import { ChartList, ChartListItem, SortConfig } from "./ChartList.js"
import { AdminAppContext, AdminAppContextType } from "./AdminAppContext.js"
import {
buildSearchWordsFromSearchString,
filterFunctionForSearchWords,
highlightFunctionForSearchWords,
SearchWord,
} from "../adminShared/search.js"
import { sortNumeric, SortOrder } from "@ourworldindata/utils"

@observer
export class ChartIndexPage extends React.Component {
Expand All @@ -21,6 +22,8 @@ export class ChartIndexPage extends React.Component {
@observable searchInput?: string
@observable maxVisibleCharts = 50
@observable charts: ChartListItem[] = []
@observable sortBy: "pageviewsPerDay" | null = null
@observable sortConfig: SortConfig = null

@computed get searchWords(): SearchWord[] {
const { searchInput } = this
Expand All @@ -31,7 +34,8 @@ export class ChartIndexPage extends React.Component {
}

@computed get allChartsToShow(): ChartListItem[] {
const { searchWords, charts } = this
const { searchWords, charts, sortConfig } = this
let filtered = charts
if (searchWords.length > 0) {
const filterFn = filterFunctionForSearchWords(
searchWords,
Expand All @@ -48,10 +52,19 @@ export class ChartIndexPage extends React.Component {
...chart.tags.map((tag) => tag.name),
]
)
return charts.filter(filterFn)
} else {
return this.charts
filtered = charts.filter(filterFn)
}

// Apply sorting if needed
if (sortConfig?.field === "pageviewsPerDay") {
return sortNumeric(
[...filtered],
(chart) => chart.pageviewsPerDay,
sortConfig.direction === "asc" ? SortOrder.asc : SortOrder.desc
)
}

return filtered
}

@computed get chartsToShow(): ChartListItem[] {
Expand All @@ -67,8 +80,12 @@ export class ChartIndexPage extends React.Component {
this.maxVisibleCharts += 100
}

@action.bound onSort(sortConfig: SortConfig) {
this.sortConfig = sortConfig
}

render() {
const { chartsToShow, searchInput, numTotalCharts } = this
const { chartsToShow, searchInput, numTotalCharts, sortConfig } = this

const highlight = highlightFunctionForSearchWords(this.searchWords)

Expand All @@ -93,6 +110,8 @@ export class ChartIndexPage extends React.Component {
onDelete={action((c: ChartListItem) =>
this.charts.splice(this.charts.indexOf(c), 1)
)}
onSort={this.onSort}
sortConfig={sortConfig}
/>
{!searchInput && (
<button
Expand Down
31 changes: 30 additions & 1 deletion adminSiteClient/ChartList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ export interface ChartListItem {
isInheritanceEnabled?: boolean

tags: DbChartTagJoin[]
pageviewsPerDay: number
}

export type SortConfig = {
field: "pageviewsPerDay"
direction: "asc" | "desc"
} | null

@observer
export class ChartList extends React.Component<{
charts: ChartListItem[]
searchHighlight?: (text: string) => string | React.ReactElement
onDelete?: (chart: ChartListItem) => void
onSort?: (sort: SortConfig) => void
sortConfig?: SortConfig
}> {
static contextType = AdminAppContext
context!: AdminAppContextType
Expand Down Expand Up @@ -109,9 +117,24 @@ export class ChartList extends React.Component<{
}

render() {
const { charts, searchHighlight } = this.props
const { charts, searchHighlight, sortConfig, onSort } = this.props
const { availableTags } = this

const getSortIndicator = () => {
if (!sortConfig || sortConfig.field !== "pageviewsPerDay") return ""
return sortConfig.direction === "desc" ? " ↓" : " ↑"
}

const handleSortClick = () => {
if (!sortConfig || sortConfig.field !== "pageviewsPerDay") {
onSort?.({ field: "pageviewsPerDay", direction: "desc" })
} else if (sortConfig.direction === "desc") {
onSort?.({ field: "pageviewsPerDay", direction: "asc" })
} else {
onSort?.(null)
}
}

// if the first chart has inheritance information, we assume all charts have it
const showInheritanceColumn =
charts[0]?.isInheritanceEnabled !== undefined
Expand All @@ -128,6 +151,12 @@ export class ChartList extends React.Component<{
<th>Tags</th>
<th>Published</th>
<th>Last Updated</th>
<th
style={{ cursor: "pointer" }}
onClick={handleSortClick}
>
views/day{getSortIndicator()}
</th>
<th></th>
<th></th>
</tr>
Expand Down
1 change: 1 addition & 0 deletions adminSiteClient/ChartRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class ChartRow extends React.Component<{
by={highlight(chart.lastEditedBy)}
/>
</td>
<td>{chart.pageviewsPerDay?.toLocaleString() ?? "0"}</td>
<td>
<Link
to={`/charts/${chart.id}/edit`}
Expand Down
11 changes: 11 additions & 0 deletions adminSiteClient/EditorReferencesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
formatValue,
ChartRedirect,
partition,
round,
} from "@ourworldindata/utils"
import { AbstractChartEditor, References } from "./AbstractChartEditor.js"
import {
Expand Down Expand Up @@ -208,6 +209,16 @@ export class EditorReferencesTabForChart extends React.Component<{
<strong>Last 365 days:</strong>{" "}
{this.renderPageview(this.pageviews?.views_365d)}
</div>
<div>
<strong>
Average pageviews per day over the last year:
</strong>{" "}
{this.renderPageview(
this.pageviews?.views_365d
? round(this.pageviews?.views_365d / 365, 1)
: undefined
)}
</div>
</div>
<small className="form-text text-muted">
Pageview numbers are inaccurate when the chart has been
Expand Down
4 changes: 4 additions & 0 deletions adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ getRouteWithROTransaction(apiRouter, "/charts.json", async (req, res, trx) => {
SELECT ${oldChartFieldList} FROM charts
JOIN chart_configs ON chart_configs.id = charts.configId
JOIN users lastEditedByUser ON lastEditedByUser.id = charts.lastEditedByUserId
LEFT JOIN analytics_pageviews on analytics_pageviews.url = CONCAT("https://ourworldindata.org/grapher/", chart_configs.slug)
LEFT JOIN users publishedByUser ON publishedByUser.id = charts.publishedByUserId
ORDER BY charts.lastEditedAt DESC LIMIT ?
`,
Expand Down Expand Up @@ -1595,6 +1596,7 @@ getRouteWithROTransaction(
JOIN chart_configs ON chart_configs.id = charts.configId
JOIN users lastEditedByUser ON lastEditedByUser.id = charts.lastEditedByUserId
LEFT JOIN users publishedByUser ON publishedByUser.id = charts.publishedByUserId
LEFT JOIN analytics_pageviews on analytics_pageviews.url = CONCAT("https://ourworldindata.org/grapher/", chart_configs.slug)
JOIN chart_dimensions cd ON cd.chartId = charts.id
WHERE cd.variableId = ?
GROUP BY charts.id
Expand Down Expand Up @@ -2081,6 +2083,7 @@ getRouteWithROTransaction(
JOIN variables AS v ON cd.variableId = v.id
JOIN users lastEditedByUser ON lastEditedByUser.id = charts.lastEditedByUserId
LEFT JOIN users publishedByUser ON publishedByUser.id = charts.publishedByUserId
LEFT JOIN analytics_pageviews on analytics_pageviews.url = CONCAT("https://ourworldindata.org/grapher/", chart_configs.slug)
WHERE v.datasetId = ?
GROUP BY charts.id
`,
Expand Down Expand Up @@ -2478,6 +2481,7 @@ getRouteWithROTransaction(
LEFT JOIN chart_tags ct ON ct.chartId=charts.id
JOIN users lastEditedByUser ON lastEditedByUser.id = charts.lastEditedByUserId
LEFT JOIN users publishedByUser ON publishedByUser.id = charts.publishedByUserId
LEFT JOIN analytics_pageviews on analytics_pageviews.url = CONCAT("https://ourworldindata.org/grapher/", chart_configs.slug)
WHERE ct.tagId ${tagId === UNCATEGORIZED_TAG_ID ? "IS NULL" : "= ?"}
GROUP BY charts.id
ORDER BY charts.updatedAt DESC
Expand Down
3 changes: 2 additions & 1 deletion db/model/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,8 @@ export const oldChartFieldList = `
lastEditedByUser.fullName AS lastEditedBy,
charts.publishedAt,
charts.publishedByUserId,
publishedByUser.fullName AS publishedBy
publishedByUser.fullName AS publishedBy,
round(views_365d / 365, 1) as pageviewsPerDay
`
// TODO: replace this with getBySlug and pick

Expand Down

0 comments on commit 6439abc

Please sign in to comment.