ibm-streams
Version:
IBM Streams Support for Visual Studio Code
1,479 lines (1,447 loc) • 95 kB
text/typescript
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