Skip to content

Commit 439b793

Browse files
committed
Change chart data to multi series for multi-metric support
1 parent 6adfa23 commit 439b793

File tree

11 files changed

+140
-79
lines changed

11 files changed

+140
-79
lines changed

demo/src/payload.config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,19 @@ export default buildConfig({
5454
widgets: [
5555
{
5656
type: "chart",
57-
metric: "pageViews",
58-
timeframe: "7d",
57+
metrics: ["pageViews", "uniqueVisitors"],
58+
timeframe: "30d",
5959
idMatcher: (document: any) => `/articles/${document.slug}`,
6060
},
6161
{
6262
type: "chart",
63-
metric: "pageViews",
64-
timeframe: "30d",
63+
metrics: ["pageViews"],
64+
timeframe: "7d",
6565
idMatcher: (document: any) => `/articles/${document.slug}`,
6666
},
6767
{
6868
type: "chart",
69-
metric: "uniqueVisitors",
69+
metrics: ["uniqueVisitors"],
7070
timeframe: "month",
7171
idMatcher: (document: any) => `/articles/${document.slug}`,
7272
},
Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,23 @@ import React, {
99
import type {
1010
DashboardAnalyticsConfig,
1111
ChartDataPoint,
12+
ChartData,
1213
ChartWidget,
13-
IdMatcherFunction,
14+
ChartWidgetMetrics,
1415
} from "../../types";
1516
import type { AxisOptions } from "react-charts";
1617
import { useDocumentInfo } from "payload/components/utilities";
1718
import { MetricMap } from "../../providers/plausible/client";
1819
import { useTheme } from "payload/dist/admin/components/utilities/Theme";
1920

20-
type ChartData = {
21-
label: string;
22-
data: ChartDataPoint[];
23-
};
24-
25-
type ChartOptions = {
21+
/* type ChartOptions = {
2622
timeframe?: string;
27-
metric: ChartWidget["metric"];
23+
metrics: ChartWidget["metrics"];
2824
idMatcher: IdMatcherFunction;
29-
};
25+
}; */
3026

3127
type Props = {
32-
options: ChartOptions;
28+
options: ChartWidget;
3329
};
3430

3531
const ChartComponent = lazy(() =>
@@ -38,14 +34,14 @@ const ChartComponent = lazy(() =>
3834
})
3935
);
4036

41-
const ViewsChart: React.FC<Props> = ({ options }) => {
42-
const [chartData, setChartData] = useState<ChartData[]>([]);
37+
const PageViewsChart: React.FC<Props> = ({ options }) => {
38+
const [chartData, setChartData] = useState<ChartData>([]);
4339
const [isLoading, setIsLoading] = useState<boolean>(true);
4440
const chartRef = useRef<any>(null);
4541
const theme = useTheme();
4642
const { publishedDoc } = useDocumentInfo();
4743

48-
const { timeframe, metric, idMatcher } = options;
44+
const { timeframe, metrics, idMatcher, label } = options;
4945

5046
const pageId = useMemo(() => {
5147
if (publishedDoc) return idMatcher(publishedDoc);
@@ -68,19 +64,19 @@ const ViewsChart: React.FC<Props> = ({ options }) => {
6864
},
6965
body: JSON.stringify({
7066
timeframe: timeframe,
71-
metric: metric,
67+
metrics: metrics,
7268
pageId: pageId,
7369
}),
7470
}).then((response) => response.json());
7571

76-
getChartData.then((data: ChartDataPoint[]) => {
77-
const processedData: ChartData[] = [
72+
getChartData.then((data: ChartData) => {
73+
/* const processedData: ChartData = [
7874
{
79-
label: MetricMap[metric].label,
75+
label: "test",
8076
data: data,
8177
},
82-
];
83-
setChartData(processedData);
78+
]; */
79+
setChartData(data);
8480
setIsLoading(false);
8581
});
8682
} else {
@@ -98,6 +94,19 @@ const ViewsChart: React.FC<Props> = ({ options }) => {
9894
importChart();
9995
}, []);
10096

97+
const chartLabel = useMemo(() => {
98+
if (label) return label;
99+
100+
const metricValues: string[] = [];
101+
102+
Object.entries(MetricMap).forEach(([key, value]) => {
103+
/* @ts-ignore */
104+
if (metrics.includes(key)) metricValues.push(value.label);
105+
});
106+
107+
return metricValues.join(", ");
108+
}, [options]);
109+
101110
const primaryAxis = React.useMemo<AxisOptions<ChartDataPoint>>(() => {
102111
return {
103112
getValue: (datum) => datum.timestamp,
@@ -131,15 +140,15 @@ const ViewsChart: React.FC<Props> = ({ options }) => {
131140
chartData.length > 0 ? (
132141
<>
133142
<h1 style={{ fontSize: "1.25rem", marginBottom: "0.5rem" }}>
134-
{MetricMap[metric].label} ({timeframeIndicator})
143+
{chartLabel} ({timeframeIndicator})
135144
</h1>
136145
<div style={{ minHeight: "200px", position: "relative" }}>
137146
<ChartComponent
138147
options={{
139148
data: chartData,
140149
dark: theme.theme === "dark",
141150
initialHeight: 220,
142-
tooltip: false,
151+
tooltip: options.metrics.length > 1,
143152
/* @ts-ignore */
144153
primaryAxis,
145154
/* @ts-ignore */
@@ -151,18 +160,18 @@ const ViewsChart: React.FC<Props> = ({ options }) => {
151160
) : isLoading ? (
152161
<> Loading...</>
153162
) : (
154-
<div>No {MetricMap[metric].label} data found.</div>
163+
<div>No data found for {chartLabel}.</div>
155164
)}
156165
</section>
157166
);
158167
};
159168

160-
export const getViewsChart = (props?: any, options?: ChartOptions) => {
169+
export const getPageViewsChart = (props?: any, options?: ChartWidget) => {
161170
const combinedProps: Props = {
162171
...props,
163172
options,
164173
};
165-
return <ViewsChart {...combinedProps} />;
174+
return <PageViewsChart {...combinedProps} />;
166175
};
167176

168-
export default { ViewsChart, getViewsChart };
177+
export default { PageViewsChart, getPageViewsChart };

src/index.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ import getGlobalAggregateData from "./routes/getGlobalAggregateData";
1212
import getGlobalChartData from "./routes/getGlobalChartData";
1313
import getPageChartData from "./routes/getPageChartData";
1414
import type { CollectionConfig } from "payload/dist/collections/config/types";
15-
import { getViewsChart } from "./components/Charts/ViewsChart";
15+
import { getPageViewsChart } from "./components/Charts/PageViewsChart";
1616

17-
const InnerWidgetMap: Record<InnerWidget["type"], (config: any) => Field> = {
18-
chart: (config: ChartWidget) => ({
17+
const InnerWidgetMap: Record<
18+
InnerWidget["type"],
19+
(config: any, index: number) => Field
20+
> = {
21+
chart: (config: ChartWidget, index: number) => ({
1922
type: "ui",
20-
name: `chart_${config.metric}_${config.timeframe ?? "30d"}`,
23+
name: `chart_${index}_${config.timeframe ?? "30d"}`,
2124
admin: {
2225
position: "sidebar",
2326
components: {
24-
Field: (props: any) =>
25-
getViewsChart(props, {
26-
timeframe: config.timeframe,
27-
metric: config.metric,
28-
idMatcher: config.idMatcher,
29-
}),
27+
Field: (props: any) => getPageViewsChart(props, config),
3028
},
3129
},
3230
}),
@@ -80,10 +78,10 @@ const payloadDashboardAnalytics =
8078
...collection,
8179
fields: [
8280
...collection.fields,
83-
...targetCollection.widgets.map((widget) => {
81+
...targetCollection.widgets.map((widget, index) => {
8482
const field = InnerWidgetMap[widget.type];
8583

86-
return field(widget);
84+
return field(widget, index);
8785
}),
8886
],
8987
};

src/providers/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ type BaseOptions = {
88

99
export interface GlobalAggregateOptions extends BaseOptions {}
1010
export interface GlobalChartOptions extends BaseOptions {
11-
metric: ChartWidget["metric"];
11+
metrics: ChartWidget["metrics"];
1212
}
1313

1414
export interface PageAggregateOptions extends BaseOptions {}
1515
export interface PageChartOptions extends BaseOptions {
16-
metric: ChartWidget["metric"];
16+
metrics: ChartWidget["metrics"];
1717
pageId: string;
1818
}
1919

src/providers/plausible/client.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { PlausibleProvider } from "../../types";
2-
import { InnerWidget } from "../../types";
2+
import { InnerWidget, ChartWidgetMetrics } from "../../types";
33

44
type ClientOptions = {
55
endpoint: string;
66
timeframe?: string;
7-
metric?: InnerWidget["metric"];
7+
metrics?: InnerWidget["metrics"];
88
};
99

1010
export const MetricMap: Record<
11-
InnerWidget["metric"],
11+
ChartWidgetMetrics,
1212
{ label: string; value: string }
1313
> = {
1414
pageViews: {
@@ -19,7 +19,7 @@ export const MetricMap: Record<
1919
};
2020

2121
function client(provider: PlausibleProvider, options: ClientOptions) {
22-
const { endpoint, timeframe, metric } = options;
22+
const { endpoint, timeframe, metrics } = options;
2323
const host = provider.host ?? `https://plausible.io`;
2424
const apiVersion = `v1`; // for future use
2525

@@ -28,9 +28,22 @@ function client(provider: PlausibleProvider, options: ClientOptions) {
2828
const url = new URL(`${host}/api/${apiVersion}${endpoint}`);
2929
url.searchParams.append("site_id", provider.siteId);
3030

31-
const plausibleMetric = metric ? MetricMap[metric].value : "pageviews";
31+
const getMetrics = () => {
32+
const myMetrics: string[] = [];
33+
const availableMetrics = Object.entries(MetricMap);
3234

33-
const plausibleMetrics = [plausibleMetric];
35+
metrics?.forEach((metric) => {
36+
const foundMetric = availableMetrics.find((mappedMetric) => {
37+
return mappedMetric[0] === metric;
38+
});
39+
40+
if (foundMetric) myMetrics.push(foundMetric[1].value);
41+
});
42+
43+
return myMetrics;
44+
};
45+
46+
const plausibleMetrics = metrics?.length ? getMetrics() : "pageviews";
3447

3548
const baseUrl = String(url.href);
3649
url.searchParams.append("period", period);
@@ -39,7 +52,7 @@ function client(provider: PlausibleProvider, options: ClientOptions) {
3952
return {
4053
host: host,
4154
baseUrl: baseUrl,
42-
metric: plausibleMetric,
55+
metric: plausibleMetrics,
4356
url: url,
4457
fetch: async (customUrl?: string) => {
4558
const fetchUrl = customUrl ?? url.toString();
Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import type { PlausibleProvider, ChartDataPoint } from "../../types";
1+
import type { PlausibleProvider, ChartData } from "../../types";
22
import type { GlobalChartOptions } from "..";
33
import { MetricMap } from "./client";
44
import payload from "payload";
55
import client from "./client";
66

77
async function getGlobalChartData(
88
provider: PlausibleProvider,
9-
options?: GlobalChartOptions
9+
options: GlobalChartOptions
1010
) {
1111
const plausibleClient = client(provider, {
1212
endpoint: "/stats/timeseries",
1313
timeframe: options?.timeframe,
14-
metric: options?.metric,
14+
metrics: options?.metrics,
1515
});
1616

1717
const { results } = await plausibleClient
@@ -23,14 +23,29 @@ async function getGlobalChartData(
2323
payload.logger.error(error);
2424
});
2525

26-
const processedData: ChartDataPoint[] = results.map((item: any) => {
27-
return {
28-
timestamp: item.date,
29-
value: item[plausibleClient.metric],
30-
};
26+
/* @todo: fix types later */
27+
/* @ts-ignore */
28+
const dataSeries: ChartData = options.metrics.map((metric) => {
29+
const mappedMetric = Object.entries(MetricMap).find(([key, value]) => {
30+
return metric === key;
31+
});
32+
33+
if (mappedMetric) {
34+
const data = results.map((item: any) => {
35+
return {
36+
timestamp: item.date,
37+
value: item[mappedMetric[1].value],
38+
};
39+
});
40+
41+
return {
42+
label: mappedMetric[1].label,
43+
data: data,
44+
};
45+
}
3146
});
3247

33-
return processedData;
48+
return dataSeries;
3449
}
3550

3651
export default getGlobalChartData;

src/providers/plausible/getPageChartData.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { PlausibleProvider, ChartDataPoint } from "../../types";
1+
import type { PlausibleProvider, ChartData } from "../../types";
22
import type { PageChartOptions } from "..";
33
import { MetricMap } from "./client";
44
import payload from "payload";
@@ -11,7 +11,7 @@ async function getPageChartData(
1111
const plausibleClient = client(provider, {
1212
endpoint: "/stats/timeseries",
1313
timeframe: options?.timeframe,
14-
metric: options?.metric,
14+
metrics: options.metrics,
1515
});
1616

1717
const url = plausibleClient.url;
@@ -29,14 +29,29 @@ async function getPageChartData(
2929
payload.logger.error(error);
3030
});
3131

32-
const processedData: ChartDataPoint[] = results.map((item: any) => {
33-
return {
34-
timestamp: item.date,
35-
value: item[plausibleClient.metric],
36-
};
32+
/* @todo: fix types later */
33+
/* @ts-ignore */
34+
const dataSeries: ChartData = options.metrics.map((metric) => {
35+
const mappedMetric = Object.entries(MetricMap).find(([key, value]) => {
36+
return metric === key;
37+
});
38+
39+
if (mappedMetric) {
40+
const data = results.map((item: any) => {
41+
return {
42+
timestamp: item.date,
43+
value: item[mappedMetric[1].value],
44+
};
45+
});
46+
47+
return {
48+
label: mappedMetric[1].label,
49+
data: data,
50+
};
51+
}
3752
});
3853

39-
return processedData;
54+
return dataSeries;
4055
}
4156

4257
export default getPageChartData;

0 commit comments

Comments
 (0)