From da6c9c03b86ad85fda6388f0a65fafbc22e599d2 Mon Sep 17 00:00:00 2001 From: Sergey Grigoriev Date: Tue, 10 Dec 2024 08:24:17 +0100 Subject: [PATCH] feat: Baseline support (#310) Refs: #305 --- docs/openapi.json | 12 + .../PdfExporterFormExtension.java | 12 +- .../exception/BaselineExecutionException.java | 7 + .../controller/CollectionApiController.java | 4 +- .../CollectionInternalController.java | 10 +- .../rest/model/conversion/ExportParams.java | 3 + .../service/PdfExporterPolarionService.java | 30 ++- .../pdf_exporter/util/DocumentDataHelper.java | 255 +++++++++--------- .../webapp/pdf-exporter/js/export-pdf.js | 15 +- .../pdf-exporter/js/modules/ExportContext.js | 12 +- .../pdf-exporter/js/modules/ExportParams.js | 7 + .../webapp/pdf-exporter/js/pdf-exporter.js | 1 + .../PdfExporterPolarionServiceTest.java | 63 ++++- src/test/js/ExportContextTest.js | 18 +- 14 files changed, 296 insertions(+), 153 deletions(-) create mode 100644 src/main/java/ch/sbb/polarion/extension/pdf_exporter/exception/BaselineExecutionException.java diff --git a/docs/openapi.json b/docs/openapi.json index d08ad4e..c6829f6 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -618,6 +618,14 @@ "schema": { "type": "string" } + }, + { + "description": "The specific revision of the provided collection", + "in": "query", + "name": "revision", + "schema": { + "type": "string" + } } ], "responses": { @@ -1672,6 +1680,10 @@ "description": "Filter for attachments to be downloaded, example: '*.pdf'", "type": "string" }, + "baselineRevision": { + "description": "The specific revision of the provided baseline", + "type": "string" + }, "chapters": { "description": "Specific higher level chapters", "items": { diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/PdfExporterFormExtension.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/PdfExporterFormExtension.java index c129e8f..460c757 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/PdfExporterFormExtension.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/PdfExporterFormExtension.java @@ -55,8 +55,10 @@ public class PdfExporterFormExtension implements IFormExtension { @Override @Nullable public String render(@NotNull IFormExtensionContext context) { - return TransactionalExecutor.executeSafelyInReadOnlyTransaction( - transaction -> renderForm(transaction.context(), context.object().getOldApi())); + return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> { + String baselineRevision = transaction.context().baselineRevision(); + return polarionService.executeInBaseline(baselineRevision, transaction, () -> renderForm(transaction.context(), context.object().getOldApi())); + }); } public String renderForm(@NotNull SharedContext context, @NotNull IPObject object) { @@ -101,7 +103,7 @@ public String renderForm(@NotNull SharedContext context, @NotNull IPObject objec form = adjustLocalizeEnums(form, selectedStylePackage, module.getCustomField(DOC_LANGUAGE_FIELD)); form = adjustLinkRoles(form, EnumValuesProvider.getAllLinkRoleNames(module.getProject()), selectedStylePackage); form = adjustFilename(form, module); - form = adjustButtons(form, module, selectedStylePackage); + form = adjustButtons(form, module, selectedStylePackage, context.baselineRevision()); builder.html(form); } @@ -302,10 +304,10 @@ private String adjustFilename(@NotNull String form, @NotNull IModule module) { return form.replace("{FILENAME}", filename).replace("{DATA_FILENAME}", filename); } - private String adjustButtons(@NotNull String form, @NotNull IModule module, @NotNull StylePackageModel stylePackage) { + private String adjustButtons(@NotNull String form, @NotNull IModule module, @NotNull StylePackageModel stylePackage, @Nullable String baselineRevision) { IProject project = module.getProject(); String moduleLocationPath = module.getModuleLocation().getLocationPath(); - String params = fillParams(project.getId(), moduleLocationPath, module.getRevision()); + String params = fillParams(project.getId(), moduleLocationPath, baselineRevision, module.getRevision()); form = form.replace("{LOAD_PDF_PARAMS}", params); form = form.replace("{VALIDATE_PDF_PARAMS}", params); diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/exception/BaselineExecutionException.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/exception/BaselineExecutionException.java new file mode 100644 index 0000000..a9b60c0 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/exception/BaselineExecutionException.java @@ -0,0 +1,7 @@ +package ch.sbb.polarion.extension.pdf_exporter.exception; + +public class BaselineExecutionException extends RuntimeException { + public BaselineExecutionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionApiController.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionApiController.java index 613b6f5..b1b43e1 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionApiController.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionApiController.java @@ -13,7 +13,7 @@ public class CollectionApiController extends CollectionInternalController { private static final PolarionService polarionService = new PolarionService(); @Override - public List getDocumentsFromCollection(String projectId, String collectionId) { - return polarionService.callPrivileged(() -> super.getDocumentsFromCollection(projectId, collectionId)); + public List getDocumentsFromCollection(String projectId, String collectionId, String revision) { + return polarionService.callPrivileged(() -> super.getDocumentsFromCollection(projectId, collectionId, revision)); } } diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionInternalController.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionInternalController.java index 151101f..3a06a18 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionInternalController.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/controller/CollectionInternalController.java @@ -16,6 +16,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.util.List; @@ -40,8 +41,11 @@ public CollectionInternalController() { ) } ) - public List getDocumentsFromCollection(@Parameter(description = "Project ID", required = true) @PathParam("projectId") String projectId, - @Parameter(description = "Collection ID", required = true) @PathParam("collectionId") String collectionId) { - return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> pdfExporterPolarionService.getDocumentsFromCollection(projectId, collectionId, transaction)); + public List getDocumentsFromCollection( + @Parameter(description = "Project ID", required = true) @PathParam("projectId") String projectId, + @Parameter(description = "Collection ID", required = true) @PathParam("collectionId") String collectionId, + @Parameter(description = "The specific revision of the provided collection") @QueryParam("revision") String revision + ) { + return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> pdfExporterPolarionService.getDocumentsFromCollection(projectId, collectionId, revision, transaction)); } } diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/model/conversion/ExportParams.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/model/conversion/ExportParams.java index e025e58..69e8a3f 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/model/conversion/ExportParams.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/rest/model/conversion/ExportParams.java @@ -22,6 +22,9 @@ public class ExportParams { @Schema(description = "Document path for export", example = "Specification/Product Specification") private @Nullable String locationPath; + @Schema(description = "The specific revision of the provided baseline") + private @Nullable String baselineRevision; + @Schema(description = "The specific revision of the document") private @Nullable String revision; diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionService.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionService.java index 0eefee8..894de61 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionService.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionService.java @@ -5,6 +5,7 @@ import ch.sbb.polarion.extension.generic.settings.SettingId; import ch.sbb.polarion.extension.generic.settings.SettingName; import ch.sbb.polarion.extension.generic.util.ScopeUtils; +import ch.sbb.polarion.extension.pdf_exporter.exception.BaselineExecutionException; import ch.sbb.polarion.extension.pdf_exporter.rest.model.attachments.TestRunAttachment; import ch.sbb.polarion.extension.pdf_exporter.rest.model.collections.DocumentCollectionEntry; import ch.sbb.polarion.extension.pdf_exporter.rest.model.settings.stylepackage.StylePackageModel; @@ -12,6 +13,7 @@ import ch.sbb.polarion.extension.pdf_exporter.settings.StylePackageSettings; import ch.sbb.polarion.extension.pdf_exporter.util.WildcardUtils; import com.polarion.alm.projects.IProjectService; +import com.polarion.alm.shared.api.model.baselinecollection.BaselineCollection; import com.polarion.alm.shared.api.model.baselinecollection.BaselineCollectionReference; import com.polarion.alm.shared.api.transaction.ReadOnlyTransaction; import com.polarion.alm.tracker.ITestManagementService; @@ -38,6 +40,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Callable; public class PdfExporterPolarionService extends PolarionService { @@ -171,10 +174,16 @@ private boolean sameDocument(@Nullable String projectId, @NotNull String spaceId return testRunAttachment; } - public @NotNull List getDocumentsFromCollection(@NotNull String projectId, @NotNull String collectionId, @NotNull ReadOnlyTransaction transaction) { + public @NotNull List getDocumentsFromCollection(@NotNull String projectId, @NotNull String collectionId, @Nullable String revision, @NotNull ReadOnlyTransaction transaction) { List result = new ArrayList<>(); - IBaselineCollection collection = new BaselineCollectionReference(projectId, collectionId).get(transaction).getOldApi(); + BaselineCollectionReference baselineCollectionReference = new BaselineCollectionReference(projectId, collectionId); + if (revision != null) { + baselineCollectionReference = baselineCollectionReference.getWithRevision(revision); + } + + BaselineCollection baselineCollection = baselineCollectionReference.get(transaction); + IBaselineCollection collection = baselineCollection.getOldApi(); List modules = collection.getElements().stream() .map(IBaselineCollectionElement::getObjectWithRevision) @@ -196,4 +205,21 @@ private boolean sameDocument(@Nullable String projectId, @NotNull String spaceId return result; } + + public T executeInBaseline(@Nullable String baselineRevision, @NotNull ReadOnlyTransaction transaction, @NotNull Callable callable) { + if (baselineRevision == null) { + return callCallable(callable); + } else { + return transaction.utils().executeInBaseline(baselineRevision, () -> callCallable(callable)); + } + } + + private static T callCallable(@NotNull Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + throw new BaselineExecutionException("Error during callable execution", e); + } + } + } diff --git a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/util/DocumentDataHelper.java b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/util/DocumentDataHelper.java index eb712bf..9d04e5d 100644 --- a/src/main/java/ch/sbb/polarion/extension/pdf_exporter/util/DocumentDataHelper.java +++ b/src/main/java/ch/sbb/polarion/extension/pdf_exporter/util/DocumentDataHelper.java @@ -40,6 +40,7 @@ import org.jetbrains.annotations.VisibleForTesting; import java.util.Map; +import java.util.Objects; @SuppressWarnings("java:S1200") public class DocumentDataHelper { @@ -58,39 +59,40 @@ public DocumentData getLiveReport(@Nullable ITrackerProject project, } public DocumentData getLiveReport(@Nullable ITrackerProject project, @NotNull ExportParams exportParams, boolean withContent) { - return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> { - - String projectId = project != null ? project.getId() : ""; - String locationPath = exportParams.getLocationPath(); - if (locationPath == null) { - throw new IllegalArgumentException("Location path is required for export"); - } - - RichPageReference richPageReference = RichPageReference.fromPath(createPath(projectId, locationPath)); - if (exportParams.getRevision() != null) { - richPageReference = richPageReference.getWithRevision(exportParams.getRevision()); - } - - RichPage richPage = richPageReference.getOriginal(transaction); - - String documentContent = null; - if (withContent) { - String html = RpeModelAspect.getPageHtml(richPage); - Map liveReportParameters = exportParams.getUrlQueryParameters() == null ? Map.of() : exportParams.getUrlQueryParameters(); - StrictMap urlParameters = new StrictMapImpl<>(liveReportParameters); - RpeRenderer richPageRenderer = new RpeRenderer((InternalReadOnlyTransaction) transaction, html, RichTextRenderTarget.PDF_EXPORT, richPageReference, richPageReference.scope(), urlParameters); - documentContent = richPageRenderer.render(null); - } - - return DocumentData.builder(DocumentType.LIVE_REPORT, richPage.getOldApi()) - .projectName(project != null ? project.getName() : "") - .lastRevision(richPage.getOldApi().getLastRevision()) - .baselineName(project != null ? getRevisionBaseline(projectId, richPage.getOldApi(), exportParams.getRevision()) : "") - .id(richPage.getOldApi().getId()) - .title(richPage.getOldApi().getTitleOrName()) - .content(documentContent) - .build(); - }); + return TransactionalExecutor.executeSafelyInReadOnlyTransaction( + transaction -> pdfExporterPolarionService.executeInBaseline(exportParams.getBaselineRevision(), transaction, () -> { + + String projectId = project != null ? project.getId() : ""; + String locationPath = exportParams.getLocationPath(); + if (locationPath == null) { + throw new IllegalArgumentException("Location path is required for export"); + } + + RichPageReference richPageReference = RichPageReference.fromPath(createPath(projectId, locationPath)); + if (exportParams.getRevision() != null) { + richPageReference = richPageReference.getWithRevision(exportParams.getRevision()); + } + + RichPage richPage = richPageReference.getOriginal(transaction); + + String documentContent = null; + if (withContent) { + String html = RpeModelAspect.getPageHtml(richPage); + Map liveReportParameters = exportParams.getUrlQueryParameters() == null ? Map.of() : exportParams.getUrlQueryParameters(); + StrictMap urlParameters = new StrictMapImpl<>(liveReportParameters); + RpeRenderer richPageRenderer = new RpeRenderer((InternalReadOnlyTransaction) transaction, html, RichTextRenderTarget.PDF_EXPORT, richPageReference, richPageReference.scope(), urlParameters); + documentContent = richPageRenderer.render(null); + } + + return DocumentData.builder(DocumentType.LIVE_REPORT, richPage.getOldApi()) + .projectName(project != null ? project.getName() : "") + .lastRevision(richPage.getOldApi().getLastRevision()) + .baselineName(project != null ? getRevisionBaseline(projectId, richPage.getOldApi(), exportParams.getRevision()) : "") + .id(richPage.getOldApi().getId()) + .title(richPage.getOldApi().getTitleOrName()) + .content(documentContent) + .build(); + })); } public DocumentData getTestRun(@NotNull ITrackerProject project, @NotNull ExportParams exportParams) { @@ -98,37 +100,38 @@ public DocumentData getTestRun(@NotNull ITrackerProject project, @NotN } public DocumentData getTestRun(@NotNull ITrackerProject project, @NotNull ExportParams exportParams, boolean withContent) { - return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> { - - String projectId = project.getId(); - Map urlQueryParameters = exportParams.getUrlQueryParameters(); - if (urlQueryParameters == null || !urlQueryParameters.containsKey(URL_QUERY_PARAM_ID)) { - throw new IllegalArgumentException("TestRun id is required for export"); - } - - TestRunReference testRunReference = TestRunReference.fromPath(createPath(projectId, urlQueryParameters.get(URL_QUERY_PARAM_ID))); - if (exportParams.getRevision() != null) { - testRunReference = testRunReference.getWithRevision(exportParams.getRevision()); - } - - TestRun testRun = testRunReference.getOriginal(transaction); - - String documentContent = null; - if (withContent) { - String html = RpeModelAspect.getPageHtml(testRun); - RpeRenderer richPageRenderer = new RpeRenderer((InternalReadOnlyTransaction) transaction, html, RichTextRenderTarget.PDF_EXPORT, testRunReference, testRunReference.scope(), new StrictMapImpl<>()); - documentContent = richPageRenderer.render(null); - } - - return DocumentData.builder(DocumentType.TEST_RUN, testRun.getOldApi()) - .projectName(project.getName()) - .lastRevision(testRun.getOldApi().getLastRevision()) - .baselineName(getRevisionBaseline(projectId, testRun.getOldApi(), exportParams.getRevision())) - .id(testRun.getOldApi().getId()) - .title(testRun.getOldApi().getLabel()) - .content(documentContent) - .build(); - }); + return TransactionalExecutor.executeSafelyInReadOnlyTransaction( + transaction -> pdfExporterPolarionService.executeInBaseline(exportParams.getBaselineRevision(), transaction, () -> { + + String projectId = project.getId(); + Map urlQueryParameters = exportParams.getUrlQueryParameters(); + if (urlQueryParameters == null || !urlQueryParameters.containsKey(URL_QUERY_PARAM_ID)) { + throw new IllegalArgumentException("TestRun id is required for export"); + } + + TestRunReference testRunReference = TestRunReference.fromPath(createPath(projectId, urlQueryParameters.get(URL_QUERY_PARAM_ID))); + if (exportParams.getRevision() != null) { + testRunReference = testRunReference.getWithRevision(exportParams.getRevision()); + } + + TestRun testRun = testRunReference.getOriginal(transaction); + + String documentContent = null; + if (withContent) { + String html = RpeModelAspect.getPageHtml(testRun); + RpeRenderer richPageRenderer = new RpeRenderer((InternalReadOnlyTransaction) transaction, html, RichTextRenderTarget.PDF_EXPORT, testRunReference, testRunReference.scope(), new StrictMapImpl<>()); + documentContent = richPageRenderer.render(null); + } + + return DocumentData.builder(DocumentType.TEST_RUN, testRun.getOldApi()) + .projectName(project.getName()) + .lastRevision(testRun.getOldApi().getLastRevision()) + .baselineName(getRevisionBaseline(projectId, testRun.getOldApi(), exportParams.getRevision())) + .id(testRun.getOldApi().getId()) + .title(testRun.getOldApi().getLabel()) + .content(documentContent) + .build(); + })); } public DocumentData getWikiPage(@Nullable ITrackerProject project, @NotNull ExportParams exportParams) { @@ -136,35 +139,36 @@ public DocumentData getWikiPage(@Nullable ITrackerProject project, @N } public DocumentData getWikiPage(@Nullable ITrackerProject project, @NotNull ExportParams exportParams, boolean withContent) { - return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> { - - String projectId = project != null ? project.getId() : ""; - String locationPath = exportParams.getLocationPath(); - if (locationPath == null) { - throw new IllegalArgumentException("Location path is required for export"); - } - - WikiPageReference wikiPageReference = WikiPageReference.fromPath(createPath(projectId, exportParams.getLocationPath())); - if (exportParams.getRevision() != null) { - wikiPageReference = wikiPageReference.getWithRevision(exportParams.getRevision()); - } - - WikiPage wikiPage = wikiPageReference.getOriginal(transaction); - - String documentContent = null; - if (withContent) { - documentContent = new WikiRenderer().render(projectId, exportParams.getLocationPath(), exportParams.getRevision()); - } - - return DocumentData.builder(DocumentType.WIKI_PAGE, wikiPage.getOldApi()) - .projectName(project != null ? project.getName() : "") - .lastRevision(wikiPage.getOldApi().getLastRevision()) - .baselineName(project != null ? getRevisionBaseline(projectId, wikiPage.getOldApi(), exportParams.getRevision()) : "") - .id(wikiPage.getOldApi().getId()) - .title(wikiPage.getOldApi().getTitleOrName()) - .content(documentContent) - .build(); - }); + return TransactionalExecutor.executeSafelyInReadOnlyTransaction( + transaction -> pdfExporterPolarionService.executeInBaseline(exportParams.getBaselineRevision(), transaction, () -> { + + String projectId = project != null ? project.getId() : ""; + String locationPath = exportParams.getLocationPath(); + if (locationPath == null) { + throw new IllegalArgumentException("Location path is required for export"); + } + + WikiPageReference wikiPageReference = WikiPageReference.fromPath(createPath(projectId, exportParams.getLocationPath())); + if (exportParams.getRevision() != null) { + wikiPageReference = wikiPageReference.getWithRevision(exportParams.getRevision()); + } + + WikiPage wikiPage = wikiPageReference.getOriginal(transaction); + + String documentContent = null; + if (withContent) { + documentContent = new WikiRenderer().render(projectId, exportParams.getLocationPath(), exportParams.getRevision()); + } + + return DocumentData.builder(DocumentType.WIKI_PAGE, wikiPage.getOldApi()) + .projectName(project != null ? project.getName() : "") + .lastRevision(wikiPage.getOldApi().getLastRevision()) + .baselineName(project != null ? getRevisionBaseline(projectId, wikiPage.getOldApi(), exportParams.getRevision()) : "") + .id(wikiPage.getOldApi().getId()) + .title(wikiPage.getOldApi().getTitleOrName()) + .content(documentContent) + .build(); + })); } public DocumentData getLiveDoc(@NotNull ITrackerProject project, @NotNull ExportParams exportParams) { @@ -172,35 +176,36 @@ public DocumentData getLiveDoc(@NotNull ITrackerProject project, @NotNu } public DocumentData getLiveDoc(@NotNull ITrackerProject project, @NotNull ExportParams exportParams, boolean withContent) { - return TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> { - - String locationPath = exportParams.getLocationPath(); - if (locationPath == null) { - throw new IllegalArgumentException("Location path is required for export"); - } - - DocumentReference documentReference = DocumentReference.fromPath(createPath(project.getId(), exportParams.getLocationPath())); - if (exportParams.getRevision() != null) { - documentReference = documentReference.getWithRevision(exportParams.getRevision()); - } - - Document document = documentReference.getOriginal(transaction); - - String documentContent = null; - if (withContent) { - ProxyDocument proxyDocument = new ProxyDocument(document.getOldApi(), (InternalReadOnlyTransaction) transaction); - documentContent = getLiveDocContent(exportParams, proxyDocument, (InternalReadOnlyTransaction) transaction); - } - - return DocumentData.builder(DocumentType.LIVE_DOC, document.getOldApi()) - .projectName(project.getName()) - .lastRevision(document.getOldApi().getLastRevision()) - .baselineName(getRevisionBaseline(project.getId(), document.getOldApi(), exportParams.getRevision())) - .id(document.getOldApi().getModuleName()) - .title(document.getOldApi().getTitleOrName()) - .content(documentContent) - .build(); - }); + return TransactionalExecutor.executeSafelyInReadOnlyTransaction( + transaction -> pdfExporterPolarionService.executeInBaseline(exportParams.getBaselineRevision(), transaction, () -> { + + String locationPath = exportParams.getLocationPath(); + if (locationPath == null) { + throw new IllegalArgumentException("Location path is required for export"); + } + + DocumentReference documentReference = DocumentReference.fromPath(createPath(project.getId(), exportParams.getLocationPath())); + if (exportParams.getRevision() != null) { + documentReference = documentReference.getWithRevision(exportParams.getRevision()); + } + + Document document = documentReference.getOriginal(transaction); + + String documentContent = null; + if (withContent) { + ProxyDocument proxyDocument = new ProxyDocument(document.getOldApi(), (InternalReadOnlyTransaction) transaction); + documentContent = getLiveDocContent(exportParams, proxyDocument, (InternalReadOnlyTransaction) transaction); + } + + return DocumentData.builder(DocumentType.LIVE_DOC, document.getOldApi()) + .projectName(project.getName()) + .lastRevision(document.getOldApi().getLastRevision()) + .baselineName(getRevisionBaseline(project.getId(), document.getOldApi(), exportParams.getRevision())) + .id(document.getOldApi().getModuleName()) + .title(document.getOldApi().getTitleOrName()) + .content(documentContent) + .build(); + })); } public String getLiveDocContent(@NotNull ExportParams exportParams, @NotNull ProxyDocument document, @NotNull InternalReadOnlyTransaction transaction) { @@ -229,8 +234,8 @@ public String getDocumentStatus(String revision, @NotNull DocumentData { - IProject project = pdfExporterPolarionService.getProject(exportParams.getProjectId()); + return Boolean.TRUE.equals(TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> pdfExporterPolarionService.executeInBaseline(exportParams.getBaselineRevision(), transaction, () -> { + IProject project = pdfExporterPolarionService.getProject(Objects.requireNonNull(exportParams.getProjectId())); ILocation location = getDocumentLocation(exportParams.getLocationPath(), exportParams.getRevision()); ProxyDocument document = new ProxyDocument(pdfExporterPolarionService.getModule(project, location), (InternalReadOnlyTransaction) transaction); @@ -240,7 +245,7 @@ public boolean hasLiveDocNestedNumberedLists(@NotNull ExportParams exportParams) String renderedContent = documentRenderer.render(internalContent != null ? internalContent : ""); return new NumberedListsSanitizer().containsNestedNumberedLists(renderedContent); - })); + }))); } public ILocation getDocumentLocation(String locationPath, @Nullable String revision) { diff --git a/src/main/resources/webapp/pdf-exporter/js/export-pdf.js b/src/main/resources/webapp/pdf-exporter/js/export-pdf.js index 838185a..e3cedba 100644 --- a/src/main/resources/webapp/pdf-exporter/js/export-pdf.js +++ b/src/main/resources/webapp/pdf-exporter/js/export-pdf.js @@ -91,7 +91,7 @@ const ExportPdf = { document.getElementById(elementId).className = className; }, - prepareRequest: function (projectId, locationPath, revision, fileName) { + prepareRequest: function (projectId, locationPath, baselineRevision, revision, fileName) { let selectedChapters = null; if (document.getElementById("specific-chapters").checked) { selectedChapters = this.getSelectedChapters(); @@ -119,14 +119,15 @@ const ExportPdf = { selectedRoles.push(...selectedOptions.map(opt => opt.value)); } - return this.buildRequestJson(projectId, locationPath, revision, selectedChapters, numberedListStyles, selectedRoles, fileName); + return this.buildRequestJson(projectId, locationPath, baselineRevision, revision, selectedChapters, numberedListStyles, selectedRoles, fileName); }, - buildRequestJson: function (projectId, locationPath, revision, selectedChapters, numberedListStyles, selectedRoles, fileName) { + buildRequestJson: function (projectId, locationPath, baselineRevision, revision, selectedChapters, numberedListStyles, selectedRoles, fileName) { const urlSearchParams = new URL(window.location.href.replace('#', '/')).searchParams; return new ExportParams.Builder(ExportParams.DocumentType.LIVE_DOC) .setProjectId(projectId) .setLocationPath(locationPath) + .setBaselineRevision(baselineRevision) .setRevision(revision) .setCoverPage(document.getElementById("cover-page-checkbox").checked ? document.getElementById("cover-page-selector").value : null) .setCss(document.getElementById("css-selector").value) @@ -181,7 +182,7 @@ const ExportPdf = { return undefined; }, - loadPdf: function (projectId, locationPath, revision) { + loadPdf: function (projectId, locationPath, baselineRevision, revision) { //clean previous errors $("#export-error").empty(); $("#export-warning").empty(); @@ -194,7 +195,7 @@ const ExportPdf = { fileName += ".pdf"; } - let request = this.prepareRequest(projectId, locationPath, revision, fileName); + let request = this.prepareRequest(projectId, locationPath, baselineRevision, revision, fileName); if (request === undefined) { return; } @@ -246,12 +247,12 @@ const ExportPdf = { } }, - validatePdf: function (projectId, locationPath, revision) { + validatePdf: function (projectId, locationPath, baselineRevision, revision) { //clean previous errors $("#validate-error").empty(); $("#validate-ok").empty(); - let request = this.prepareRequest(projectId, locationPath, revision); + let request = this.prepareRequest(projectId, locationPath, baselineRevision, revision); if (request === undefined) { return; } diff --git a/src/main/resources/webapp/pdf-exporter/js/modules/ExportContext.js b/src/main/resources/webapp/pdf-exporter/js/modules/ExportContext.js index 69e358b..fc25cb0 100644 --- a/src/main/resources/webapp/pdf-exporter/js/modules/ExportContext.js +++ b/src/main/resources/webapp/pdf-exporter/js/modules/ExportContext.js @@ -3,6 +3,7 @@ import ExportParams from "./ExportParams.js"; export default class ExportContext { projectId = undefined; locationPath = undefined; + baselineRevision = undefined; revision = undefined; documentType = undefined; exportType = undefined; @@ -19,7 +20,7 @@ export default class ExportContext { const baseline = getBaseline(normalizedPolarionLocationHash); if (baseline) { - this.revision = getBaselineRevision(baseline); + this.baselineRevision = getBaselineRevision(baseline); } const scope = getScope(normalizedPolarionLocationHash); @@ -36,9 +37,7 @@ export default class ExportContext { } this.urlQueryParameters = getQueryParams(searchParameters); - if (!this.revision) { - this.revision = this.urlQueryParameters?.revision; - } + this.revision = this.urlQueryParameters?.revision; this.bulkExportWidget = bulkExportWidget; @@ -141,6 +140,10 @@ export default class ExportContext { return this.locationPath; } + getBaselineRevision() { + return this.baselineRevision; + } + getRevision() { return this.revision; } @@ -187,6 +190,7 @@ export default class ExportContext { return new ExportParams.Builder(this.documentType) .setProjectId(this.projectId) .setLocationPath(this.locationPath) + .setBaselineRevision(this.baselineRevision) .setRevision(this.revision) .setUrlQueryParameters(this.urlQueryParameters) .build(); diff --git a/src/main/resources/webapp/pdf-exporter/js/modules/ExportParams.js b/src/main/resources/webapp/pdf-exporter/js/modules/ExportParams.js index 3f2176b..50ece30 100644 --- a/src/main/resources/webapp/pdf-exporter/js/modules/ExportParams.js +++ b/src/main/resources/webapp/pdf-exporter/js/modules/ExportParams.js @@ -33,6 +33,7 @@ export default class ExportParams { constructor(builder) { this.projectId = builder.projectId; this.locationPath = builder.locationPath; + this.baselineRevision = builder.baselineRevision; this.revision = builder.revision; this.documentType = builder.documentType; this.coverPage = builder.coverPage; @@ -83,6 +84,7 @@ export default class ExportParams { // initialize all other values as undefined this.projectId = undefined; this.locationPath = undefined; + this.baselineRevision = undefined; this.revision = undefined; this.coverPage = undefined; this.css = undefined; @@ -120,6 +122,11 @@ export default class ExportParams { return this; } + setBaselineRevision(baselineRevision) { + this.baselineRevision = baselineRevision; + return this; + } + setRevision(revision) { this.revision = revision; return this; diff --git a/src/main/resources/webapp/pdf-exporter/js/pdf-exporter.js b/src/main/resources/webapp/pdf-exporter/js/pdf-exporter.js index 4145668..a6feb87 100644 --- a/src/main/resources/webapp/pdf-exporter/js/pdf-exporter.js +++ b/src/main/resources/webapp/pdf-exporter/js/pdf-exporter.js @@ -548,6 +548,7 @@ const PdfExporter = { return new ExportParams.Builder(this.exportContext.getDocumentType()) .setProjectId(this.exportContext.getProjectId()) .setLocationPath(this.exportContext.getLocationPath()) + .setBaselineRevision(this.exportContext.getBaselineRevision()) .setRevision(this.exportContext.getRevision()) .setCoverPage(document.getElementById("popup-cover-page-checkbox").checked ? document.getElementById("popup-cover-page-selector").value : null) .setCss(document.getElementById("popup-css-selector").value) diff --git a/src/test/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionServiceTest.java b/src/test/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionServiceTest.java index 39dd6a2..718ebe4 100644 --- a/src/test/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionServiceTest.java +++ b/src/test/java/ch/sbb/polarion/extension/pdf_exporter/service/PdfExporterPolarionServiceTest.java @@ -5,6 +5,7 @@ import ch.sbb.polarion.extension.generic.settings.SettingName; import ch.sbb.polarion.extension.generic.util.PObjectListStub; import ch.sbb.polarion.extension.generic.util.ScopeUtils; +import ch.sbb.polarion.extension.pdf_exporter.exception.BaselineExecutionException; import ch.sbb.polarion.extension.pdf_exporter.rest.model.attachments.TestRunAttachment; import ch.sbb.polarion.extension.pdf_exporter.rest.model.collections.DocumentCollectionEntry; import ch.sbb.polarion.extension.pdf_exporter.rest.model.settings.stylepackage.StylePackageModel; @@ -14,6 +15,8 @@ import com.polarion.alm.shared.api.model.baselinecollection.BaselineCollection; import com.polarion.alm.shared.api.model.baselinecollection.BaselineCollectionReference; import com.polarion.alm.shared.api.transaction.ReadOnlyTransaction; +import com.polarion.alm.shared.api.utils.PolarionUtils; +import com.polarion.alm.shared.api.utils.RunnableWithResult; import com.polarion.alm.tracker.ITestManagementService; import com.polarion.alm.tracker.ITrackerService; import com.polarion.alm.tracker.model.IModule; @@ -26,6 +29,8 @@ import com.polarion.platform.security.ISecurityService; import com.polarion.platform.service.repository.IRepositoryService; import com.polarion.subterra.base.location.ILocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedConstruction; @@ -243,7 +248,7 @@ void testGetDocumentsFromCollection() { String collectionId = "testCollectionId"; IBaselineCollection mockCollection = mock(IBaselineCollection.class); - BaselineCollectionReference mockReference = mock(BaselineCollectionReference.class); + BaselineCollectionReference mockBaselineCollectionReference = mock(BaselineCollectionReference.class); IModule mockModule1 = mock(IModule.class); IModule mockModule2 = mock(IModule.class); @@ -268,13 +273,14 @@ void testGetDocumentsFromCollection() { BaselineCollection baselineCollection = mock(BaselineCollection.class); when(mockCollection.getElements()).thenReturn(List.of(mockElement1, mockElement2)); - when(mockReference.get(Mockito.any())).thenReturn(baselineCollection); + when(mockBaselineCollectionReference.get(Mockito.any())).thenReturn(baselineCollection); when(baselineCollection.getOldApi()).thenReturn(mockCollection); try (MockedConstruction mockedStaticReference = mockConstruction(BaselineCollectionReference.class, (mock, context) -> { when(mock.get(Mockito.any())).thenReturn(baselineCollection); + when(mock.getWithRevision(Mockito.anyString())).thenReturn(mock); })) { - List result = service.getDocumentsFromCollection(projectId, collectionId, mock(ReadOnlyTransaction.class)); + List result = service.getDocumentsFromCollection(projectId, collectionId, null, mock(ReadOnlyTransaction.class)); assertNotNull(result); assertEquals(2, result.size()); @@ -285,6 +291,57 @@ void testGetDocumentsFromCollection() { assertEquals("_default", result.get(1).getSpaceId()); assertEquals("test Module2", result.get(1).getDocumentName()); assertEquals("2", result.get(1).getRevision()); + + List resultWithRevision = service.getDocumentsFromCollection(projectId, collectionId, "1234", mock(ReadOnlyTransaction.class)); + + assertNotNull(resultWithRevision); + assertEquals(2, resultWithRevision.size()); + assertEquals("space 1", resultWithRevision.get(0).getSpaceId()); + assertEquals("test Module1", resultWithRevision.get(0).getDocumentName()); + assertEquals("1", resultWithRevision.get(0).getRevision()); + + assertEquals("_default", resultWithRevision.get(1).getSpaceId()); + assertEquals("test Module2", resultWithRevision.get(1).getDocumentName()); + assertEquals("2", resultWithRevision.get(1).getRevision()); + } } + + @Test + void testExecuteInBaseline() { + PolarionUtils polarionUtils = new PolarionUtils() { + @Override + public @Nullable T executeInBaseline(@NotNull String s, @NotNull RunnableWithResult runnableWithResult) { + return runnableWithResult.run(); + } + + @Override + public @Nullable T executeOutsideBaseline(@NotNull RunnableWithResult runnableWithResult) { + return null; + } + + @Override + public @NotNull String convertToAscii(@Nullable String s) { + return ""; + } + + @Override + public @NotNull String convertToAscii(@Nullable String s, @Nullable String s1) { + return ""; + } + }; + + ReadOnlyTransaction mockReadOnlyTransaction = mock(ReadOnlyTransaction.class); + when(mockReadOnlyTransaction.utils()).thenReturn(polarionUtils); + + assertEquals("valueWithoutBaseline", service.executeInBaseline(null, mockReadOnlyTransaction, () -> "valueWithoutBaseline")); + + assertEquals("valueInBaseline", service.executeInBaseline("1234", mockReadOnlyTransaction, () -> "valueInBaseline")); + assertThrows(BaselineExecutionException.class, () -> service.executeInBaseline("5678", mockReadOnlyTransaction, this::testCallable)); + } + + private String testCallable() throws Exception { + throw new Exception("argument"); + } + } diff --git a/src/test/js/ExportContextTest.js b/src/test/js/ExportContextTest.js index f49fe76..618a7d4 100644 --- a/src/test/js/ExportContextTest.js +++ b/src/test/js/ExportContextTest.js @@ -10,6 +10,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.equal('_default/BigDoc'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -24,6 +25,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.equal('Specification/Administration Specification'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -38,6 +40,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('mega_project'); expect(exportContext.locationPath).to.equal('Specs/test'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -52,6 +55,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.be.null; expect(exportContext.locationPath).to.equal('_default/classic wiki page'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -66,6 +70,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_REPORT); expect(exportContext.projectId).to.be.null; expect(exportContext.locationPath).to.equal('_default/TestLiveReport'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -80,6 +85,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.TEST_RUN); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.be.undefined; + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.deep.equal({ id: 'elibrary_20231026-163136654' }); @@ -94,6 +100,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.equal('Reports/LiveReport with params'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.deep.equal({ stringParameter: 'asd', @@ -112,6 +119,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.TEST_RUN); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.be.undefined; + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -126,6 +134,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.BASELINE_COLLECTION); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.be.undefined; + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.deep.equal({ id: '144' }); @@ -140,6 +149,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.equal('_default/live_doc'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -154,6 +164,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('drivepilot'); expect(exportContext.locationPath).to.equal('Requirements/live doc'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; @@ -168,6 +179,7 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('drivepilot'); expect(exportContext.locationPath).to.equal('Requirements/System Requirement Specification'); + expect(exportContext.baselineRevision).to.be.undefined; expect(exportContext.revision).to.equal('112'); expect(exportContext.urlQueryParameters).to.deep.equal({ revision: '112' }); @@ -182,7 +194,8 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_DOC); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.equal('_default/BigDoc2'); - expect(exportContext.revision).to.equal('6749'); + expect(exportContext.baselineRevision).to.equal('6749'); + expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.be.undefined; expect(exportContext.getSpaceId()).to.equal('_default'); @@ -196,7 +209,8 @@ describe('ExportContext Class', function () { expect(exportContext.documentType).to.equal(ExportParams.DocumentType.LIVE_REPORT); expect(exportContext.projectId).to.equal('elibrary'); expect(exportContext.locationPath).to.equal('Specification/Epic Statistics'); - expect(exportContext.revision).to.equal('6711'); + expect(exportContext.baselineRevision).to.equal('6711'); + expect(exportContext.revision).to.be.undefined; expect(exportContext.urlQueryParameters).to.deep.equal({ parameter: 'value' }); expect(exportContext.getSpaceId()).to.equal('Specification');