diff --git a/Doc/ReleaseNotes-ISHRemote-8.3.md b/Doc/ReleaseNotes-ISHRemote-8.3.md index 040d4d4..58cada7 100644 --- a/Doc/ReleaseNotes-ISHRemote-8.3.md +++ b/Doc/ReleaseNotes-ISHRemote-8.3.md @@ -18,6 +18,23 @@ The one that is The below text describes the delta compared to fielded release ISHRemote v8.2. +### Retrieve the content objects of your publication +Cmdlet `Get-IshPublicationOutputContent` returns the `IshDocumentObj` content objects (topics, maps, illustrations, resources) directly reachable through the saved baseline of one or more incoming `IshPublicationOutput` objects, using `Baseline25.ExpandBaseline` under the hood. Content objects whose version is not pinned in the baseline (a "gap", for example a sub-map that has no version selected) are not returned, even if a topic used by that sub-map does have a version pinned — the baseline walk stops at the gap. Optional parameter `-AutoCompleteMode` (`FirstVersion`, `LatestReleased` or `LatestAvailable`) switches from `Baseline25.ExpandBaseline` to `Baseline25.CompleteBaselineByCandidateAndMode` so gaps get filled in using the given strategy before the (now larger) reachable set is returned. Optional parameters `-Language`/`-Resolution` let you override the languages/resolutions used to walk the baseline instead of relying on the publication output's own `FISHPUBLNGCOMBINATION`/output format `FISHRESOLUTIONS`. + +For example: +```powershell +Get-IshPublicationOutput -LogicalId "GUID-03081B9A-11E4-4862-845B-27339E0C400D" | +Out-GridView -PassThru | +Get-IshPublicationOutputContent -AutoCompleteMode LatestAvailable | +Get-IshDocumentObjData -FolderPath "C:\TEMP\" +``` +interactively picks one or more publication outputs in a grid view, completes any gaps in their baselines using the latest available version of each missing content object, and extracts all reachable content objects to the file system. Or release all directly reachable content objects of a publication output: +```powershell +Get-IshPublicationOutput -LogicalId "GUID-03081B9A-11E4-4862-845B-27339E0C400D" | +Get-IshPublicationOutputContent | +Set-IshDocumentObj -Metadata (Set-IshMetadataField -Name "FSTATUS" -Level Lng -Value "Released") +``` + ## Platform Support for PowerShell .... @@ -30,6 +47,7 @@ The below text describes the delta compared to fielded release ISHRemote v8.2. + ## Breaking Changes - Cmdlets All cmdlets and business logic are fully compatible. diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputContent.Tests.ps1 b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputContent.Tests.ps1 new file mode 100644 index 0000000..40a1ab8 --- /dev/null +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputContent.Tests.ps1 @@ -0,0 +1,190 @@ +BeforeAll { + $cmdletName = "Get-IshPublicationOutputContent" + Write-Host ("`r`nLoading ISHRemote.PesterSetup.ps1 on PSVersion[" + $psversionTable.PSVersion + "] over BeforeAll-block for MyCommand[" + $cmdletName + "]...") + . (Join-Path (Split-Path -Parent $PSCommandPath) "\..\..\ISHRemote.PesterSetup.ps1") + + Write-Host ("Running "+$cmdletName+" Test Data and Variables initialization") +} + +Describe "Get-IshPublicationOutputContent" -Tags "Read" { + BeforeAll { + $requestedMetadata = Set-IshRequestedMetadataField -IshSession $ishSession -Name "FNAME" | + Set-IshRequestedMetadataField -IshSession $ishSession -Name "FDOCUMENTTYPE" | + Set-IshRequestedMetadataField -IshSession $ishSession -Name "READ-ACCESS" -ValueType Element | + Set-IshRequestedMetadataField -IshSession $ishSession -Name "FUSERGROUP" -ValueType Element + $ishFolderTestRootOriginal = Get-IshFolder -IShSession $ishSession -FolderPath $folderTestRootPath -RequestedMetadata $requestedMetadata + $folderIdTestRootOriginal = $ishFolderTestRootOriginal.IshFolderRef + $folderTypeTestRootOriginal = $ishFolderTestRootOriginal.IshFolderType + + Write-Debug("folderIdTestRootOriginal[" + $folderIdTestRootOriginal + "] folderTypeTestRootOriginal[" + $folderTypeTestRootOriginal + "]") + $ownedByTestRootOriginal = Get-IshMetadataField -IshSession $ishSession -Name "FUSERGROUP" -ValueType Element -IshField $ishFolderTestRootOriginal.IshField + $readAccessTestRootOriginal = (Get-IshMetadataField -IshSession $ishSession -Name "READ-ACCESS" -ValueType Element -IshField $ishFolderTestRootOriginal.IshField).Split($ishSession.Separator) + + $ishFolderCmdlet = Add-IshFolder -IShSession $ishSession -ParentFolderId $folderIdTestRootOriginal -FolderType $folderTypeTestRootOriginal -FolderName $cmdletName -OwnedBy $ownedByTestRootOriginal -ReadAccess $readAccessTestRootOriginal + + # ---- Create a topic ---- + $ishFolderTopic = Add-IshFolder -IshSession $ishSession -ParentFolderId ($ishFolderCmdlet.IshFolderRef) -FolderType ISHModule -FolderName "Topic" -OwnedBy $ownedByTestRootOriginal -ReadAccess $readAccessTestRootOriginal + $ishTopicMetadata = Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "$cmdletName Topic $timestamp" | + Set-IshMetadataField -IshSession $ishSession -Name "FAUTHOR" -Level Lng -ValueType Element -Value $ishUserAuthor | + Set-IshMetadataField -IshSession $ishSession -Name "FSTATUS" -Level Lng -ValueType Element -Value $ishStatusDraft + $ishObjectTopic = Add-IshDocumentObj -IshSession $ishSession -IshFolder $ishFolderTopic -IshType ISHModule ` + -LogicalId ("GETISHPUBLICATIONOUTPUTCONTENT-TOPIC-" + $timestamp) -Version '1' -Lng $ishLng ` + -Metadata $ishTopicMetadata -Edt "EDTXML" -FileContent $ditaTopicFileContent + + # ---- Create a map referencing the topic ---- + $mapFileContent = $ditaMapWithTopicrefFileContent -replace '', $ishObjectTopic.IshRef + $ishFolderMap = Add-IshFolder -IshSession $ishSession -ParentFolderId ($ishFolderCmdlet.IshFolderRef) -FolderType ISHMasterDoc -FolderName "Map" -OwnedBy $ownedByTestRootOriginal -ReadAccess $readAccessTestRootOriginal + $ishMapMetadata = Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "$cmdletName Map $timestamp" | + Set-IshMetadataField -IshSession $ishSession -Name "FAUTHOR" -Level Lng -ValueType Element -Value $ishUserAuthor | + Set-IshMetadataField -IshSession $ishSession -Name "FSTATUS" -Level Lng -ValueType Element -Value $ishStatusDraft + $ishObjectMap = Add-IshDocumentObj -IshSession $ishSession -IshFolder $ishFolderMap -IshType ISHMasterDoc ` + -LogicalId ("GETISHPUBLICATIONOUTPUTCONTENT-MAP-" + $timestamp) -Version '2' -Lng $ishLng ` + -Metadata $ishMapMetadata -Edt "EDTXML" -FileContent $mapFileContent + + # ---- Create a library topic ---- + $ishFolderLibraryTopic = Add-IshFolder -IshSession $ishSession -ParentFolderId ($ishFolderCmdlet.IshFolderRef) -FolderType ISHLibrary -FolderName "Lib" -OwnedBy $ownedByTestRootOriginal -ReadAccess $readAccessTestRootOriginal + $ishLibraryTopicMetadata = Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "$cmdletName Topic $timestamp" | + Set-IshMetadataField -IshSession $ishSession -Name "FAUTHOR" -Level Lng -ValueType Element -Value $ishUserAuthor | + Set-IshMetadataField -IshSession $ishSession -Name "FSTATUS" -Level Lng -ValueType Element -Value $ishStatusDraft + $ishObjectLibraryTopic = Add-IshDocumentObj -IshSession $ishSession -IshFolder $ishFolderLibraryTopic -IshType ISHLibrary ` + -LogicalId ("GETISHPUBLICATIONOUTPUTCONTENT-LIB-" + $timestamp) -Version '3' -Lng $ishLng ` + -Metadata $ishLibraryTopicMetadata -Edt "EDTXML" -FileContent $ditaTopicFileContent + + # ---- Create a publication output (Add-IshPublicationOutput implicitly creates a fresh baseline) ---- + $ishFolderPub = Add-IshFolder -IshSession $ishSession -ParentFolderId ($ishFolderCmdlet.IshFolderRef) -FolderType ISHPublication -FolderName "Pub" -OwnedBy $ownedByTestRootOriginal -ReadAccess $readAccessTestRootOriginal + $ishPubMetadata = Set-IshMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical -Value "$cmdletName Pub $timestamp" | + Set-IshMetadataField -IshSession $ishSession -Name "FISHMASTERREF" -Level Version -ValueType Element -Value $ishObjectMap.IshRef | + Set-IshMetadataField -IshSession $ishSession -Name "FISHRESOURCES" -Level Version -ValueType Element -Value $ishObjectLibraryTopic.IshRef | + Set-IshMetadataField -IshSession $ishSession -Name "FISHPUBSOURCELANGUAGES" -Level Version -ValueType Element -Value $ishLng | + Set-IshMetadataField -IshSession $ishSession -Name "FISHREQUIREDRESOLUTIONS" -Level Version -ValueType Element -Value $ishResolution + $ishObjectPub = Add-IshPublicationOutput -IshSession $ishSession -IshFolder $ishFolderPub ` + -LogicalId ("GETISHPUBLICATIONOUTPUTCONTENT-PUB-" + $timestamp) -Version '1' ` + -LanguageCombination $ishLngCombination -OutputFormat $ishOutputFormatDitaXml -Metadata $ishPubMetadata + + # ---- Retrieve the auto-created baseline ID and pin the map and topic versions in it ---- + # Add-IshPublicationOutput implicitly creates a fresh empty baseline; retrieve its element name. + $baselineId = $ishObjectPub | + Get-IshPublicationOutput -IshSession $ishSession -RequestedMetadata (Set-IshRequestedMetadataField -IshSession $ishSession -Name "FISHBASELINE" -Level Version -ValueType Element) | + Get-IshMetadataField -IshSession $ishSession -Name "FISHBASELINE" -Level Version -ValueType Element + $ishBaseline = Get-IshBaseline -IshSession $ishSession -Id $baselineId + $ishBaseline = Set-IshBaselineItem -IshSession $ishSession -IshObject $ishBaseline -LogicalId $ishObjectMap.IshRef -Version '2' + $ishBaseline = Set-IshBaselineItem -IshSession $ishSession -IshObject $ishBaseline -LogicalId $ishObjectTopic.IshRef -Version '1' + #$ishBaseline = Set-IshBaselineItem -IshSession $ishSession -IshObject $ishBaseline -LogicalId $ishObjectLibraryTopic.IshRef -Version '3' #deliberately to set up autocomplete + } + + Context "Get-IshPublicationOutputContent ParameterGroup validation" { + It "InvalidIshSession" { + { Get-IshPublicationOutputContent -IshSession "INVALIDISHSESSION" -IshObject $ishObjectPub } | Should-Throw + } + } + + Context "Get-IshPublicationOutputContent IshObjectGroup empty pipeline" { + It "EmptyIshObject over pipeline" { + $result = @() | Get-IshPublicationOutputContent -IshSession $ishSession + $result.Count | Should-Be 0 + } + It "EmptyIshObject over parameter" { + $result = Get-IshPublicationOutputContent -IshSession $ishSession -IshObject @() + $result.Count | Should-Be 0 + } + } + + Context "Get-IshPublicationOutputContent IshObjectGroup" { + BeforeAll { + $result = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession + } + It "Returns IshDocumentObj objects" { + $result | Should-NotBeNull + $result.Count -eq 2 | Should-Be $true + } + It "GetType returns IshDocumentObj" { + foreach ($item in $result) { + $item.GetType().Name | Should-BeString -CaseSensitive "IshDocumentObj" + } + } + It "ResultContainsMap" { + $mapFound = $result | Where-Object { $_.IshRef -eq $ishObjectMap.IshRef } + $mapFound | Should-NotBeNull + } + It "ResultContainsTopic" { + $topicFound = $result | Where-Object { $_.IshRef -eq $ishObjectTopic.IshRef } + $topicFound | Should-NotBeNull + } + It "ResultDoesNotContainLibraryTopic" { + # Library topic was deliberately not pinned in the baseline; expand-only (no -AutoCompleteMode) should not reach it. + $libFound = $result | Where-Object { $_.IshRef -eq $ishObjectLibraryTopic.IshRef } + $libFound | Should-BeNull + } + It "result[0].IshData" { + { $result[0].IshData } | Should -Not -Throw + } + It "result[0].IshField" { + $result[0].IshField | Should-NotBeNull + } + It "result[0].IshType" { + $result[0].IshType | Should-NotBeNull + } + It "result[0].ObjectRef" { + $result[0].ObjectRef | Should-NotBeNull + } + It "result[0].VersionRef" { + $result[0].VersionRef | Should-NotBeNull + } + It "result[0].LngRef" { + $result[0].LngRef | Should-NotBeNull + } + It "Explicit IshObject parameter instead of pipeline" { + $explicitResult = Get-IshPublicationOutputContent -IshSession $ishSession -IshObject $ishObjectPub + $explicitResult.Count -eq 2 | Should-Be $true + } + It "RequestedMetadata returns extra fields" { + $extraRequestedMetadata = Set-IshRequestedMetadataField -IshSession $ishSession -Name "FTITLE" -Level Logical + $requestedResult = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -RequestedMetadata $extraRequestedMetadata + $requestedResult.Count -eq 2 | Should-Be $true + $withTitle = $requestedResult | Where-Object { $_.ftitle_logical_value.Length -ge 1 } + $withTitle | Should-NotBeNull + } + } + + Context "Get-IshPublicationOutputContent IshObjectGroup AutoCompleteMode" { + It "AutoCompleteMode LatestAvailable returns more objects than expand alone" { + $expandResult = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession + $completeResult = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -AutoCompleteMode LatestAvailable + $completeResult.Count -eq 3 | Should-Be $true + $completeResult.Count -gt $expandResult.Count | Should-Be $true + ($completeResult | Where-Object { $_.IshRef -eq $ishObjectLibraryTopic.IshRef }).version_version_value | Should-BeString -CaseSensitive "3" + ($completeResult | Where-Object { $_.IshRef -eq $ishObjectMap.IshRef }).version_version_value | Should-BeString -CaseSensitive "2" + ($completeResult | Where-Object { $_.IshRef -eq $ishObjectTopic.IshRef }).version_version_value | Should-BeString -CaseSensitive "1" + } + It "AutoCompleteMode LatestAvailable ResultContainsLibraryTopic" { + $result = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -AutoCompleteMode LatestAvailable + $libFound = $result | Where-Object { $_.IshRef -eq $ishObjectLibraryTopic.IshRef } + $libFound | Should-NotBeNull + } + It "AutoCompleteMode FirstVersion does not throw" { + { $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -AutoCompleteMode FirstVersion } | Should -Not -Throw + } + It "AutoCompleteMode LatestReleased does not throw" { + { $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -AutoCompleteMode LatestReleased } | Should -Not -Throw + } + } + + Context "Get-IshPublicationOutputContent IshObjectGroup Language and Resolution override" { + It "Language override returns results" { + $result = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -Language @($ishLngCombination) + $result.Count -ge 1 | Should-Be $true + } + It "Resolution override returns results" { + $result = $ishObjectPub | Get-IshPublicationOutputContent -IshSession $ishSession -Resolution @($ishResolution) + $result.Count -ge 1 | Should-Be $true + } + } +} + +AfterAll { + Write-Host ("Running "+$cmdletName+" Test Data and Variables cleanup") + $folderCmdletRootPath = (Join-Path $folderTestRootPath $cmdletName) + try { Get-IshFolder -IshSession $ishSession -FolderPath $folderCmdletRootPath -Recurse | Where-Object -Property IshFolderType -EQ -Value "ISHPublication" | Get-IshFolderContent -IshSession $ishSession | Remove-IshPublicationOutput -IshSession $ishSession -Force } catch { } + try { Get-IshFolder -IshSession $ishSession -FolderPath $folderCmdletRootPath -Recurse | Get-IshFolderContent -IshSession $ishSession | Remove-IshDocumentObj -IshSession $ishSession -Force } catch { } + try { Remove-IshFolder -IshSession $ishSession -FolderPath $folderCmdletRootPath -Recurse } catch { } +} \ No newline at end of file diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputContent.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputContent.cs new file mode 100644 index 0000000..4f25ffe --- /dev/null +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputContent.cs @@ -0,0 +1,403 @@ +/* +* Copyright (c) 2014 All Rights Reserved by the SDL Group. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.ServiceModel; +using System.Xml; +using Trisoft.ISHRemote.Exceptions; +using Trisoft.ISHRemote.HelperClasses; +using Trisoft.ISHRemote.Objects; +using Trisoft.ISHRemote.Objects.Public; + +namespace Trisoft.ISHRemote.Cmdlets.PublicationOutput +{ + /// + /// The Get-IshPublicationOutputContent cmdlet returns the IshDocumentObj objects that are directly reachable through the saved baseline of the incoming IshPublicationOutput objects. + /// The Get-IshPublicationOutputContent cmdlet expands the saved baseline of each incoming IshPublicationOutput and returns the IshDocumentObj objects (topics, maps, illustrations, resources) that are directly reachable — meaning only content objects for which the baseline holds a pinned version are returned. Content objects whose version is not selected in the baseline (gaps) are not returned even if they would be reachable via a full autocomplete pass. + /// When -AutoCompleteMode is provided, the cmdlet instead completes the baseline for any gaps (missing objects) using the specified strategy (FirstVersion, LatestReleased or LatestAvailable) before returning the (now larger) set of directly reachable IshDocumentObj objects. Without -AutoCompleteMode, gaps are left untouched and the corresponding content objects are not returned. + /// The -Language and -Resolution parameters allow overriding the languages and resolutions used to walk the baseline; when omitted they default to the publication output's own FISHPUBLNGCOMBINATION and output format FISHRESOLUTIONS. + /// The cmdlet silently fetches all publication output metadata it needs (FISHBASELINE, FISHMASTERREF, FISHRESOURCES, FISHPUBLNGCOMBINATION, FISHOUTPUTFORMATREF and the output format's FISHRESOLUTIONS) — callers do not need to pre-fetch specific fields. + /// Typical follow-on pipeline operations are Get-IshDocumentObjData, Set-IshDocumentObj or status-transition scripts. + /// + /// + /// + /// Get-IshPublicationOutput -LogicalId "GUID-12345678-ABCD-EFGH-IJKL-1234567890AB" | + /// Get-IshPublicationOutputContent + /// + /// Returns all directly reachable IshDocumentObj objects from the saved baseline of the given publication output. + /// + /// + /// + /// Get-IshPublicationOutput -LogicalId "GUID-12345678-ABCD-EFGH-IJKL-1234567890AB" | + /// Get-IshPublicationOutputContent | + /// Set-IshDocumentObj -Metadata (Set-IshMetadataField -Name "FSTATUS" -Level Lng -Value "Released") + /// + /// Releases all content objects that are directly reachable through the saved baseline of the given publication output. + /// + /// + /// + /// Get-IshPublicationOutput -LogicalId "GUID-12345678-ABCD-EFGH-IJKL-1234567890AB" | + /// Get-IshPublicationOutputContent -AutoCompleteMode LatestReleased + /// + /// Completes gaps in the saved baseline using the latest released version of each missing content object, then returns all reachable IshDocumentObj objects. + /// + /// + /// + /// Get-IshPublicationOutput -LogicalId "GUID-12345678-ABCD-EFGH-IJKL-1234567890AB" | + /// Get-IshPublicationOutputContent -Language @("en") -Resolution @("VRESLOW") + /// + /// Returns all directly reachable IshDocumentObj objects, overriding the languages and resolutions used to walk the baseline. + /// + /// + /// + /// Get-IshPublicationOutput -LogicalId ""GUID-12345678-ABCD-EFGH-IJKL-1234567890AB" | + /// Out-GridView -PassThru | + /// Get-IshPublicationOutputContent -AutoCompleteMode LatestAvailable | + /// Get-IshDocumentObjData -FolderPath "C:\TEMP\" + /// + /// Lets you interactively pick one or more publication outputs in a grid view, completes gaps in their baselines using the latest available version of each missing content object, and extracts the resulting content objects to the file system. + /// + [Cmdlet(VerbsCommon.Get, "IshPublicationOutputContent", SupportsShouldProcess = false)] + [OutputType(typeof(IshDocumentObj))] + public sealed class GetIshPublicationOutputContent : PublicationOutputCmdlet + { + /// + /// The IshSession variable holds the authentication and contract information. This object can be initialized using the New-IshSession cmdlet. + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] + [ValidateNotNullOrEmpty] + public IshSession IshSession { get; set; } + + /// + /// Array with the publication outputs for which to retrieve the directly reachable content objects. This array can be passed through the pipeline or explicitly passed via the parameter. + /// + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "IshObjectGroup")] + [AllowEmptyCollection] + public IshObject[] IshObject { get; set; } + + /// + /// The requested metadata fields to retrieve on the returned IshDocumentObj objects. Defaults to IshSession.DefaultRequestedMetadata. + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] + [ValidateNotNull] + public IshField[] RequestedMetadata { get; set; } + + /// + /// When specified, gaps (missing objects) in the saved baseline are completed using the given strategy before the reachable content objects are returned. When omitted, gaps are left untouched (expand-only behavior). + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] + public Enumerations.BaselineAutoCompleteMode AutoCompleteMode { get; set; } + + /// + /// Overrides the languages used to walk the baseline, expressed as language Value labels (e.g. "en"), not Element identifiers. When omitted, defaults to the publication output's own FISHPUBLNGCOMBINATION. + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] + public string[] Language { get; set; } + + /// + /// Overrides the resolutions used to walk the baseline, expressed as resolution Element identifiers (e.g. "VRESLOW"), not Value labels. When omitted, defaults to the output format's FISHRESOLUTIONS. + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = false, ParameterSetName = "IshObjectGroup")] + public string[] Resolution { get; set; } + + // Accumulate incoming publication outputs across ProcessRecord calls so we can batch + // the metadata re-fetch and the ExpandBaseline calls in EndProcessing. + private readonly List _incomingIshObjects = new List(); + + protected override void BeginProcessing() + { + if (IshSession == null) { IshSession = (IshSession)SessionState.PSVariable.GetValue(ISHRemoteSessionStateIshSession); } + if (IshSession == null) { IshSession = (IshSession)SessionState.PSVariable.GetValue(ISHRemoteSessionStateGlobalIshSession); } + if (IshSession == null) { throw new ArgumentException(ISHRemoteSessionStateIshSessionException); } + WriteDebug($"Using IshSession[{IshSession.Name}] from SessionState.{ISHRemoteSessionStateIshSession} or in turn SessionState.{ISHRemoteSessionStateGlobalIshSession}"); + base.BeginProcessing(); + } + + protected override void ProcessRecord() + { + if (IshObject != null && IshObject.Length == 0) + { + WriteVerbose("IshObject is empty, so nothing to retrieve"); + return; + } + if (IshObject != null) + { + _incomingIshObjects.AddRange(IshObject); + } + } + + protected override void EndProcessing() + { + try + { + if (_incomingIshObjects.Count == 0) + { + WriteVerbose("IshObject is empty, so nothing to retrieve"); + return; + } + + // --- Step 1: silently re-fetch all publication output metadata we need --- + // We need (on the publication output): + // Version level : FISHBASELINE, FISHMASTERREF, FISHRESOURCES + // Lng level : FISHPUBLNGCOMBINATION, FISHOUTPUTFORMATREF + // lngref is always structurally present on every IshPublicationOutput so we use it + // to batch-fetch all the fields we need regardless of what the caller had requested. + WriteDebug("PublicationOutput metadata retrieval"); + var lngRefs = _incomingIshObjects + .Select(o => Convert.ToInt64(o.ObjectRef[Enumerations.ReferenceType.Lng])) + .ToArray(); + + // Build a minimal requested metadata set covering only what ExpandBaseline needs. + var requiredPubFields = new IshFields(); + requiredPubFields.AddField(new IshRequestedMetadataField("VERSION", Enumerations.Level.Version, Enumerations.ValueType.Value)); + requiredPubFields.AddField(new IshRequestedMetadataField("FISHBASELINE", Enumerations.Level.Version, Enumerations.ValueType.Element)); + requiredPubFields.AddField(new IshRequestedMetadataField("FISHMASTERREF", Enumerations.Level.Version, Enumerations.ValueType.Element)); + requiredPubFields.AddField(new IshRequestedMetadataField("FISHRESOURCES", Enumerations.Level.Version, Enumerations.ValueType.Element)); + requiredPubFields.AddField(new IshRequestedMetadataField("FISHPUBLNGCOMBINATION", Enumerations.Level.Lng, Enumerations.ValueType.Value)); + requiredPubFields.AddField(new IshRequestedMetadataField("FISHOUTPUTFORMATREF", Enumerations.Level.Lng, Enumerations.ValueType.Element)); + + string xmlPubObjects = IshSession.PublicationOutput25.RetrieveMetadataByIshLngRefs( + lngRefs, + requiredPubFields.ToXml()); + var refreshedPubObjects = new IshObjects(ISHType, xmlPubObjects).Objects; + + WriteDebug($"PublicationOutput metadata retrieval count[{refreshedPubObjects.Length}]"); + + // --- Step 2: for each publication output call ExpandBaseline --- + var allLngRefs = new List(); + int current = 0; + + foreach (var pubObject in refreshedPubObjects) + { + string version = ((IshMetadataField)pubObject.IshFields.RetrieveFirst("VERSION", Enumerations.Level.Version, Enumerations.ValueType.Value)?.ToMetadataField()).Value; + string pubLngCombination = ((IshMetadataField)pubObject.IshFields.RetrieveFirst("FISHPUBLNGCOMBINATION", Enumerations.Level.Lng, Enumerations.ValueType.Value)?.ToMetadataField()).Value; + string pubOutputFormatRef = ((IshMetadataField)pubObject.IshFields.RetrieveFirst("FISHOUTPUTFORMATREF", Enumerations.Level.Lng, Enumerations.ValueType.Element)?.ToMetadataField()) .Value; + string pubObjectHumanId = $"={pubObject.IshRef}={version}={pubLngCombination}={pubOutputFormatRef}"; + WriteDebug($"Processing[{pubObjectHumanId}] {++current}/{refreshedPubObjects.Length}"); + + // Extract FISHBASELINE (baseline GUID) + // RetrieveFirst prefers id over element over value; ToMetadataField() / cast gives .Value + var baselineField = pubObject.IshFields + .RetrieveFirst("FISHBASELINE", Enumerations.Level.Version, Enumerations.ValueType.Element) + ?.ToMetadataField() as IshMetadataField; + string baselineId = baselineField?.Value; + if (string.IsNullOrEmpty(baselineId)) + { + WriteWarning($"Processing[{pubObjectHumanId}] has no FISHBASELINE field value — skipping."); + continue; + } + + // Extract FISHMASTERREF (root map logical ID) — startLogicalIds[] + var masterRefField = pubObject.IshFields + .RetrieveFirst("FISHMASTERREF", Enumerations.Level.Version, Enumerations.ValueType.Element) + ?.ToMetadataField() as IshMetadataField; + string masterRef = masterRefField?.Value; + var startLogicalIds = string.IsNullOrEmpty(masterRef) + ? Array.Empty() + : new[] { masterRef }; + + // Extract FISHRESOURCES (multi-value) — startResourceLogicalIds[] + var resourcesField = pubObject.IshFields + .RetrieveFirst("FISHRESOURCES", Enumerations.Level.Version, Enumerations.ValueType.Element) + ?.ToMetadataField() as IshMetadataField; + string resourcesRaw = resourcesField?.Value ?? string.Empty; + string[] startResourceLogicalIds = string.IsNullOrEmpty(resourcesRaw) + ? Array.Empty() + : resourcesRaw.Split(new[] { IshSession.Separator }, StringSplitOptions.RemoveEmptyEntries); + + // Extract FISHPUBLNGCOMBINATION — languages[], illustrationLanguages[], resourceLanguages[] + // unless overridden by -Language parameter. + string[] languages; + if (MyInvocation.BoundParameters.ContainsKey(nameof(Language))) + { + languages = Language ?? Array.Empty(); + } + else + { + var langComboField = pubObject.IshFields + .RetrieveFirst("FISHPUBLNGCOMBINATION", Enumerations.Level.Lng, Enumerations.ValueType.Value) + ?.ToMetadataField() as IshMetadataField; + string langComboRaw = langComboField?.Value ?? string.Empty; + languages = string.IsNullOrEmpty(langComboRaw) + ? Array.Empty() + : langComboRaw.Split(new[] { IshSession.Separator }, StringSplitOptions.RemoveEmptyEntries); + } + + // Extract FISHOUTPUTFORMATREF (element name of the output format card) + // then fetch FISHRESOLUTIONS from that output format card — resolutions[] + // unless overridden by -Resolution parameter. + string[] resolutions; + if (MyInvocation.BoundParameters.ContainsKey(nameof(Resolution))) + { + resolutions = Resolution ?? Array.Empty(); + } + else + { + var outputFormatRefField = pubObject.IshFields + .RetrieveFirst("FISHOUTPUTFORMATREF", Enumerations.Level.Lng, Enumerations.ValueType.Element) + ?.ToMetadataField() as IshMetadataField; + string outputFormatRef = outputFormatRefField?.Value; + resolutions = Array.Empty(); + if (!string.IsNullOrEmpty(outputFormatRef)) + { + var outputFormatRequestedFields = new IshFields(); + outputFormatRequestedFields.AddField(new IshRequestedMetadataField("FISHRESOLUTIONS", Enumerations.Level.None, Enumerations.ValueType.Element)); + var outputFormatResponse = IshSession.OutputFormat25.GetMetadata( + new OutputFormat25ServiceReference.GetMetadataRequest( + outputFormatRef, + outputFormatRequestedFields.ToXml())); + Enumerations.ISHType[] outputFormatISHType = { Enumerations.ISHType.ISHOutputFormat }; + var outputFormatObjects = new IshObjects(outputFormatISHType, outputFormatResponse.xmlObjectList); + if (outputFormatObjects.Objects.Length > 0) + { + var resolutionsField = outputFormatObjects.Objects[0].IshFields + .RetrieveFirst("FISHRESOLUTIONS", Enumerations.Level.None, Enumerations.ValueType.Element) + ?.ToMetadataField() as IshMetadataField; + string resolutionsRaw = resolutionsField?.Value ?? string.Empty; + resolutions = string.IsNullOrEmpty(resolutionsRaw) + ? Array.Empty() + : resolutionsRaw.Split(new[] { IshSession.Separator }, StringSplitOptions.RemoveEmptyEntries); + } + WriteDebug($"Processing[{pubObjectHumanId}] OutputFormatRef[{outputFormatRef}] Resolutions[{string.Join(",", resolutions)}]"); + } + } + + WriteDebug($"Processing[{pubObjectHumanId}] BaselineId[{baselineId}] MasterRef[{masterRef}] Languages[{string.Join(",", languages)}] Resolutions[{string.Join(",", resolutions)}] Resources[{string.Join(",", startResourceLogicalIds)}]"); + + // --- Step 3: call ExpandBaseline, or CompleteBaselineByCandidateAndMode when -AutoCompleteMode is specified --- + string xmlBaselineReport; + if (MyInvocation.BoundParameters.ContainsKey(nameof(AutoCompleteMode))) + { + WriteDebug($"Processing[{pubObjectHumanId}] AutoCompleteMode[{AutoCompleteMode}]"); + xmlBaselineReport = IshSession.Baseline25.CompleteBaselineByCandidateAndMode( + baselineId, + string.Empty, // extendBaselineId — none + EnumConverter.ToBaselineAutoCompleteModeSoap(AutoCompleteMode), + startLogicalIds, + startResourceLogicalIds, + languages, // languages[] + languages, // illustrationLanguages[] — same combination per publication output convention + languages, // resourceLanguages[] — same combination per publication output convention + resolutions); + } + else + { + xmlBaselineReport = IshSession.Baseline25.ExpandBaseline( + baselineId, + startLogicalIds, + startResourceLogicalIds, + languages, // languages[] + languages, // illustrationLanguages[] — same combination per publication output convention + languages, // resourceLanguages[] — same combination per publication output convention + resolutions); + } + + WriteDebug($"Processing[{pubObjectHumanId}] BaselineReport.length[{xmlBaselineReport?.Length ?? 0}]"); + + // --- Step 4: collect lngrefs from reportitems with reportresult="OK" --- + if (!string.IsNullOrEmpty(xmlBaselineReport)) + { + var reportDoc = new XmlDocument(); + reportDoc.LoadXml(xmlBaselineReport); + // Each contributes a language card ID + foreach (XmlElement reportItem in reportDoc.SelectNodes("//reportitem[@lngref]")) + { + string lngRefStr = reportItem.GetAttribute("lngref"); + if (long.TryParse(lngRefStr, out long lngRef)) + { + allLngRefs.Add(lngRef); + } + } + } + } + + WriteDebug($"Total lngRefs collected[{allLngRefs.Count}]"); + + if (allLngRefs.Count == 0) + { + WriteVerbose("returned object count[0]"); + return; + } + + // --- Step 5: deduplicate (same content object may appear for multiple pub outputs) --- + var distinctLngRefs = allLngRefs.Distinct().ToList(); + WriteDebug($"Distinct lngRefs[{distinctLngRefs.Count}]"); + + // --- Step 6: batch-hydrate into IshDocumentObj via RetrieveMetadataByIshLngRefs --- + Enumerations.ISHType[] docObjISHType = { + Enumerations.ISHType.ISHModule, + Enumerations.ISHType.ISHMasterDoc, + Enumerations.ISHType.ISHLibrary, + Enumerations.ISHType.ISHTemplate, + Enumerations.ISHType.ISHIllustration + }; + IshFields requestedMetadata = IshSession.IshTypeFieldSetup.ToIshRequestedMetadataFields( + IshSession.DefaultRequestedMetadata, + docObjISHType, + new IshFields(RequestedMetadata), + Enumerations.ActionMode.Read); + + var returnedObjects = new List(); + var batches = DivideListInBatches(distinctLngRefs, IshSession.MetadataBatchSize); + int batchCurrent = 0; + foreach (var batch in batches) + { + batchCurrent += batch.Count; + WriteDebug($"Retrieving DocumentObj batch lngRefs.length[{batch.Count}] {batchCurrent}/{distinctLngRefs.Count}"); + string xmlIshObjects = IshSession.DocumentObj25.RetrieveMetadataByIshLngRefs( + batch.ToArray(), + requestedMetadata.ToXml()); + var batchObjects = new IshObjects(docObjISHType, xmlIshObjects); + returnedObjects.AddRange(batchObjects.Objects); + } + + WriteVerbose("returned object count[" + returnedObjects.Count + "]"); + WriteObject(IshSession, docObjISHType, returnedObjects.ConvertAll(x => (IshBaseObject)x), true); + } + catch (TrisoftAutomationException trisoftAutomationException) + { + ThrowTerminatingError(new ErrorRecord(trisoftAutomationException, base.GetType().Name, ErrorCategory.InvalidOperation, null)); + } + catch (AggregateException aggregateException) + { + var flattenedAggregateException = aggregateException.Flatten(); + WriteWarning(flattenedAggregateException.ToString()); + ThrowTerminatingError(new ErrorRecord(flattenedAggregateException, base.GetType().Name, ErrorCategory.NotSpecified, null)); + } + catch (TimeoutException timeoutException) + { + WriteVerbose("TimeoutException Message[" + timeoutException.Message + "] StackTrace[" + timeoutException.StackTrace + "]"); + ThrowTerminatingError(new ErrorRecord(timeoutException, base.GetType().Name, ErrorCategory.OperationTimeout, null)); + } + catch (CommunicationException communicationException) + { + WriteVerbose("CommunicationException Message[" + communicationException.Message + "] StackTrace[" + communicationException.StackTrace + "]"); + ThrowTerminatingError(new ErrorRecord(communicationException, base.GetType().Name, ErrorCategory.OperationStopped, null)); + } + catch (Exception exception) + { + ThrowTerminatingError(new ErrorRecord(exception, base.GetType().Name, ErrorCategory.NotSpecified, null)); + } + finally + { + base.EndProcessing(); + } + } + } +} diff --git a/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/EnumConverter.cs b/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/EnumConverter.cs index 1e7f6cb..b5f27ad 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/EnumConverter.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/HelperClasses/EnumConverter.cs @@ -167,5 +167,33 @@ public static TResult ToIshType(Enumerations.ISHType ishType) return (TResult)Enum.Parse(typeof(TResult), ishType.ToString(), true); } + /// + /// Convert a BaselineAutoCompleteMode to a SOAP enumeration value. The SOAP enumeration prefixes every value with "ISH" (e.g. ISHFirstVersion). + /// + /// The BaselineAutoCompleteMode that needs to be converted. + /// A SOAP BaselineAutoCompleteMode enumeration value. + public static TResult ToBaselineAutoCompleteModeSoap(Enumerations.BaselineAutoCompleteMode autoCompleteMode) + { + if (!typeof(TResult).IsEnum) + { + throw new NotSupportedException("TResult must be an Enum"); + } + return (TResult)Enum.Parse(typeof(TResult), "ISH" + autoCompleteMode.ToString(), true); + } + + /// + /// Convert a BaselineAutoCompleteMode to a REST enumeration value. + /// + /// The BaselineAutoCompleteMode that needs to be converted. + /// A REST BaselineAutoCompleteMode enumeration value. + public static TResult ToBaselineAutoCompleteModeRest(Enumerations.BaselineAutoCompleteMode autoCompleteMode) + { + if (!typeof(TResult).IsEnum) + { + throw new NotSupportedException("TResult must be an Enum"); + } + return (TResult)Enum.Parse(typeof(TResult), autoCompleteMode.ToString(), true); + } + } } diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Objects/Enumerations.cs b/Source/ISHRemote/Trisoft.ISHRemote/Objects/Enumerations.cs index 48dab90..f7eeae6 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Objects/Enumerations.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Objects/Enumerations.cs @@ -516,6 +516,25 @@ public enum StatusFilter ISHNoStatusFilter } + /// + /// Enumeration indicating which auto complete mode must be used to fill gaps (missing versions) in a baseline. + /// + public enum BaselineAutoCompleteMode + { + /// + /// Fills gaps with the first available version of each missing object. + /// + FirstVersion, + /// + /// Fills gaps with the latest released version of each missing object. + /// + LatestReleased, + /// + /// Fills gaps with the latest available version (any status) of each missing object. + /// + LatestAvailable + } + /// /// BackgroundTask Status Filter ///