Skip to content

Commit dfd17f1

Browse files
committed
updates for IG withdrawal, renaming, and replacement
1 parent 14c92a5 commit dfd17f1

9 files changed

Lines changed: 366 additions & 160 deletions

File tree

org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/PublicationChecker.java

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,16 @@ public String check() throws IOException {
6868
if (summary.size() > 0) {
6969
bs.append("<table class=\"grid\">\r\n");
7070
for (StringPair p : summary) {
71-
if ("descmd".equals(p.getName())) {
72-
bs.append(" <tr><td>"+Utilities.escapeXml(p.getName())+"</d><td>"+p.getValue()+"</td></tr>\r\n");
71+
String name = p.getName();
72+
String style = "";
73+
if (name.startsWith("!")) {
74+
name = name.substring(1);
75+
style = " style=\"background-color: #ffe6cc;\"";
76+
}
77+
if ("descmd".equals(name)) {
78+
bs.append(" <tr"+style+"><td>"+Utilities.escapeXml(name)+"</d><td>"+p.getValue()+"</td></tr>\r\n");
7379
} else {
74-
bs.append(" <tr><td>"+Utilities.escapeXml(p.getName())+"</d><td>"+Utilities.escapeXml(p.getValue())+"</td></tr>\r\n");
80+
bs.append(" <tr"+style+"><td>"+Utilities.escapeXml(name)+"</d><td>"+Utilities.escapeXml(p.getValue())+"</td></tr>\r\n");
7581
}
7682
}
7783
bs.append("</table>\r\n");
@@ -162,9 +168,14 @@ private JsonObject checkFolder(List<String> messages, List<StringPair> summary)
162168
} catch (Exception e) {
163169
check(messages, false, "Error reading package: "+e.getMessage()+mkError());
164170
}
171+
JsonObject pr = null;
172+
File prFile = new File(Utilities.path(folder, "publication-request.json"));
173+
if (prFile.exists()) {
174+
pr = JsonParser.parseObject(prFile);
175+
}
165176
if (npm != null) {
166-
checkPackage(messages, npm);
167-
String dst = determineDestination(npm);
177+
checkPackage(messages, npm);
178+
String dst = pr != null && pr.has("movedFrom") ? pr.asString("movedFrom").replace("https:", "http:") : determineDestination(npm);
168179
PackageList pl = null;
169180
try {
170181
pl = readPackageList(dst);
@@ -175,7 +186,7 @@ private JsonObject checkFolder(List<String> messages, List<StringPair> summary)
175186
check(messages, false, "Error fetching package-list from "+dst+": "+e.getMessage()+mkError());
176187
}
177188
}
178-
checkExistingPublication(messages, npm, pl);
189+
checkExistingPublication(messages, npm, pl, pr);
179190
if (check(messages, exists("publication-request.json"), "No publication request found"+mkInfo())) {
180191
return checkPublicationRequest(messages, npm, pl, summary);
181192
}
@@ -209,11 +220,19 @@ private void checkPackage(List<String> messages, NpmPackage npm) {
209220
Utilities.escapeXml(Utilities.pathURL(npm.canonical(), "history.html"))+"'"+mkError());
210221
}
211222

212-
private void checkExistingPublication(List<String> messages, NpmPackage npm, PackageList pl) {
223+
private void checkExistingPublication(List<String> messages, NpmPackage npm, PackageList pl, JsonObject pr) {
213224
if (pl != null) {
214-
check(messages, npm.name().equals(pl.pid()), "Package ID mismatch. This package is "+npm.name()+" but the website has "+pl.pid()+mkError());
215-
check(messages, npm.canonical().equals(pl.canonical()), "Package canonical mismatch. This package canonical is "+npm.canonical()+" but the website has "+pl.canonical()+mkError());
216-
check(messages, !hasVersion(pl, npm.version()), "Version "+npm.version()+" has already been published"+mkWarning());
225+
if (pr.has("movedFrom")) {
226+
check(messages, !npm.name().equals(pl.pid()), "Package ID matches, which is wrong. This package is " + npm.name() + " and the website is also " + pl.pid()+", but it must change" + mkError());
227+
check(messages, !npm.canonical().equals(pl.canonical()), "Package canonical matches, which is wrong. This package canonical is " + npm.canonical() + " and the website has " + pl.canonical()+", but it must change" + mkError());
228+
check(messages, !hasVersion(pl, npm.version()), "Version " + npm.version() + " has already been published" + mkWarning());
229+
check(messages, pl.getNowPublishedAs() == null, "Package " + npm.name() + " has been superceded by " + pl.getNowPublishedAs() + " and no further publications are possible" + mkError());
230+
} else {
231+
check(messages, npm.name().equals(pl.pid()), "Package ID mismatch. This package is " + npm.name() + " but the website has " + pl.pid() + mkError());
232+
check(messages, npm.canonical().equals(pl.canonical()), "Package canonical mismatch. This package canonical is " + npm.canonical() + " but the website has " + pl.canonical() + mkError());
233+
check(messages, !hasVersion(pl, npm.version()), "Version " + npm.version() + " has already been published" + mkWarning());
234+
check(messages, pl.getNowPublishedAs() == null, "Package " + npm.name() + " has been superceded by " + pl.getNowPublishedAs() + " and no further publications are possible" + mkError());
235+
}
217236
} else {
218237
check(messages, npm.version().startsWith("0.1") || npm.version().contains("-"), "This IG has never been published, so the version should start with '0.' or include a patch version e.g. '-ballot'"+mkWarning());
219238
}
@@ -254,10 +273,19 @@ private JsonObject checkPublicationRequest(List<String> messages, NpmPackage npm
254273
check(messages, cv == null || VersionUtilities.isThisOrLater(cv, v, VersionUtilities.VersionPrecision.MINOR), "Proposed version v"+v+" is older than already published version v"+cv+mkError());
255274
}
256275
}
257-
if (check(messages, pr.has("path"), "No publication request path found"+mkError())) {
258-
if (check(messages, pr.asString("path").startsWith(npm.canonical()) && !pr.asString("path").equals(npm.canonical()), "Proposed path for this publication does not start with the canonical URL ("+pr.asString("path")+" vs "+npm.canonical() +")"+mkError())) {
259-
summary.add(new StringPair("path", pr.asString("path")));
276+
boolean pathok = false;
277+
if ("withdrawal".equals(pr.asString("mode"))) {
278+
check(messages, !pr.has("path"), "No publication request path allowed for withdrawals"+mkError());
279+
pathok = true;
280+
} else {
281+
if (check(messages, pr.has("path"), "No publication request path found" + mkError())) {
282+
if (check(messages, pr.asString("path").startsWith(npm.canonical()) && !pr.asString("path").equals(npm.canonical()), "Proposed path for this publication does not start with the canonical URL (" + pr.asString("path") + " vs " + npm.canonical() + ")" + mkError())) {
283+
pathok = true;
284+
summary.add(new StringPair("path", pr.asString("path")));
285+
}
260286
}
287+
}
288+
if (pathok) {
261289
boolean exists = false;
262290
if (pl != null) {
263291
for (PackageListEntry v : pl.versions()) {
@@ -282,26 +310,26 @@ private JsonObject checkPublicationRequest(List<String> messages, NpmPackage npm
282310
}
283311
}
284312
} else if (pr.has("version")) {
285-
check(messages,
286-
pr.asString("path").equals(Utilities.pathURL(npm.canonical(), pr.asString("version"))) ||
313+
check(messages,
314+
pr.asString("path") == null || pr.asString("path").equals(Utilities.pathURL(npm.canonical(), pr.asString("version"))) ||
287315
pr.asString("path").startsWith(Utilities.pathURL(npm.canonical(), pr.asString("version"))+"-") ||
288316
pr.asString("path").startsWith(Utilities.pathURL(npm.canonical(), pr.asString("sequence"))+"-"),
289317
"Proposed path for this publication should usually be the canonical with the version or sequence appended and then some kind of label (typically '-snapshot')"+mkWarning());
290318
}
291319
}
292320
PublicationProcessMode mode = null;
293-
if (check(messages, Utilities.existsInList(pr.asString("mode"), "working", "milestone", "technical-correction"), "The release must have a mode of working, milestone or technical-correction"+mkError())) {
321+
if (check(messages, Utilities.existsInList(pr.asString("mode"), "working", "milestone", "technical-correction", "withdrawal"), "The release must have a mode of working, milestone, technical-correction or withdrawal"+mkError())) {
294322
mode = PublicationProcessMode.fromCode(pr.asString("mode"));
295-
summary.add(new StringPair("Pub-Mode", mode.toCode()));
296-
if (mode != PublicationProcessMode.WORKING) {
323+
summary.add(new StringPair((mode == PublicationProcessMode.WITHDRAWAL ? "!": "")+"Pub-Mode", mode.toCode()));
324+
if (mode != PublicationProcessMode.WORKING && mode != PublicationProcessMode.WITHDRAWAL) {
297325
check(messages, !npm.version().contains("-"), "This release is labelled as a "+mode.toCode()+", so should not have a patch version ("+npm.version() +")"+mkWarning());
298326
} else {
299327
check(messages, npm.version().contains("-"), "This release is not labelled as a milestone or technical correction, so should have a patch version ("+npm.version() +")"+(isHL7(npm) ? mkError() : mkWarning()));
300328
}
301329
}
302330

303331
if (check(messages, pr.has("status"), "No publication request status found"+mkError())) {
304-
if (check(messages, isValidStatus(pr.asString("status")), "Proposed status for this publication is not valid (valid values: release|trial-use|update|preview|ballot|draft|normative+trial-use|normative|informative)"+mkError())) {
332+
if (check(messages, isValidStatus(pr.asString("status")), "Proposed status for this publication is not valid (valid values: release|trial-use|update|preview|ballot|draft|normative+trial-use|normative|informative|withdrawn)"+mkError())) {
305333
summary.add(new StringPair("status", pr.asString("status")));
306334
}
307335
}
@@ -319,7 +347,7 @@ private JsonObject checkPublicationRequest(List<String> messages, NpmPackage npm
319347
PackageListEntry lv = getLastVersionForSequence(pl, seq);
320348
check(messages, mode != PublicationProcessMode.WORKING || lv.current() || Utilities.existsInList(lv.status(), "ballot", "draft"),
321349
"This release is labelled as a working release in the sequence '"+seq+"'. This is an unexpected workflow - check that the sequence really is correct."+mkWarning());
322-
} else if (check(messages, mode != PublicationProcessMode.TECHNICAL_CORRECTION, "Technical Corrections must happen in the scope of the current sequence ('"+seq+"', not '"+pr.asString("sequence")+"'."+mkWarning())) {
350+
} else if (check(messages, mode != PublicationProcessMode.TECHNICAL_CORRECTION && mode != PublicationProcessMode.WITHDRAWAL, "Technical Corrections and withdrawals must happen in the scope of the current sequence ('"+seq+"', not '"+pr.asString("sequence")+"'."+mkWarning())) {
323351
PackageListEntry ls = getLastVersionForSequence(pl, pr.asString("sequence"));
324352
check(messages, ls == null || !ls.current(), "The sequence '"+seq+"' has already been closed with a current publication, and a new sequence '"+seq+"' started - is going back to '"+pr.asString("sequence")+"' really what's intended?"+mkWarning());
325353
}
@@ -337,8 +365,13 @@ private JsonObject checkPublicationRequest(List<String> messages, NpmPackage npm
337365
}
338366
}
339367

368+
if (mode == PublicationProcessMode.WITHDRAWAL) {
369+
check(messages, pr.has("registry-reason"), "For withdrawals, a short succint reason for withdrawal must be provided in 'registry-reason'"+mkError());
370+
}
340371
if (check(messages, pr.has("desc") || pr.has("descmd") , "No publication request description found"+mkError())) {
341-
check(messages, pr.has("desc"), "No publication request desc found (it is recommended to provide a shorter desc as well as descmd"+mkWarning());
372+
check(messages, pr.has("desc"), "No publication request desc found (it is recommended to provide a shorter desc as well as descmd)"+mkWarning());
373+
check(messages, mode != PublicationProcessMode.WITHDRAWAL || pr.has("descmd"), "No publication request descmd found (needed for a withdrawal)"+mkWarning());
374+
342375
if (pr.has("desc")) {
343376
summary.add(new StringPair("desc", pr.asString("desc")));
344377
}
@@ -369,8 +402,11 @@ private JsonObject checkPublicationRequest(List<String> messages, NpmPackage npm
369402
summary.add(new StringPair("category", pr.asString("category")));
370403
}
371404
if (check(messages, pr.has("title"), "No publication request title found (needed for first publication)"+mkError())) {
372-
summary.add(new StringPair("title", pr.asString("title")));
405+
summary.add(new StringPair("title", pr.asString("title")));
373406
}
407+
check(messages, pr.has("registry-description"), "No publication description (`registry-description`) found (needed for first publication)"+mkError());
408+
check(messages, pr.has("registry-country"), "No publication country (`registry-country`) found (needed for first publication)"+mkError());
409+
check(messages, pr.has("registry-authority"), "No publication authority (`registry-authority`) found (needed for first publication)"+mkError());
374410
if (check(messages, pr.has("introduction"), "No publication request introduction found (needed for first publication)"+mkError())) {
375411
summary.add(new StringPair("introduction", pr.asString("introduction")));
376412
}
@@ -385,6 +421,28 @@ private JsonObject checkPublicationRequest(List<String> messages, NpmPackage npm
385421
}
386422
check(messages, !pr.has("date"), "Cannot specify a date of publication in the request"+mkError());
387423
check(messages, !pr.has("canonical"), "Cannot specify a canonical in the request"+mkError());
424+
425+
if (pr.has("previouslyPublishedAs")) {
426+
try {
427+
PackageList plOld = readPackageList(pr.asString("previouslyPublishedAs"));
428+
check(messages, pl == null, "Has Already been published, so can't be replacing another publication"+mkError());
429+
check(messages, !plOld.pid().equals(npm.id()), "Previously published package ID mismatch - has not changed: "+plOld.pid()+" vs "+npm.id()+mkError());
430+
check(messages, !plOld.canonical().equals(npm.canonical()), "Previously published package canonical has not changed: "+plOld.canonical()+" vs "+npm.canonical()+mkError());
431+
check(messages, plOld.getNowPublishedAs() == null, "Package "+npm.name()+" has already been been superceded by "+plOld.getNowPublishedAs()+" and can't be superceded again"+mkError());
432+
check(messages, !pr.has("previouslyPublishedAs"), "Cannot have both previouslyPublishedAs and movedFrom"+mkError());
433+
summary.add(new StringPair("Replaces", plOld.title()+" ("+plOld.pid()+")"));
434+
} catch (IOException e) {
435+
check(messages, false, "!Previously published package not found at "+pr.asString("previouslyPublishedAs")+mkError());
436+
}
437+
}
438+
if (pr.has("movedFrom")) {
439+
summary.add(new StringPair("!Renames", pl.title()+" ("+pl.pid()+" / "+pl.canonical()+")"));
440+
try {
441+
PackageList plOld = readPackageList(determineDestination(npm));
442+
check(messages, false, "A package-list.json already exists at "+pr.asString("movedFrom").replace("https:", "http:")+mkError());
443+
} catch (IOException e) {
444+
}
445+
}
388446
return pr;
389447
}
390448

@@ -433,7 +491,7 @@ private String mkInfo() {
433491
}
434492

435493
private boolean isValidStatus(String str) {
436-
return Utilities.existsInList(str, "release", "trial-use", "update", "preview", "ballot", "draft", "normative+trial-use", "normative", "informative", "public-comment");
494+
return Utilities.existsInList(str, "release", "trial-use", "update", "preview", "ballot", "draft", "normative+trial-use", "normative", "informative", "public-comment", "withdrawn");
437495
}
438496

439497
private String getCurrentSequence(PackageList pl) {

org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/ValueSetRenderer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState;
3838
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
3939
import org.hl7.fhir.r5.context.IWorkerContext;
40+
import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
4041
import org.hl7.fhir.r5.model.CanonicalResource;
4142
import org.hl7.fhir.r5.model.DataRequirement;
4243
import org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent;
@@ -96,6 +97,9 @@ public String cld(Set<String> outputTracker) throws EOperationOutcome, FHIRExcep
9697
vsc.setExpansion(null); // we don't want to render an expansion by mistake
9798
RendererFactory.factory(vsc, gen).renderOrError(ResourceWrapper.forResource(gen, vsc));
9899
return "<h3>"+gen.formatPhrase(RenderingContext.VSR_LOGICAL)+"</h3>\r\n" + new XhtmlComposer(XhtmlComposer.HTML).compose(vsc.getText().getDiv());
100+
} else if (vs.hasExtension(ExtensionDefinitions.EXT_VALUESET_RULES_TEXT)) {
101+
String md = processMarkdown("ValueSet.extension", vs.getExtensionString(ExtensionDefinitions.EXT_VALUESET_RULES_TEXT));
102+
return "<h3>"+gen.formatPhrase(RenderingContext.VSR_LOGICAL)+"</h3>\r\n" +md;
99103
} else {
100104
return "<h3>"+gen.formatPhrase(RenderingContext.VSR_LOGICAL)+"</h3>\r\n<p>"+gen.formatPhrase(RenderingI18nContext.VSR_NO_DEF)+"</p>\r\n";
101105

0 commit comments

Comments
 (0)