diff --git a/src/cli/journey/journey-describe.ts b/src/cli/journey/journey-describe.ts index 83cdc1fd6..775f7df9e 100644 --- a/src/cli/journey/journey-describe.ts +++ b/src/cli/journey/journey-describe.ts @@ -43,6 +43,10 @@ export default function setup() { "Override version. Notation: '..' e.g. '7.2.0'. Override detected version with any version. This is helpful in order to check if journeys in one environment would be compatible running in another environment (e.g. in preparation of migrating from on-prem to ForgeRock Identity Cloud." ) ) + .addOption( + new Option( + '-u, --usage', + 'List all uses of the journey.')) .action( // implement command logic inside action handler async (host, realm, user, password, options, command) => { @@ -119,7 +123,8 @@ export default function setup() { if (!options.markdown) { const outcome = await describeJourney( journeyData, - createFileParamTreeExportResolver(options.file) + createFileParamTreeExportResolver(options.file), + options.usage ); if (!outcome) process.exitCode = 1; } @@ -154,7 +159,7 @@ export default function setup() { const treeData = await exportJourney(journey['_id']); // ANSI text output if (!options.markdown) { - const outcome = await describeJourney(treeData); + const outcome = await describeJourney(treeData, undefined, options.usage); if (!outcome) process.exitCode = 1; } // Markdown output @@ -175,7 +180,7 @@ export default function setup() { const treeData = await exportJourney(options.journeyId); // ANSI text output if (!options.markdown) { - const outcome = await describeJourney(treeData); + const outcome = await describeJourney(treeData, undefined, options.usage); if (!outcome) process.exitCode = 1; } // Markdown output diff --git a/src/ops/JourneyOps.ts b/src/ops/JourneyOps.ts index d416a2c04..e953c2521 100644 --- a/src/ops/JourneyOps.ts +++ b/src/ops/JourneyOps.ts @@ -14,6 +14,7 @@ import { import fs from 'fs'; import { + createKeyValueTable, createProgressIndicator, createTable, debugMessage, @@ -21,6 +22,7 @@ import { printMessage, stopProgressIndicator, updateProgressIndicator, + stopAllProgressBars, } from '../utils/Console'; import * as CirclesOfTrust from './CirclesOfTrustOps'; import * as EmailTemplate from './EmailTemplateOps'; @@ -31,6 +33,7 @@ import * as Script from './ScriptOps'; import * as Theme from './ThemeOps'; import { cloneDeep, errorHandler } from './utils/OpsUtils'; import wordwrap from './utils/Wordwrap'; +import { getFullExportConfig } from '../utils/Config'; const { getTypedFilename, @@ -704,7 +707,8 @@ function describeTreeDescendentsMd( */ export async function describeJourney( journeyData: SingleTreeExportInterface, - resolveTreeExport: TreeExportResolverInterface = onlineTreeExportResolver + resolveTreeExport: TreeExportResolverInterface = onlineTreeExportResolver, + usage = false ): Promise { const errors: Error[] = []; try { @@ -766,6 +770,35 @@ export async function describeJourney( 'data' ); } + // Usage + if (usage) { + try { + const journeysExport = await exportJourneys({ + useStringArrays: true, + deps: false, + coords: false, + }); + let journey = journeyData as SingleTreeExportInterface & { + locations: string[]; + }; + const journeyName = + typeof journey.tree === 'string' ? journey.tree : journey.tree?._id; + journey.locations = getJourneyLocations(journeysExport, journeyName); + const table = createKeyValueTable(); + table.push([ + `Usage Locations (${journey.locations.length} total)`['brightCyan'], + journey.locations.length > 0 ? journey.locations[0] : '', + ]); + for (let i = 1; i < journey.locations.length; i++) { + table.push(['', journey.locations[i]]); + } + stopAllProgressBars(); + printMessage(table.toString(), 'data'); + return true; + } catch (error) { + return false; + } + } // Dependency Tree try { @@ -1212,3 +1245,39 @@ export async function deleteJourneys( } return false; } + +/** + * Helper that finds all locations where a journey is being used as an inner journey in another journey + * @param journeysExport export data containing journeys + * @param journeyName ID of the journey to search for as an inner journey + */ +function getJourneyLocations( + journeysExport: MultiTreeExportInterface, + journeyName: string +): string[] { + const locations: string[] = []; + for (const journeyData of Object.values(journeysExport.trees)) { + interface InnerTreeNode { + _id: string; + _type?: { _id?: string }; + tree?: string | { _id: string }; + } + + for (const node of Object.values( + journeyData.nodes ?? {} + ) as InnerTreeNode[]) { + const innerTreeName = + typeof node.tree === 'string' ? node.tree : node.tree?._id; + + if ( + node._type?._id === 'InnerTreeEvaluatorNode' && + innerTreeName === journeyName + ) { + locations.push( + `journey.${journeyData.tree?._id ?? journeyData.tree._id}` + ); + } + } + } + return locations; +} diff --git a/src/ops/ScriptOps.ts b/src/ops/ScriptOps.ts index 7ac1f1922..1cbb32c79 100644 --- a/src/ops/ScriptOps.ts +++ b/src/ops/ScriptOps.ts @@ -28,6 +28,7 @@ import { stopProgressIndicator, succeedSpinner, updateProgressIndicator, + stopAllProgressBars, } from '../utils/Console'; import { errorHandler } from './utils/OpsUtils'; import wordwrap from './utils/Wordwrap'; @@ -252,6 +253,7 @@ export async function describeScript( table.push(['', script.locations[i]]); } } + stopAllProgressBars(); printMessage(table.toString(), 'data'); } return true; diff --git a/src/utils/Console.ts b/src/utils/Console.ts index e00d0aa82..07fed0b49 100644 --- a/src/utils/Console.ts +++ b/src/utils/Console.ts @@ -330,6 +330,13 @@ function stopProgressBar(id: string, message: string = null) { ); } +export function stopAllProgressBars() { + if (multiBarContainer) { + multiBarContainer.stop(); + multiBarContainer = null; + } +} + /** * Clean-up progress bars */