Skip to content

Commit ad2cc80

Browse files
Merge pull request #1416 from opencomponents/add-history-endpoint
Add history endpoint and lazy load it on the UI
2 parents e8c5cf2 + 8bade96 commit ad2cc80

File tree

9 files changed

+208
-120
lines changed

9 files changed

+208
-120
lines changed

src/registry/router.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ComponentInfoRoute from './routes/component-info';
88
import ComponentPreviewRoute from './routes/component-preview';
99
import ComponentsRoute from './routes/components';
1010
import DependenciesRoute from './routes/dependencies';
11+
import HistoryRoute from './routes/history';
1112
import PluginsRoute from './routes/plugins';
1213
import PublishRoute from './routes/publish';
1314
import StaticRedirectorRoute from './routes/static-redirector';
@@ -22,7 +23,8 @@ export function create(app: Express, conf: Config, repository: Repository) {
2223
publish: PublishRoute(repository),
2324
staticRedirector: StaticRedirectorRoute(repository),
2425
plugins: PluginsRoute(conf),
25-
dependencies: DependenciesRoute(conf)
26+
dependencies: DependenciesRoute(conf),
27+
history: HistoryRoute(repository)
2628
};
2729

2830
const prefix = conf.prefix;
@@ -38,6 +40,7 @@ export function create(app: Express, conf: Config, repository: Repository) {
3840

3941
app.get(`${prefix}~registry/plugins`, routes.plugins);
4042
app.get(`${prefix}~registry/dependencies`, routes.dependencies);
43+
app.get(`${prefix}~registry/history`, routes.history);
4144

4245
if (conf.local) {
4346
app.get(

src/registry/routes/history.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Request, Response } from 'express';
2+
import type { Repository } from '../domain/repository';
3+
import getComponentsHistory from './helpers/get-components-history';
4+
5+
export default function history(repository: Repository) {
6+
return async (_req: Request, res: Response): Promise<void> => {
7+
try {
8+
if (res.conf.discovery && !res.conf.local) {
9+
const details = await repository.getComponentsDetails();
10+
const componentsHistory = getComponentsHistory(details);
11+
res.setHeader('Cache-Control', 'public, max-age=600');
12+
res.status(200).json({ componentsHistory });
13+
} else {
14+
res.status(401);
15+
}
16+
} catch (error) {
17+
res.status(500).json({ error: 'Internal server error' });
18+
}
19+
};
20+
}

src/registry/routes/index.ts

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import parseAuthor from 'parse-author';
66
import dateStringified from '../../utils/date-stringify';
77
import indexView from '../views';
88
import getAvailableDependencies from './helpers/get-available-dependencies';
9-
import getComponentsHistory from './helpers/get-components-history';
109
import urlBuilder = require('../domain/url-builder');
1110
import type { IncomingHttpHeaders } from 'node:http';
1211
import type { NextFunction, Request, Response } from 'express';
@@ -76,41 +75,36 @@ export default function (repository: Repository) {
7675
componentsInfo = componentsInfo.sort((a, b) =>
7776
a.name.localeCompare(b.name)
7877
);
79-
fromPromise(repository.getComponentsDetails)((err, details) => {
80-
if (err) console.log(err);
81-
res.send(
82-
indexView(
83-
// @ts-ignore
84-
Object.assign(baseResponse, {
85-
availableDependencies: getAvailableDependencies(
86-
res.conf.dependencies
87-
),
88-
availablePlugins: res.conf.plugins,
89-
components: componentsInfo,
90-
componentsReleases,
91-
componentsList: componentsInfo.map((component) => {
92-
const state: 'deprecated' | 'experimental' | '' =
93-
component?.oc?.state || '';
94-
if (state) {
95-
stateCounts[state] = (stateCounts[state] || 0) + 1;
96-
}
78+
res.send(
79+
indexView(
80+
// @ts-ignore
81+
Object.assign(baseResponse, {
82+
availableDependencies: getAvailableDependencies(
83+
res.conf.dependencies
84+
),
85+
availablePlugins: res.conf.plugins,
86+
components: componentsInfo,
87+
componentsReleases,
88+
componentsList: componentsInfo.map((component) => {
89+
const state: 'deprecated' | 'experimental' | '' =
90+
component?.oc?.state || '';
91+
if (state) {
92+
stateCounts[state] = (stateCounts[state] || 0) + 1;
93+
}
9794

98-
return {
99-
name: component.name,
100-
author: component.author,
101-
state
102-
};
103-
}),
104-
componentsHistory:
105-
!res.conf.local && getComponentsHistory(details),
106-
q: req.query['q'] || '',
107-
stateCounts,
108-
templates: repository.getTemplatesInfo(),
109-
title: 'OpenComponents Registry'
110-
})
111-
)
112-
);
113-
});
95+
return {
96+
name: component.name,
97+
author: component.author,
98+
state
99+
};
100+
}),
101+
q: req.query['q'] || '',
102+
stateCounts,
103+
templates: repository.getTemplatesInfo(),
104+
title: 'OpenComponents Registry'
105+
})
106+
)
107+
);
114108
}
115109
);
116110
} else {

src/registry/views/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ ${indexJS}</script>`;
7171
components={vm.components}
7272
stateCounts={vm.stateCounts}
7373
/>
74-
<History componentsHistory={vm.componentsHistory} />
74+
<History />
7575
<Templates templates={vm.templates} />
7676
<Dependencies availableDependencies={vm.availableDependencies} />
7777
<Plugins

src/registry/views/partials/components-history.tsx

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,15 @@
1-
import type { VM } from '../../../types';
2-
3-
type ComponentHistory = VM['componentsHistory'];
4-
type ComponentHistoryItem = Exclude<ComponentHistory, undefined>[number];
5-
6-
const ComponentsHistory = (props: { componentsHistory: ComponentHistory }) => {
7-
const componentRow = ({
8-
name,
9-
publishDate,
10-
version,
11-
templateSize
12-
}: ComponentHistoryItem) => (
13-
<a href={`${name}/${version}/~info`}>
14-
<div class="componentRow row table">
15-
<p class="release">
16-
{publishDate} - Published {name}@{version}
17-
{templateSize ? ` [${Math.round(templateSize / 1024)} kb]` : ''}
18-
</p>
19-
</div>
20-
</a>
21-
);
22-
1+
const ComponentsHistory = () => {
232
return (
243
<div id="components-history" class="box">
25-
{props.componentsHistory ? props.componentsHistory.map(componentRow) : ''}
4+
<div id="history-loader" class="loader">
5+
<p>Loading components history...</p>
6+
</div>
7+
<div id="history-content" style="display: none;">
8+
{/* Content will be populated by JavaScript */}
9+
</div>
10+
<div id="history-error" style="display: none;">
11+
<p>Failed to load components history. Please try again later.</p>
12+
</div>
2613
</div>
2714
);
2815
};

src/registry/views/partials/components-templates.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const ComponentsTemplates = (props: { templates: Templates }) => {
1919
const templateRow = ({ externals, type, version }: Template) => {
2020
const externalLinks = externals.map(externalLink);
2121
const externalsLabel = externalLinks.length ? (
22-
<>(Externals: {externalLinks})</>
22+
<> (Externals: {externalLinks}) </>
2323
) : null;
2424

2525
return (

src/registry/views/static/index.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,69 @@ oc.cmd.push(function() {
5959
return false;
6060
};
6161
62+
var loadComponentsHistory = function() {
63+
var historyLoader = $('#history-loader');
64+
var historyContent = $('#history-content');
65+
var historyError = $('#history-error');
66+
67+
// Show loader
68+
historyLoader.show();
69+
historyContent.hide();
70+
historyError.hide();
71+
72+
// Fetch history data
73+
fetch('~registry/history')
74+
.then(function(response) {
75+
if (!response.ok) {
76+
throw new Error('Failed to fetch history');
77+
}
78+
return response.json();
79+
})
80+
.then(function(data) {
81+
var componentsHistory = data.componentsHistory || [];
82+
var historyHtml = '';
83+
84+
for (var i = 0; i < componentsHistory.length; i++) {
85+
var item = componentsHistory[i];
86+
var templateSizeText = item.templateSize ?
87+
' [' + Math.round(item.templateSize / 1024) + ' kb]' : '';
88+
89+
historyHtml += '<a href="' + item.name + '/' + item.version + '/~info">' +
90+
'<div class="componentRow row table">' +
91+
'<p class="release">' +
92+
item.publishDate + ' - Published ' + item.name + '@' + item.version +
93+
templateSizeText +
94+
'</p>' +
95+
'</div>' +
96+
'</a>';
97+
}
98+
99+
historyContent.html(historyHtml);
100+
historyLoader.hide();
101+
historyContent.show();
102+
})
103+
.catch(function(error) {
104+
console.error('Error loading components history:', error);
105+
historyLoader.hide();
106+
historyError.show();
107+
});
108+
};
109+
110+
var isHistoryLoaded = false;
111+
62112
var initialiseTabs = function() {
63113
var selectItem = function(target) {
64114
var $target = $(target);
65115
$('.box').hide();
66116
$target.show();
67117
$('#menuList a').removeClass('selected');
68118
$('#menuList a[href="' + target + '"]').addClass('selected');
119+
120+
// Load history data when history tab is selected for the first time
121+
if (target === '#components-history' && !isHistoryLoaded) {
122+
loadComponentsHistory();
123+
isHistoryLoaded = true;
124+
}
69125
};
70126
71127
var hash = location.href.split('#')[1] || '';

0 commit comments

Comments
 (0)