Skip to content

Commit 13f2023

Browse files
authored
LDEV-5867 refactor changelog, split business logic, add tests (#71)
1 parent 058d776 commit 13f2023

File tree

6 files changed

+527
-109
lines changed

6 files changed

+527
-109
lines changed

apps/download/changelog.cfc

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
component {
2+
3+
function init( required download ) {
4+
variables.download = arguments.download;
5+
return this;
6+
}
7+
8+
/**
9+
* Process raw versions from the update provider and filter them for the changelog
10+
* @versions The raw versions struct from download.getVersions()
11+
* @returns Struct with { major: {}, latestSnapshots: {} }
12+
*/
13+
function processVersions( required struct versions ) localmode=true {
14+
var major = {};
15+
var snapshots = {};
16+
17+
// add types to each version
18+
loop struct=arguments.versions index="local.vs" item="local.data" {
19+
if ( findNoCase( "-snapshot", data.version ) ) {
20+
data['type'] = "snapshots";
21+
} else if ( findNoCase( "-rc", data.version ) ) {
22+
data['type'] = "rc";
23+
} else if ( findNoCase( "-beta", data.version ) ) {
24+
data['type'] = "beta";
25+
} else if ( findNoCase( "-alpha", data.version ) ) {
26+
data['type'] = "alpha";
27+
} else {
28+
data['type'] = "releases";
29+
}
30+
data['versionNoAppendix'] = data.version;
31+
32+
if ( data.type != "snapshots" ) {
33+
major[ vs ] = data;
34+
} else {
35+
// Track all snapshots - key (vs) is already the sorted version
36+
// Extract major version from key: "07" from "07.000.001.0044.000"
37+
var majorVersion = listFirst( vs, "." );
38+
39+
// Keep only the latest snapshot per major version (highest key)
40+
if ( !structKeyExists( snapshots, majorVersion ) || vs > snapshots[ majorVersion ].key ) {
41+
snapshots[ majorVersion ] = {
42+
key: vs,
43+
data: data
44+
};
45+
}
46+
}
47+
}
48+
49+
// Promote the latest snapshot per major version to major
50+
// BUT only if there isn't already an RC/Beta/Release with the same version
51+
structEach( snapshots, function( majorVer, snapshot ) {
52+
var snapshotKey = snapshot.key;
53+
var snapshotData = snapshot.data;
54+
55+
// Extract version prefix (first 4 parts) to check for duplicates
56+
// e.g., "07.000.001.0044" from "07.000.001.0044.000"
57+
var versionPrefix = listDeleteAt( snapshotKey, listLen( snapshotKey, "." ), "." );
58+
59+
// Check if there's already an RC/Beta/Release with same version
60+
var hasDuplicate = false;
61+
loop list=".050,.075,.100" index="local.qualifier" {
62+
if ( structKeyExists( major, versionPrefix & qualifier ) ) {
63+
hasDuplicate = true;
64+
break;
65+
}
66+
}
67+
68+
// If no duplicate, promote this snapshot to major
69+
if ( !hasDuplicate ) {
70+
major[ snapshotKey ] = snapshotData;
71+
}
72+
});
73+
74+
return {
75+
major: major,
76+
latestSnapshots: snapshots
77+
};
78+
}
79+
80+
/**
81+
* Get sorted array of version keys for display
82+
* @major The major versions struct
83+
* @returns Array of version keys, sorted newest first
84+
*/
85+
function getSortedVersions( required struct major ) localmode=true {
86+
return structKeyArray( arguments.major ).reverse().sort( "text", "desc" );
87+
}
88+
89+
/**
90+
* Sort changelog struct keys by version, filtering to only include versions matching the major version filter
91+
* @changelog The changelog struct from download.getChangelog() - keys are fix versions, values are ticket structs
92+
* @majorVersionFilter The major version to filter by (e.g., "7.0")
93+
* @returns Array of version keys, sorted newest first, filtered to only include matching major versions
94+
*/
95+
function getSortedChangelogVersions( required struct changelog, required string majorVersionFilter ) localmode=true {
96+
var versions = structKeyArray( arguments.changelog );
97+
98+
// Filter to only include versions that start with the major version filter
99+
var filtered = [];
100+
loop array=versions index="local.i" item="local.ver" {
101+
if ( left( ver, len( arguments.majorVersionFilter ) ) eq arguments.majorVersionFilter ) {
102+
arrayAppend( filtered, ver );
103+
}
104+
}
105+
106+
// Sort using proper version sorting (convert to sortable format first)
107+
// Use callback to compare using toVersionSortable format
108+
arraySort( filtered, function( v1, v2 ) {
109+
try {
110+
var sorted1 = toVersionSortable( arguments.v1 );
111+
var sorted2 = toVersionSortable( arguments.v2 );
112+
// Descending order (newest first)
113+
return compare( sorted2, sorted1 );
114+
} catch ( any e ) {
115+
// Fallback to text comparison if version parsing fails
116+
return compareNoCase( arguments.v2, arguments.v1 );
117+
}
118+
});
119+
120+
return filtered;
121+
}
122+
123+
/**
124+
* Build the changelog data array for a specific major version
125+
* @versions The processed versions struct (from processVersions)
126+
* @arrVersions Array of sorted version keys (from getSortedVersions)
127+
* @majorVersionFilter Major version to filter by (e.g., "7.0")
128+
* @returns Array of structs with version metadata and changelog data
129+
*/
130+
function buildChangelogData( required struct versions, required array arrVersions, required string majorVersionFilter ) localmode=true {
131+
var arrChangeLogs = [];
132+
133+
loop array=arguments.arrVersions index="local.idx" item="local._version" {
134+
var version = arguments.versions[ _version ].version;
135+
var prevVersion = "";
136+
137+
// Determine previous version for changelog range
138+
if ( idx lt arrayLen( arguments.arrVersions ) ) {
139+
prevVersion = arguments.versions[ arguments.arrVersions[ idx + 1 ] ].version;
140+
} else {
141+
// Last version - use the oldest version from the sorted array (last item)
142+
var lastKey = arguments.arrVersions[ arrayLen( arguments.arrVersions ) ];
143+
prevVersion = arguments.versions[ lastKey ].version;
144+
}
145+
146+
// Determine header type and title
147+
var versionTitle = version;
148+
var header = "h4";
149+
switch( arguments.versions[ _version ].type ) {
150+
case "releases":
151+
header = "h2";
152+
versionTitle &= " Stable";
153+
break;
154+
}
155+
156+
// Fetch changelog only if version matches the major version filter
157+
var changelog = {};
158+
var versionReleaseDate = "";
159+
if ( left( version, len( arguments.majorVersionFilter ) ) eq arguments.majorVersionFilter ) {
160+
changelog = variables.download.getChangelog( prevVersion, version, false, true );
161+
versionReleaseDate = variables.download.getReleaseDate( version );
162+
}
163+
164+
if ( !isStruct( changelog ) ) {
165+
changelog = {};
166+
}
167+
168+
arrayAppend( arrChangeLogs, {
169+
version: version,
170+
_version: _version,
171+
type: arguments.versions[ _version ].type,
172+
prevVersion: prevVersion,
173+
versionReleaseDate: versionReleaseDate,
174+
changelog: changelog,
175+
header: header,
176+
versionTitle: versionTitle
177+
});
178+
}
179+
180+
return arrChangeLogs;
181+
}
182+
183+
/**
184+
* Convert a version string to sortable format (from VersionUtils.cfc logic)
185+
* @version Version string like "7.0.1.44-SNAPSHOT"
186+
* @returns Sortable version string like "07.000.001.0044.000"
187+
*/
188+
private function toVersionSortable( required string version ) localmode=true {
189+
var arr = listToArray( arguments.version, '.' );
190+
191+
if ( arr.len() != 4 || !isNumeric( arr[ 1 ] ) || !isNumeric( arr[ 2 ] ) || !isNumeric( arr[ 3 ] ) ) {
192+
throw "version number [" & arguments.version & "] is invalid";
193+
}
194+
195+
var sct = {
196+
major: arr[ 1 ] + 0,
197+
minor: arr[ 2 ] + 0,
198+
micro: arr[ 3 ] + 0,
199+
qualifier_appendix: "",
200+
qualifier_appendix_nbr: 100
201+
};
202+
203+
// qualifier has an appendix? (BETA,SNAPSHOT)
204+
var qArr = listToArray( arr[ 4 ], '-' );
205+
if ( qArr.len() == 1 && isNumeric( qArr[ 1 ] ) ) {
206+
sct.qualifier = qArr[ 1 ] + 0;
207+
} else if ( qArr.len() == 2 && isNumeric( qArr[ 1 ] ) ) {
208+
sct.qualifier = qArr[ 1 ] + 0;
209+
sct.qualifier_appendix = qArr[ 2 ];
210+
if ( sct.qualifier_appendix == "SNAPSHOT" ) {
211+
sct.qualifier_appendix_nbr = 0;
212+
} else if ( sct.qualifier_appendix == "BETA" ) {
213+
sct.qualifier_appendix_nbr = 50;
214+
} else {
215+
sct.qualifier_appendix_nbr = 75; // every other appendix is better than SNAPSHOT
216+
}
217+
} else {
218+
sct.qualifier = qArr[ 1 ] + 0;
219+
sct.qualifier_appendix_nbr = 75;
220+
}
221+
222+
return repeatString( "0", 2 - len( sct.major ) ) & sct.major
223+
& "." & repeatString( "0", 3 - len( sct.minor ) ) & sct.minor
224+
& "." & repeatString( "0", 3 - len( sct.micro ) ) & sct.micro
225+
& "." & repeatString( "0", 4 - len( sct.qualifier ) ) & sct.qualifier
226+
& "." & repeatString( "0", 3 - len( sct.qualifier_appendix_nbr ) ) & sct.qualifier_appendix_nbr;
227+
}
228+
229+
}

apps/download/changelog/index.cfm

Lines changed: 11 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -62,48 +62,15 @@
6262
}
6363
versions=tmp;
6464
65-
major = {};
66-
preRelease = {};
67-
// add types
68-
//releases,snapshots,rc,beta
69-
loop struct=versions index="vs" item="data" {
70-
if(findNoCase("-snapshot",data.version)) data['type']="snapshots";
71-
else if(findNoCase("-rc",data.version)) data['type']="rc";
72-
else if(findNoCase("-beta",data.version)) data['type']="beta";
73-
else if(findNoCase("-alpha",data.version)) data['type']="alpha";
74-
else data['type']="releases";
75-
data['versionNoAppendix']=data.version;
76-
if ( data.type != "snapshots" ) {
77-
major[ vs ] = data;
78-
} else {
79-
// need to find latest pre release builds
80-
if ( structKeyExists( data, "versionSorted" ) ){
81-
v = ArrayToList( ArraySlice( listToArray( data.versionSorted,"." ), 1 , 2 ), "." );
82-
preRelease[ v ] = {
83-
versionSorted: data.versionSorted,
84-
versionNoAppendix: data.versionNoAppendix
85-
};
86-
}
87-
}
88-
}
89-
// avoid showingh a snapshot for a release etc
90-
structEach( preRelease, function( k, v ) {
91-
var releaseVersionSorted = left( v.versionSorted, len( v.versionSorted ) -4 );
92-
// check for RC / BETA / SNAPSHOT with the same version
93-
arrayEach( [ ".050",".100",".075" ], function( i ){
94-
if ( structKeyExists( major, releaseVersionSorted & arguments.i ) )
95-
structDelete( preRelease, k );
96-
});
97-
});
98-
structEach( preRelease, function( k, v ) {
99-
major[ v.versionSorted ] = {
100-
version: v.versionNoAppendix,
101-
type: "snapshot"
102-
};
103-
});
65+
// Use changelog.cfc to process versions
66+
changelogService = CreateObject( "component", "changelog" ).init( download );
67+
processedVersions = changelogService.processVersions( versions );
68+
major = processedVersions.major;
10469
105-
arrVersions = structKeyArray(major).reverse().sort("text","desc");
106-
arrChangeLogs = [];
70+
arrVersions = changelogService.getSortedVersions( major );
71+
72+
// Build changelog data array using the new method
73+
arrChangeLogs = changelogService.buildChangelogData( major, arrVersions, url.version );
10774
10875
function getBadgeForType( type ) {
10976
switch(arguments.type){
@@ -132,66 +99,6 @@
13299
<cfinclude template="../_linkbar.cfm">
133100
<h2 class="display-3">Lucee Server Changelogs - #url.version#</h2>
134101
</div>
135-
<cfsilent>
136-
<cfloop array="#arrVersions#" item="_version" index="idx">
137-
138-
<cfscript>
139-
version = versions[ _version ].version;
140-
if (idx lt ArrayLen(arrVersions)){
141-
prevVersion = versions[arrVersions[ idx + 1 ]].version;
142-
} else {
143-
prevVersion = structKeyArray(versions);
144-
prevVersion = versions[prevVersion[arrayLen(prevVersion)]].version;
145-
}
146-
versionTitle = version;
147-
switch(versions[_version].type){
148-
case "releases":
149-
header="h2";
150-
versionTitle &= " Stable";
151-
break;
152-
default:
153-
header="h4";
154-
}
155-
changelog = {};
156-
versionReleaseDate = "";
157-
if ( left( version, 3 ) eq url.version ){
158-
changeLog = download.getChangelog( prevVersion, version, false, true );
159-
versionReleaseDate = download.getReleaseDate(version);
160-
}
161-
if (!isStruct(changelog))
162-
changelog = {};
163-
164-
arrayAppend(arrChangeLogs, {
165-
version: version,
166-
_version: _version,
167-
type: versions[_version].type,
168-
prevVersion: prevVersion,
169-
versionReleaseDate: versionReleaseDate,
170-
changelog: changelog,
171-
header: header,
172-
versionTitle: versionTitle
173-
});
174-
/*
175-
structEach(changeLog, function(cl){
176-
structEach(changeLog[cl], function( ticket ){
177-
var _type= changeLog[ cl ][ ticket ].type;
178-
if ( !structKeyExists( ticketTypes, _type ) )
179-
ticketTypes[ _type ]=0;
180-
ticketTypes[ _type ]++;
181-
182-
var _labels = changeLog [cl ][ ticket ].labels;
183-
arrayEach(_labels, function(_label) {
184-
if (!structKeyExists( ticketLabels, _label ) )
185-
ticketLabels[_label]=0;
186-
ticketLabels[_label]++;
187-
});
188-
});
189-
});
190-
*/
191-
192-
</cfscript>
193-
</cfloop>
194-
</cfsilent>
195102
<cfset lastMajor = "">
196103
<div class="versionList">
197104
<cfloop array="#arrChangeLogs#" item="luceeVersion">
@@ -288,7 +195,9 @@
288195
</td>
289196
</tr>
290197
<cfset changelogTicketList = {}>
291-
<cfloop struct="#lv.changelog#" index="ver" item="tickets">
198+
<cfset sortedVersionKeys = changelogService.getSortedChangelogVersions( lv.changelog, url.version )>
199+
<cfloop array="#sortedVersionKeys#" index="ver">
200+
<cfset tickets = lv.changelog[ ver ]>
292201
<cfloop struct="#tickets#" index="id" item="ticket">
293202
<cfif !StructKeyExists(changelogTicketList, ticket.id)>
294203
<tr valign="top"

0 commit comments

Comments
 (0)