Skip to content

Commit 86e8130

Browse files
add chart stats on component
1 parent 391664a commit 86e8130

File tree

3 files changed

+130
-31
lines changed

3 files changed

+130
-31
lines changed

src/registry/routes/component-info.ts

+32-22
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import infoView from '../views/info';
66
import isUrlDiscoverable from './helpers/is-url-discoverable';
77
import * as urlBuilder from '../domain/url-builder';
88
import type { Repository } from '../domain/repository';
9-
import { Component, Config } from '../../types';
9+
import { Component, ComponentDetail, Config } from '../../types';
1010
import { Request, Response } from 'express';
1111

1212
function getParams(component: Component) {
@@ -38,9 +38,10 @@ function componentInfo(
3838
err: InfoError | string | null,
3939
req: Request,
4040
res: Response,
41-
component: Component
41+
component?: Component,
42+
componentDetail?: ComponentDetail
4243
): void {
43-
if (err) {
44+
if (!component || err) {
4445
res.errorDetails = (err as any).registryError || err;
4546
res.status(404).json(err);
4647
return;
@@ -67,6 +68,7 @@ function componentInfo(
6768
res.send(
6869
infoView({
6970
component,
71+
componentDetail,
7072
dependencies: Object.keys(component.dependencies || {}),
7173
href,
7274
parsedAuthor,
@@ -89,24 +91,32 @@ export default function componentInfoRoute(
8991
conf: Config,
9092
repository: Repository
9193
) {
92-
return function (req: Request, res: Response): void {
93-
fromPromise(repository.getComponent)(
94-
req.params['componentName'],
95-
req.params['componentVersion'],
96-
(registryError: any, component) => {
97-
if (registryError && conf.fallbackRegistryUrl) {
98-
return getComponentFallback.getComponentInfo(
99-
conf,
100-
req,
101-
res,
102-
registryError,
103-
(fallbackError, fallbackComponent) =>
104-
componentInfo(fallbackError, req, res, fallbackComponent)
105-
);
106-
}
107-
108-
componentInfo(registryError, req, res, component);
94+
async function handler(req: Request, res: Response): Promise<void> {
95+
try {
96+
const history = await repository
97+
.getComponentsDetails()
98+
.catch(() => undefined);
99+
const componentDetail = history?.components[req.params['componentName']];
100+
const component = await repository.getComponent(
101+
req.params['componentName'],
102+
req.params['componentVersion']
103+
);
104+
componentInfo(null, req, res, component, componentDetail);
105+
} catch (registryError) {
106+
if (conf.fallbackRegistryUrl) {
107+
return getComponentFallback.getComponentInfo(
108+
conf,
109+
req,
110+
res,
111+
registryError as any,
112+
(fallbackError, fallbackComponent) =>
113+
componentInfo(fallbackError, req, res, fallbackComponent)
114+
);
109115
}
110-
);
111-
};
116+
117+
componentInfo(registryError as any, req, res);
118+
}
119+
}
120+
121+
return handler;
112122
}

src/registry/views/info.ts

+90-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from '../../types';
1+
import { Component, ComponentDetail } from '../../types';
22

33
import getComponentAuthor from './partials/component-author';
44
import getComponentParameters from './partials/component-parameters';
@@ -12,13 +12,89 @@ import isTemplateLegacy from '../../utils/is-template-legacy';
1212
interface Vm {
1313
parsedAuthor: { name?: string; email?: string; url?: string };
1414
component: Component;
15+
componentDetail?: ComponentDetail;
1516
dependencies: string[];
1617
href: string;
1718
sandBoxDefaultQs: string;
1819
title: string;
1920
repositoryUrl: string | null;
2021
}
2122

23+
function statsJs(componentDetail: ComponentDetail) {
24+
return `
25+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
26+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
27+
<script>
28+
(function () {
29+
const componentDetail = ${JSON.stringify(componentDetail)};
30+
window.componentDetail = componentDetail;
31+
const ctx = document.getElementById('stats');
32+
const dataPoints = [];
33+
const versionNumbers = Object.keys(componentDetail);
34+
35+
for (const versionNumber of versionNumbers) {
36+
const versionData = componentDetail[versionNumber];
37+
const date = new Date(versionData.publishDate);
38+
const size = Math.round(versionData.templateSize / 1024);
39+
40+
// Add the data point to the array
41+
dataPoints.push({ x: date, y: size, version: versionNumber });
42+
}
43+
44+
const dataset = {
45+
label: 'guest-stay-config',
46+
data: dataPoints,
47+
tension: 0.1,
48+
borderWidth: 1
49+
}
50+
51+
new Chart(ctx, {
52+
type: 'line',
53+
data: {
54+
datasets: [dataset]
55+
},
56+
options: {
57+
plugins: {
58+
tooltip: {
59+
callbacks: {
60+
footer(items) {
61+
const version = items[0].raw.version;
62+
return 'Version: ' + version;
63+
}
64+
}
65+
}
66+
},
67+
title: {
68+
display: true,
69+
text: "Package Sizes Over Time",
70+
},
71+
scales: {
72+
x: {
73+
type: "time",
74+
time: {
75+
unit: "day",
76+
},
77+
display: true,
78+
title: {
79+
display: true,
80+
text: 'Date published'
81+
}
82+
},
83+
y: {
84+
display: true,
85+
title: {
86+
display: true,
87+
text: 'Size in KB'
88+
}
89+
},
90+
},
91+
}
92+
});
93+
}());
94+
</script>
95+
`;
96+
}
97+
2298
export default function info(vm: Vm): string {
2399
const componentAuthor = getComponentAuthor(vm);
24100
const componentParameters = getComponentParameters(vm);
@@ -49,10 +125,18 @@ export default function info(vm: Vm): string {
49125
? 'legacy'
50126
: compiler + '@' + component.oc.files.template.version
51127
})`;
128+
const statsAvailable =
129+
!!vm.componentDetail && Object.keys(vm.componentDetail).length > 1;
52130

53131
const content = `<a class="back" href="${href}">&lt;&lt; All components</a>
54132
<h2>${component.name} &nbsp;${componentVersions()}</h2>
55133
<p class="w-100">${component.description} ${componentState()}</p>
134+
${
135+
statsAvailable
136+
? `<h3>Stats</h3>
137+
<canvas id="stats" width="400" height="200"></canvas>`
138+
: ''
139+
}
56140
<h3>Component Info</h3>
57141
${property('Repository', repositoryUrl || 'not available', !!repositoryUrl)}
58142
${componentAuthor()}
@@ -82,9 +166,12 @@ export default function info(vm: Vm): string {
82166
</h3>
83167
<iframe class="preview" src="~preview/${sandBoxDefaultQs}"></iframe>`;
84168

85-
const scripts = `<script>var thisComponentHref="${href}${component.name}/";
169+
const scripts = `
170+
<script>var thisComponentHref="${href}${component.name}/";
86171
${infoJS}
87-
</script>`;
172+
</script>
173+
${statsAvailable ? statsJs(vm.componentDetail!) : ''}
174+
`;
88175

89176
return layout({ content, scripts });
90177
}

src/types.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ export interface TemplateInfo {
3232
version: string;
3333
}
3434

35+
export type ComponentDetail = {
36+
[componentVersion: string]: {
37+
publishDate: number;
38+
templateSize?: number;
39+
};
40+
};
41+
3542
export interface ComponentsDetails {
3643
components: {
37-
[componentName: string]: {
38-
[componentVersion: string]: {
39-
publishDate: number;
40-
templateSize?: number;
41-
};
42-
};
44+
[componentName: string]: ComponentDetail;
4345
};
4446
lastEdit: number;
4547
}

0 commit comments

Comments
 (0)