UNPKG

ibm-streams

Version:
1,479 lines (1,447 loc) 95 kB
import { Build, BuildImage, BuildToolkit, BuildType, CloudPakForDataJobType, CpdJob as CpdJobCommon, Editor, EditorAction, EditorSelector, generateRandomId, Instance, InstanceSelector, PostBuildAction, PrimitiveOperatorType, refreshCloudPakForDataInfo, Registry, SourceArchiveUtils, store, StreamsInstanceType, StreamsRest, StreamsUtils, SubmitJob, ToolkitUtils } from '@ibmstreams/common'; import * as fs from 'fs'; import * as glob from 'glob'; import _map from 'lodash/map'; import _omit from 'lodash/omit'; import _pick from 'lodash/pick'; import _some from 'lodash/some'; import * as os from 'os'; import * as path from 'path'; import { commands, ConfigurationChangeEvent, ExtensionContext, Uri, window, workspace, WorkspaceFolder } from 'vscode'; import * as packageJson from '../../package.json'; import { Commands } from '../commands'; import { CpdJob, Streams, StreamsInstance } from '../streams'; import { ActionType, Authentication, Configuration, DOC_BASE_URL, EXTENSION_ID, LANGUAGE_SPL, Logger, Settings, TOOLKITS_CACHE_DIR } from '../utils'; import { getStreamsExplorer } from '../views'; import LintHandler from './LintHandler'; import MessageHandler from './MessageHandler'; /** * Handles Streams builds and submissions */ export default class StreamsBuild { private static _context: ExtensionContext; public static _streamingAnalyticsCredentials: string; private static _toolkitPaths: string; private static _defaultMessageHandler: MessageHandler; /** * Perform initial configuration * @param context the extension context */ public static async configure(context: ExtensionContext): Promise<void> { this._context = context; this._defaultMessageHandler = Registry.getDefaultMessageHandler(); if (!fs.existsSync(TOOLKITS_CACHE_DIR)) { fs.mkdirSync(TOOLKITS_CACHE_DIR); } const toolkitPathsSetting = Configuration.getSetting( Settings.ENV_TOOLKIT_PATHS ); this._toolkitPaths = toolkitPathsSetting !== '' && toolkitPathsSetting !== Settings.ENV_TOOLKIT_PATHS_DEFAULT ? toolkitPathsSetting : null; const timeout = Configuration.getSetting(Settings.ENV_TIMEOUT_FOR_REQUESTS); StreamsRest.setRequestTimeout(timeout); this.monitorConfigSettingChanges(); await this.initReduxState(); this.monitorOpenSplFile(); } /** * Add a toolkit to the Streams build service * @param selectedPath The selected path */ public static async addToolkitToBuildService( selectedPath: string ): Promise<void> { const messageHandler = Registry.getDefaultMessageHandler(); messageHandler.logInfo( 'Received request to add a toolkit to the build service.' ); // Check for default instance if (!Streams.getInstances().length) { const notificationButtons = [ { label: 'Add Instance', callbackFn: (): void => { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(() => this.addToolkitToBuildService(selectedPath) ) }) ); StreamsInstance.authenticate(null, false, queuedActionId); } } ]; const message = 'There are no Streams instances available. Add an instance to continue with adding a toolkit.'; messageHandler.logInfo(message, { notificationButtons }); } else if (!Streams.getDefaultInstanceEnv()) { window .showWarningMessage( `A default Streams instance has not been set.`, 'Set Default' ) .then((selection: string) => { if (selection) { window .showQuickPick( Streams.getQuickPickItems(Streams.getInstances()), { canPickMany: false, ignoreFocusOut: true, placeHolder: 'Select a Streams instance to set as the default' } ) .then( async (item: any): Promise<void> => { if (item) { StreamsInstance.setDefaultInstance(item); return this.addToolkitToBuildService(selectedPath); } } ); } return null; }); } else { let defaultUri; if (selectedPath) { defaultUri = fs.lstatSync(selectedPath).isDirectory() ? Uri.file(selectedPath) : Uri.file(path.dirname(selectedPath)); } const selected = await window.showOpenDialog({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, ...(defaultUri && { defaultUri }), openLabel: 'Set as toolkit folder' }); const toolkitFolderUri = selected && selected.length ? selected[0] : null; if (toolkitFolderUri) { let toolkitFolderPath = toolkitFolderUri.path; if (os.platform() === 'win32' && toolkitFolderPath.charAt(0) === '/') { toolkitFolderPath = toolkitFolderPath.substring(1); } messageHandler.logInfo(`Selected: ${toolkitFolderPath}`); const defaultInstance = Streams.checkDefaultInstance(); if (!defaultInstance) { return; } this.runAddToolkitToBuildService(defaultInstance, toolkitFolderPath); } } } /** * Handle adding a toolkit to the Streams build service * @param targetInstance The target Streams instance * @param toolkitFolderPath The toolkit folder path */ public static async runAddToolkitToBuildService( targetInstance: any, toolkitFolderPath: string ): Promise<void> { const addToolkitToBuildService = (): void => { if (targetInstance) { const startAddAction = Build.addToolkitToBuildService({ folderPath: toolkitFolderPath, targetInstance }); if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(addToolkitToBuildService) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { store .dispatch(startAddAction) .then(async () => { // Refresh the toolkits setTimeout(async () => { await ToolkitUtils.refreshToolkits(targetInstance.connectionId); getStreamsExplorer().refreshToolkitsView(); }, 10000); }) .catch((err) => { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); const errorMsg = `Failed to add the toolkit in the ${toolkitFolderPath} folder to the build service using the Streams instance ${instanceName}.`; this.handleError( err, Registry.getDefaultMessageHandler(), errorMsg ); }); } } }; addToolkitToBuildService(); } /** * Remove a toolkit from the Streams build service * @param selectedPath The selected path */ public static async removeToolkitsFromBuildService(): Promise<void> { const messageHandler = Registry.getDefaultMessageHandler(); messageHandler.logInfo( 'Received request to remove toolkit(s) from the Streams build service.' ); // Check for default instance if (!Streams.getInstances().length) { const notificationButtons = [ { label: 'Add Instance', callbackFn: (): void => { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(() => this.removeToolkitsFromBuildService() ) }) ); StreamsInstance.authenticate(null, false, queuedActionId); } } ]; const message = 'There are no Streams instances available. Add an instance to continue with removing a toolkit.'; messageHandler.logInfo(message, { notificationButtons }); } else if (!Streams.getDefaultInstanceEnv()) { window .showWarningMessage( `A default Streams instance has not been set.`, 'Set Default' ) .then((selection: string) => { if (selection) { window .showQuickPick( Streams.getQuickPickItems(Streams.getInstances()), { canPickMany: false, ignoreFocusOut: true, placeHolder: 'Select a Streams instance to set as the default' } ) .then( async (item: any): Promise<void> => { if (item) { StreamsInstance.setDefaultInstance(item); return this.removeToolkitsFromBuildService(); } } ); } return null; }); } else { const defaultInstance = Streams.checkDefaultInstance(); if (!defaultInstance) { return; } this.runRemoveToolkitsFromBuildService(defaultInstance); } } /** * Handle removing a toolkit from the Streams build service * @param targetInstance The target Streams instance */ public static async runRemoveToolkitsFromBuildService( targetInstance: any ): Promise<void> { const removeToolkitsFromBuildService = (): void => { if (targetInstance) { if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(removeToolkitsFromBuildService) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); const toolkits = InstanceSelector.selectBuildServiceToolkits( store.getState(), targetInstance.connectionId ); const userToolkitItems = toolkits .filter((toolkit) => toolkit.id.startsWith('streams-toolkits/')) .map((toolkit) => ({ label: toolkit.name, description: toolkit.version, toolkit })); if (userToolkitItems.length > 0) { window .showQuickPick(userToolkitItems, { canPickMany: true, ignoreFocusOut: true, placeHolder: 'Select one or more Streams build service toolkits to remove' }) .then( async (items: Array<any>): Promise<void> => { if (items) { const toolkits = items.map((item) => item.toolkit); const startRemoveAction = Build.removeToolkitsFromBuildService( { removedToolkits: toolkits, targetInstance } ); store .dispatch(startRemoveAction) .then(async () => { // Refresh the toolkits setTimeout(async () => { await ToolkitUtils.refreshToolkits( targetInstance.connectionId ); getStreamsExplorer().refreshToolkitsView(); }, 10000); }) .catch((err) => { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); const errorMsg = `Failed to remove the toolkit(s) from the build service using the Streams instance ${instanceName}.`; this.handleError( err, Registry.getDefaultMessageHandler(), errorMsg ); }); } } ); } else { this._defaultMessageHandler.logInfo( `There are no build service toolkits available to remove from the Streams instance ${instanceName}.` ); } } } }; removeToolkitsFromBuildService(); } /** * Perform a build of an SPL file and either download the bundle or submit the application * @param filePath the path to the SPL file * @param action the post-build action to take */ public static async buildApp( filePath: string, action: PostBuildAction ): Promise<void> { await this.checkIfDirty(); Streams.checkDefaultInstance(); if (filePath) { const filePaths = [filePath]; const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( action, (instance: any) => { this.runBuildApp(instance, filePaths, action); }, () => { this.buildApp(filePath, action); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel(ActionType.BuildApp, filePaths, action); }; this.handleAction( ActionType.BuildApp, action, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( 'The build failed. Unable to retrieve the application file path.', { showNotification: true } ); } } /** * Handle building of an application * @param targetInstance the target Streams instance * @param filePaths the selected file paths * @param action the post-build action to take */ public static async runBuildApp( targetInstance: any, filePaths: string[], action: PostBuildAction ): Promise<void> { const { appRoot, compositeToBuild, messageHandlerId, messageHandler } = await this.initBuild('buildApp', filePaths[0], action); action = action === PostBuildAction.Submit && Streams.doesInstanceHaveCpdSpacesSupport(targetInstance) ? PostBuildAction.SubmitCpd : action; const buildApp = (): void => { if (targetInstance) { const startBuildAction = Build.startBuild({ messageHandlerId, appRoot, fqn: compositeToBuild, makefilePath: null, postBuildAction: action, targetInstance }); if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(buildApp) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { messageHandler.logInfo( `Selected Streams instance: ${InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId )}.` ); store .dispatch(startBuildAction) .then((buildResults) => { if (action === PostBuildAction.Submit) { setTimeout(() => StreamsInstance.refreshInstances(), 2000); return; } if (action === PostBuildAction.SubmitCpd) { if (buildResults && buildResults.length) { StreamsBuild.runSubmit(targetInstance, buildResults, [ messageHandlerId ]); } return; } if ( action === PostBuildAction.BuildImage && buildResults && buildResults.length ) { buildResults.forEach( ({ bundlePath, artifact }: { bundlePath: string; artifact: any; }) => { const newMessageHandler = bundlePath ? Registry.getMessageHandler(bundlePath) : messageHandler; if (newMessageHandler && artifact) { this.displayEdgeAppImageBuildDetails( newMessageHandler, artifact, targetInstance.connectionId ); } } ); } }) .catch((err) => { if (action === PostBuildAction.Submit) { setTimeout(() => StreamsInstance.refreshInstances(), 2000); } let errorMsg: string; if ( err && err.message && err.message.startsWith('Failed to build') ) { errorMsg = err.message; } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); let type = ''; if (action === PostBuildAction.Submit) { type = ' and submit'; } else if (action === PostBuildAction.BuildImage) { type = ' an image for'; } errorMsg = `Failed to build${type} the application ${compositeToBuild} using the Streams instance ${instanceName}.`; } this.handleError(err, messageHandler, errorMsg); }); } } }; buildApp(); } /** * Perform a build from a Makefile and either download the bundle(s) or submit the application(s) * @param filePath the path to the Makefile * @param action the post-build action to take */ public static async buildMake( filePath: string, action: PostBuildAction ): Promise<void> { await this.checkIfDirty(); Streams.checkDefaultInstance(); if (filePath) { const filePaths = [filePath]; const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( action, (instance: any) => { this.runBuildMake(instance, filePaths, action); }, () => { this.buildMake(filePath, action); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel(ActionType.BuildMake, filePaths, action); }; this.handleAction( ActionType.BuildMake, action, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( 'The build failed. Unable to retrieve the Makefile file path.', { showNotification: true } ); } } /** * Handle building of a Makefile * @param targetInstance the target Streams instance * @param filePaths the selected file paths * @param action the post-build action to take */ public static async runBuildMake( targetInstance: any, filePaths: string[], action: PostBuildAction ): Promise<void> { const { appRoot, messageHandlerId, messageHandler } = await StreamsBuild.initBuild('buildMake', filePaths[0], action); action = action === PostBuildAction.Submit && Streams.doesInstanceHaveCpdSpacesSupport(targetInstance) ? PostBuildAction.SubmitCpd : action; const buildMake = (): void => { if (targetInstance) { const startBuildAction = Build.startBuild({ messageHandlerId, appRoot, fqn: null, makefilePath: filePaths[0], postBuildAction: action, targetInstance }); if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(buildMake) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { messageHandler.logInfo( `Selected Streams instance: ${InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId )}.` ); store .dispatch(startBuildAction) .then((buildResults) => { if (action === PostBuildAction.Submit) { setTimeout(() => StreamsInstance.refreshInstances(), 2000); return; } if (action === PostBuildAction.SubmitCpd) { if (buildResults && buildResults.length) { StreamsBuild.runSubmit(targetInstance, buildResults, [ messageHandlerId ]); } return; } if ( action === PostBuildAction.BuildImage && buildResults && buildResults.length ) { buildResults.forEach( ({ bundlePath, artifact }: { bundlePath: string; artifact: any; }) => { const newMessageHandler = bundlePath ? Registry.getMessageHandler(bundlePath) : messageHandler; if (newMessageHandler && artifact) { this.displayEdgeAppImageBuildDetails( newMessageHandler, artifact, targetInstance.connectionId ); } } ); } }) .catch((err) => { if (action === PostBuildAction.Submit) { setTimeout(() => StreamsInstance.refreshInstances(), 2000); } let errorMsg: string; if ( err && err.message && err.message.startsWith('Failed to build') ) { errorMsg = err.message; } else { const identifier = `${path.basename(appRoot)}${ path.sep }${path.relative(appRoot, filePaths[0])}`; const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); let type = ''; if (action === PostBuildAction.Submit) { type = ' and submit'; } else if (action === PostBuildAction.BuildImage) { type = ' an image for'; } errorMsg = `Failed to build${type} the application(s) in ${identifier} using the Streams instance ${instanceName}.`; } this.handleError(err, messageHandler, errorMsg); }); } } }; buildMake(); } /** * Submit application bundle(s) * @param filePaths the paths to the application bundle(s) */ public static async submit(filePaths: string[]): Promise<void> { if (filePaths) { Streams.checkDefaultInstance(); const { messageHandlerIds, bundleFilePaths } = this.initSubmit(filePaths); if (!bundleFilePaths.length) { this._defaultMessageHandler.logInfo( 'There are no Streams application bundles to submit.' ); return; } const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( ActionType.Submit, (instance: any) => { this.runSubmit(instance, bundleFilePaths, messageHandlerIds); }, () => { this.submit(filePaths); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel(ActionType.Submit, bundleFilePaths, null, { messageHandlerIds }); }; this.handleAction( ActionType.Submit, null, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( 'The submission(s) failed. Unable to retrieve the application bundle file paths.', { showNotification: true } ); } } /** * Handle submission of application bundles * @param targetInstance the target Streams instance * @param filePaths the selected file paths * @param messageHandlerIds the message handler identifiers */ public static runSubmit( targetInstance: any, bundleFilePaths: string[], messageHandlerIds: string[] ): void { const submit = async (): Promise<void> => { if (targetInstance) { if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(submit) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { bundleFilePaths.forEach((bundleFilePath, index) => { const messageHandler = messageHandlerIds ? Registry.getMessageHandler(messageHandlerIds[index]) : Registry.getMessageHandler(bundleFilePath); if (messageHandler) { messageHandler.logInfo( `Selected Streams instance: ${InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId )}.` ); } }); // Check if the instance supports CPD spaces if (Streams.doesInstanceHaveCpdSpacesSupport(targetInstance)) { try { await Promise.all( bundleFilePaths.map(async (bundleFilePath, index) => { // Submit a Cloud Pak for Data Streams job await CpdJob.submitJob( targetInstance, bundleFilePath, messageHandlerIds ? messageHandlerIds[index] : null ); }) ); } catch (err) { CpdJob.handleError(err); } } else { const startSubmitJobAction = SubmitJob.startSubmitJobFromApplicationBundles( bundleFilePaths, targetInstance, messageHandlerIds ); store .dispatch(startSubmitJobAction) .then(() => { setTimeout(() => StreamsInstance.refreshInstances(), 2000); }) .catch((err) => { setTimeout(() => StreamsInstance.refreshInstances(), 2000); const getErrorMsg = (bundleFilePath: string): string => { let errorMsg: string; if ( err && err.message && err.message.startsWith('Failed to submit') ) { errorMsg = err.message; } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); errorMsg = `Failed to submit the application ${path.basename( bundleFilePath )} to the Streams instance ${instanceName}.`; } return errorMsg; }; bundleFilePaths.forEach((bundleFilePath) => { const messageHandler = Registry.getMessageHandler( bundleFilePath ); if (messageHandler) { this.handleError( err, messageHandler, getErrorMsg(bundleFilePath) ); } }); }); } } } }; submit(); } /** * Upload application bundle * @param bundleFilePath the path to the application bundle */ public static async uploadApplicationBundle( bundleFilePath: string ): Promise<void> { if (bundleFilePath) { Streams.checkDefaultInstance(); const statusMessage = 'Received request to upload an application bundle.'; this._defaultMessageHandler.logInfo(statusMessage); this._defaultMessageHandler.logInfo(`Selected: ${bundleFilePath}`); const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( ActionType.UploadBundleCpd, (instance: any) => { this.runUploadApplicationBundle(instance, bundleFilePath); }, () => { this.uploadApplicationBundle(bundleFilePath); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel( ActionType.UploadBundleCpd, [bundleFilePath], null ); }; this.handleAction( ActionType.UploadBundleCpd, null, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( 'The upload failed. Unable to retrieve the application bundle file path.', { showNotification: true } ); } } /** * Handle submission of application bundles * @param targetInstance the target Streams instance * @param bundleFilePath the path to the application bundle */ public static runUploadApplicationBundle( targetInstance: any, bundleFilePath: string ): void { const upload = async (): Promise<void> => { if (targetInstance) { if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(upload) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); const instanceNamespace = InstanceSelector.selectCloudPakForDataServiceInstanceNamespace( store.getState(), targetInstance.connectionId ); this._defaultMessageHandler.logInfo( `Selected Streams instance: ${instanceName}.` ); // Prompt user to select a job const cpdJobItems = []; const cpdSpaces = InstanceSelector.selectCloudPakForDataSpacesOrProjects( store.getState(), targetInstance.connectionId, CloudPakForDataJobType.Space ); const multipleSpaces = cpdSpaces.length > 1; cpdSpaces.forEach((space) => { const { entity: { name: spaceName }, jobs } = space; const cpdJobItemsForSpace = jobs .filter((job) => { return ( job.metadata.name !== `${instanceName}.${instanceNamespace}` ); }) .map((job) => ({ label: job.metadata.name, ...(multipleSpaces && { description: spaceName }), job, space })); cpdJobItems.push(...cpdJobItemsForSpace); }); if (!cpdJobItems.length) { return this._defaultMessageHandler.logInfo( `There are no jobs available for application bundle uploads in the Streams instance ${instanceName}.`, { notificationButtons: [ { label: 'Create Job', callbackFn: async (): Promise<void> => { try { await CpdJob.submitJob(targetInstance); } catch (err) { CpdJob.handleError(err); } } } ] } ); } const selectedItem = await window.showQuickPick(cpdJobItems, { canPickMany: false, ignoreFocusOut: true, placeHolder: 'Select an IBM Cloud Pak for Data job definition to upload the application bundle to' }); if (selectedItem) { const { label, job, space } = selectedItem; try { await store.dispatch( CpdJobCommon.updateCloudPakForDataJob( targetInstance.connectionId, CloudPakForDataJobType.Space, space.metadata.id, job.metadata.asset_id, label, null, null, bundleFilePath, null ) ); await new Promise((resolve) => { setTimeout(async () => { await store.dispatch( refreshCloudPakForDataInfo(targetInstance.connectionId) ); getStreamsExplorer().refresh(); resolve(null); }, 1000); }); } catch (err) { CpdJob.handleError(err); } } } } }; upload(); } /** * Perform build(s) of edge application image(s) * @param filePaths the paths to the application bundle(s) */ public static async buildImage(filePaths: string[]): Promise<void> { if (filePaths) { const { messageHandlerIds, bundleFilePaths } = this.initBuildImage( filePaths ); if (!bundleFilePaths.length) { this._defaultMessageHandler.logInfo( 'There are no Streams application bundles to build.' ); return; } const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( ActionType.BuildImage, (instance: any) => { this.runBuildImage( instance, bundleFilePaths, null, messageHandlerIds ); }, () => { this.buildImage(filePaths); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel(ActionType.BuildImage, bundleFilePaths, null, { messageHandlerIds }); }; this.handleAction( ActionType.BuildImage, null, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( 'The build(s) failed. Unable to retrieve the application bundle file paths.', { showNotification: true } ); } } /** * Handle building of an edge application image * @param targetInstance the target Streams instance * @param filePaths the selected file paths * @param baseImage the base image * @param messageHandlerIds the message handler identifiers */ public static runBuildImage( targetInstance: any, bundleFilePaths: string[], baseImage = null, messageHandlerIds: string[] ): void { const buildImage = (): void => { if (targetInstance) { const startBuildImageAction = BuildImage.startBuildImageFromApplicationBundles( messageHandlerIds, bundleFilePaths, null, baseImage, targetInstance ); if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(buildImage) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { bundleFilePaths.forEach((bundleFilePath) => { const messageHandler = Registry.getMessageHandler(bundleFilePath); if (messageHandler) { messageHandler.logInfo( `Selected Streams instance: ${InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId )}.` ); } }); store .dispatch(startBuildImageAction) .then((buildResults) => { if (buildResults && buildResults.length) { buildResults.forEach( ({ bundlePath, artifact }: { bundlePath: string; artifact: any; }) => { const messageHandler = Registry.getMessageHandler( bundlePath ); if (messageHandler && artifact) { this.displayEdgeAppImageBuildDetails( messageHandler, artifact, targetInstance.connectionId ); } } ); } }) .catch((err) => { const getErrorMsg = (bundleFilePath: string): string => { let errorMsg: string; if ( err && err.message && err.message.startsWith('Failed to build an image') ) { errorMsg = err.message; } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); errorMsg = `Failed to build an image for the application ${path.basename( bundleFilePath )} using the Streams instance ${instanceName}.`; } return errorMsg; }; bundleFilePaths.forEach((bundleFilePath) => { const messageHandler = Registry.getMessageHandler( bundleFilePath ); if (messageHandler) { this.handleError( err, messageHandler, getErrorMsg(bundleFilePath) ); } }); }); } } }; buildImage(); } /** * Perform a toolkit build * @param folderOrFilePath the path to the folder, `toolkit.xml`, or `info.xml` file */ public static async buildToolkit(folderOrFilePath: string): Promise<void> { await this.checkIfDirty(); if (folderOrFilePath) { const { toolkitFolderPath, messageHandlerId, messageHandler } = this.initBuildToolkit(folderOrFilePath); if (!toolkitFolderPath || !messageHandler) { this._defaultMessageHandler.logError( 'The toolkit build failed. Unable to retrieve the toolkit folder path.', { showNotification: true } ); return; } const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( ActionType.BuildToolkit, (instance: any) => { this.runBuildToolkit(instance, toolkitFolderPath, messageHandlerId); }, () => { this.buildToolkit(folderOrFilePath); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel( ActionType.BuildToolkit, [toolkitFolderPath], null, { messageHandlerId } ); }; this.handleAction( ActionType.BuildToolkit, null, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( 'The toolkit build failed. Unable to retrieve the folder or file path.', { showNotification: true } ); } } /** * Handle building a toolkit * @param targetInstance the target Streams instance * @param toolkitFolderPath the toolkit folder path * @param messageHandlerId the message handler identifier */ public static runBuildToolkit( targetInstance: any, toolkitFolderPath: string, messageHandlerId: string ): void { const buildToolkit = (): void => { if (targetInstance) { const startBuildToolkitAction = BuildToolkit.startBuildToolkit({ messageHandlerId, folderPath: toolkitFolderPath, type: BuildType.Toolkit, targetInstance }); if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(buildToolkit) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { const messageHandler = Registry.getMessageHandler(toolkitFolderPath); messageHandler.logInfo( `Selected Streams instance: ${InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId )}.` ); store.dispatch(startBuildToolkitAction).catch((err) => { let errorMsg: string; let learnMoreUrl = null; let buttons = null; if ( err && err.message && err.message.startsWith('Failed to build a toolkit') ) { errorMsg = err.message; if (errorMsg.includes('CDISB5078E')) { errorMsg += ' The toolkit folder must contain a Makefile in order to build.'; learnMoreUrl = `${DOC_BASE_URL}/docs/developing-toolkits/#building-a-toolkit`; buttons = [ { label: 'Learn More', callbackFn: (): void => { Registry.openUrl(learnMoreUrl); } } ]; } } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); errorMsg = `Failed to build a toolkit using the Streams instance ${instanceName}.`; } this.handleError(err, messageHandler, errorMsg, buttons); if (learnMoreUrl) { messageHandler.logError(`Learn more: ${learnMoreUrl}.`); } }); } } }; buildToolkit(); } /** * Perform a primitive operator build * @param operatorType the primitive operator type * @param folderOrFilePath the path to the selected folder or file */ public static async buildPrimitiveOperator( operatorType: PrimitiveOperatorType, folderOrFilePath: string ): Promise<void> { await this.checkIfDirty(); if (folderOrFilePath) { const { primitiveOperatorFolderPath, messageHandlerId, messageHandler, error } = this.initBuildPrimitiveOperator(operatorType, folderOrFilePath); if (error) { this._defaultMessageHandler.logError( `The ${operatorType} primitive operator build failed. ${error.errorMsg}`, { showNotification: true, notificationButtons: error.buttons } ); return; } if (!primitiveOperatorFolderPath || !messageHandler) { this._defaultMessageHandler.logError( `The ${operatorType} primitive operator build failed. Unable to retrieve the ${operatorType} primitive operator folder path.`, { showNotification: true } ); return; } const zeroOrOneInstancesCallbackFn = this.getZeroOrOneInstancesCallbackFn( ActionType.BuildPrimitiveOperator, (instance: any) => { this.runBuildPrimitiveOperator( instance, operatorType, primitiveOperatorFolderPath, messageHandlerId ); }, () => { this.buildPrimitiveOperator(operatorType, folderOrFilePath); } ); const multipleInstancesCallbackFn = (): void => { this.showInstancePanel( ActionType.BuildPrimitiveOperator, [primitiveOperatorFolderPath, messageHandlerId], null, { messageHandlerId, operatorType } ); }; this.handleAction( ActionType.BuildPrimitiveOperator, null, zeroOrOneInstancesCallbackFn, multipleInstancesCallbackFn ); } else { this._defaultMessageHandler.logError( `The ${operatorType} primitive operator build failed. Unable to retrieve the folder or file path.`, { showNotification: true } ); } } /** * Handle building a primitive operator * @param targetInstance the target Streams instance * @param operatorType the primitive operator type * @param primitiveOperatorFolderPath the primitive operator folder path * @param messageHandlerId the message handler identifier */ public static runBuildPrimitiveOperator( targetInstance: any, operatorType: PrimitiveOperatorType, primitiveOperatorFolderPath: string, messageHandlerId: string ): void { const buildPrimitiveOperator = (): void => { if (targetInstance) { const startBuildPrimitiveOperatorAction = BuildToolkit.startBuildToolkit( { messageHandlerId, folderPath: primitiveOperatorFolderPath, type: BuildType.PrimitiveOperator, buildPrimitiveOperatorArgs: { operatorType }, targetInstance } ); if (!Authentication.isAuthenticated(targetInstance)) { const queuedActionId = generateRandomId('queuedAction'); store.dispatch( EditorAction.addQueuedAction({ id: queuedActionId, action: Editor.executeCallbackFn(buildPrimitiveOperator) }) ); StreamsInstance.authenticate(targetInstance, false, queuedActionId); } else { const messageHandler = Registry.getMessageHandler( primitiveOperatorFolderPath ); messageHandler.logInfo( `Selected Streams instance: ${InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId )}.` ); store.dispatch(startBuildPrimitiveOperatorAction).catch((err) => { let errorMsg: string; if ( err && err.message && err.message.startsWith( `Failed to build a ${operatorType} primitive operator` ) ) { errorMsg = err.message; } else { const instanceName = InstanceSelector.selectInstanceName( store.getState(), targetInstance.connectionId ); errorMsg = `Failed to build a ${operatorType} primitive operator using the Streams instance ${instanceName}.`; } this.handleError(err, messageHandler, errorMsg); }); } } }; buildPrimitiveOperator(); } /** * Make a C++ primitive operator * @param projectFolderPath the path to the project folder * @param operatorFolderPath the path to the operator folder * @param genericOperator whether or not should be a generic operator * @param resolve the promise resolve function */ public static async makeCppPrimitiveOperator( projectFolderPath: string, operatorFolderPath: string, genericOperator: boolean, resolve: Function ): Promise<void> { await this.checkIfDirty(); let messageHandler: MessageHandler; if (projectFolderPath && operatorFolderPath) { const result = this.initMakeCppPrimitiveOperator( projectFolderPath, genericOperator ); ({ messageHandler } = result); const { messageHandlerId } = result; if (result.error) { messageHandler.logError( `The C++ primitive operator cre