From ddef9d94d114c4e6da9658ef9063c87c970c6fdc Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Mon, 10 Dec 2018 11:04:15 -0500 Subject: [PATCH 01/12] add originator param for build service debug --- lib/spl-build-common.js | 17 ++++++++++++----- lib/spl-build.js | 6 ++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 773ec7f..51f79ff 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -44,18 +44,22 @@ export class SplBuilder { openUrlHandler = null; serviceCredentials = null; accessToken = null; + originatorString = null; - constructor(messageHandler, lintHandler, openUrlHandler) { + constructor(messageHandler, lintHandler, openUrlHandler, originator) { this.messageHandler = messageHandler; this.lintHandler = lintHandler; this.openUrlHandler = openUrlHandler; + this.originatorString = originator ? `${originator.originator}-${originator.version}:${originator.type}` : ""; } dispose() { - messageHandler = null; - lintHandler = null; - serviceCredentials = null; - accessToken = null; + // this.messageHandler = null; + // this.lintHandler = null; + // this.openUrlHandler = null; + // this.serviceCredentials = null; + // this.accessToken = null; + // this.originatorString = null; } /** @@ -504,6 +508,9 @@ export class SplBuilder { method: "POST", url: `${this.serviceCredentials.v2_rest_url}/builds`, json: true, + qs: { + originator: this.originatorString + }, headers: { "Authorization": `Bearer ${this.accessToken}`, "Content-Type": "application/json" diff --git a/lib/spl-build.js b/lib/spl-build.js index 3e87b62..0553e05 100644 --- a/lib/spl-build.js +++ b/lib/spl-build.js @@ -15,6 +15,8 @@ import { LintHandler } from "./LintHandler"; import { SplBuilder } from "./spl-build-common"; import { MainCompositePickerView } from "./views/MainCompositePickerView"; +import { version } from "../package.json"; + const CONF_TOOLKITS_PATH = "ide-ibmstreams.toolkitsPath"; const CONF_STREAMING_ANALYTICS_CREDENTIALS = "build-ibmstreams.streamingAnalyticsCredentials"; @@ -218,7 +220,7 @@ export default { this.subscriptions.add(this.consoleService); this.messageHandler = new MessageHandler(this.consoleService); this.lintHandler = new LintHandler(this.linterService, SplBuilder.SPL_MSG_REGEX, this.appRoot); - this.splBuilder = new SplBuilder(this.messageHandler, this.lintHandler, this.openUrlHandler); + this.splBuilder = new SplBuilder(this.messageHandler, this.lintHandler, this.openUrlHandler, {originator: "atom", version: version, type: "make"}); atom.workspace.open("atom://nuclide/console"); @@ -267,7 +269,7 @@ export default { this.consoleService = this.consumeConsoleService({id: fqn, name: fqn}); this.messageHandler = new MessageHandler(this.consoleService); this.lintHandler = new LintHandler(this.linterService, SplBuilder.SPL_MSG_REGEX, this.appRoot); - this.splBuilder = new SplBuilder(this.messageHandler, this.lintHandler, this.openUrlHandler); + this.splBuilder = new SplBuilder(this.messageHandler, this.lintHandler, this.openUrlHandler, {originator: "atom", version: version, type: "spl"}); atom.workspace.open("atom://nuclide/console"); From 530c986fd374abb298e20d39f9f4e135b9f734a8 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Tue, 11 Dec 2018 13:24:46 -0500 Subject: [PATCH 02/12] comment cleanup --- lib/spl-build-common.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 51f79ff..851b67d 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -54,12 +54,6 @@ export class SplBuilder { } dispose() { - // this.messageHandler = null; - // this.lintHandler = null; - // this.openUrlHandler = null; - // this.serviceCredentials = null; - // this.accessToken = null; - // this.originatorString = null; } /** From b2ffbd6a65137f4419d8865a3a502f5ef46f990a Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Wed, 19 Dec 2018 13:25:53 -0500 Subject: [PATCH 03/12] #27 If service not started, a button is shown to start it and retry build/submit. #21 Messages now indicate which build they refer to. #22 MacOS only issue; switched to using Atom notifications instead of dialog for submit. --- lib/MessageHandler.js | 218 ++++++++++++++------------- lib/spl-build-common.js | 318 +++++++++++++++++++++++++++++----------- 2 files changed, 351 insertions(+), 185 deletions(-) diff --git a/lib/MessageHandler.js b/lib/MessageHandler.js index 39445ba..346e111 100644 --- a/lib/MessageHandler.js +++ b/lib/MessageHandler.js @@ -15,113 +15,105 @@ export class MessageHandler { this.consoleService = service; } - handleBuildProgressMessage(messageOutput: Array | string, showNotification?: boolean) { - if (Array.isArray(messageOutput)) { - const message = this.getLoggableMessage(messageOutput); - if (message) { - this.consoleService.log(message); - if (showNotification) { - atom.notifications.addInfo("building...", {detail: message}); - } - } - } else if (typeof(messageOutput) === "string") { - this.consoleService.log(messageOutput); - if (showNotification) { - atom.notifications.addInfo(messageOutput, {}); - } + handleInfoMessage( + message, + { + detail = null, + description = null, + showNotification = true, + showConsoleMessage = true, + notificationAutoDismiss = true, + notificationButtons = [] + } = {} + ) { + const addedButtons = this.processButtons(notificationButtons); + const detailMessage = this.joinMessageArray(detail); + + if (showConsoleMessage) { + this.consoleService.log(`${message}${detailMessage ? "\n"+detailMessage: ""}`); } - } - - handleBuildSuccess(messageOutput: Array) { - const message = this.getLoggableMessage(messageOutput); - if (message) { - this.consoleService.success(message); - atom.notifications.addSuccess("Build succeeded", {detail: message, dismissable: true}); - } else { - this.consoleService.success("Build succeeded"); - atom.notifications.addSuccess("Build succeeded", {dismissable: true}); + if (showNotification && typeof(message) === "string") { + const notificationOptions = { + ...addedButtons, + dismissable: !notificationAutoDismiss, + detail: detailMessage ? detailMessage : "", + description: description ? description : "" + }; + return atom.notifications.addInfo(message, notificationOptions); } } - handleBuildFailure(messageOutput: Array) { - const message = this.getLoggableMessage(messageOutput); - if (message) { + handleError( + message, + { + detail, + description, + stack, + showNotification = true, + showConsoleMessage = true, + consoleErrorLog = true, + notificationAutoDismiss = false, + notificationButtons = [] + } = {} + ) { + const addedButtons = this.processButtons(notificationButtons); + const detailMessage = this.joinMessageArray(detail); + const stackMessage = this.joinMessageArray(stack); + + if (consoleErrorLog) { + if (stack) { + console.error(message, stack); + } else { + console.error(message); + } + } + if (showConsoleMessage) { this.consoleService.error(message); - atom.notifications.addError("Build failed", {detail: message, dismissable: true}); - } else { - this.consoleService.error("Build failed"); - atom.notifications.addError("Build failed", {dismissable: true}); + if (typeof(detailMessage) === "string" && detailMessage.length > 0) { + this.consoleService.error(detailMessage); + } } - } - - handleSubmitProgressMessage(input) { - if (typeof(input) === "string") { - atom.notifications.addInfo(input, {}); + if (showNotification && typeof(message) === "string") { + const notificationOptions = { + ...addedButtons, + dismissable: !notificationAutoDismiss, + detail: detailMessage ? detailMessage : "", + stack: stackMessage ? stackMessage: "", + description: description ? description : "" + }; + return atom.notifications.addError(message, notificationOptions); } - this.consoleService.log(input); } - handleSubmitSuccess(input, notificationButtons = []) { - let addedButtons = {}; - if (Array.isArray(notificationButtons)) { - addedButtons.buttons = notificationButtons.map(obj => ({onDidClick: obj.callbackFn, text: obj.label})); + handleSuccess( + message, + { + detail = null, + description = null, + showNotification = true, + showConsoleMessage = true, + notificationAutoDismiss = false, + notificationButtons = [] + } = {} + ) { + const addedButtons = this.processButtons(notificationButtons); + const detailMessage = this.joinMessageArray(detail); + + if (showConsoleMessage) { + this.consoleService.log(`${message}${detailMessage ? "\n"+detailMessage: ""}`); } - atom.notifications.addSuccess(`Job ${input.name} is ${input.health}`, {...addedButtons, dismissable: true}); - - if (this.consoleService) { - this.consoleService.success(`Job ${input.name} is ${input.health}`); - } - } - - handleSubmitFailure(input) { - const errorString = input.errors.map(err => err.message).join("\n"); - atom.notifications.addError(`Job submission failed`, {detail: errorString, dismissable: true}); - - if (this.consoleService) { - this.consoleService.error(`Job submission failed\n${errorString}`); - } - } - handleError(input, notificationButtons = []) { - let addedButtons = {}; - if (Array.isArray(notificationButtons)) { - addedButtons.buttons = notificationButtons.map(obj => ({onDidClick: obj.callbackFn, text: obj.label})); - } - if (typeof(input) === "string") { - atom.notifications.addError(input, {...addedButtons, dismissable: true}); - this.consoleService.error(input); - } else if (input.message) { - atom.notifications.addError( - input.message, - {...addedButtons, dismissable: true, detail: input.stack, stack: input.stack} - ); - this.consoleService.error(input.message); - } - console.error(input); - } - handleSuccess(input, detail, showNotification, showConsoleMsg, notificationButtons = []) { - let addedButtons = {}; - if (Array.isArray(notificationButtons)) { - addedButtons.buttons = notificationButtons.map(obj => ({onDidClick: obj.callbackFn, text: obj.label})); - } - if (showNotification) { - atom.notifications.addSuccess(input, {...addedButtons, detail: detail, dismissable: true}); - } - if (showConsoleMsg) { - if (this.consoleService) { - this.consoleService.success(`${input}\n${detail}`); - } - } - } - - handleWarning(message) { - if (message && typeof(message) === "string") { - this.consoleService.warn(message); - atom.notifications.addWarning(message, {}); + if (showNotification && typeof(message) === "string") { + const notificationOptions = { + ...addedButtons, + dismissable: !notificationAutoDismiss, + detail: detailMessage ? detailMessage : "", + description: description ? description : "" + }; + return atom.notifications.addSuccess(message, notificationOptions); } } showDialog(message, detail, buttonObjs) { - const nativeImage = require("electron").nativeImage; const labels = buttonObjs.map(obj => obj.label); @@ -130,6 +122,8 @@ export class MessageHandler { labels.forEach((label, index) => { buttons[label] = callbacks[index]; }); + console.log("MessageHandler.showDialog() - input:\nmessage:",message,"\ndetail:",detail,"\nbuttonLabels:",labels,"\nbuttonCallbacks:",callbacks); + console.log("MessageHandler.showDialog() - before atom.confirm()"); atom.confirm( { message: message, @@ -144,25 +138,49 @@ export class MessageHandler { } } ); + console.log("MessageHandler.showDialog() - after atom.confirm()"); } - handleCredentialsMissing() { - atom.notifications.addError( + handleCredentialsMissing(errorNotification) { + const n = atom.notifications.addError( "Copy and paste the Streaming Analytics service credentials into the build-ibmstreams package settings page.", { dismissable: true, buttons: [{ text: "Open package settings", - onDidClick: () => {atom.workspace.open("atom://config/packages/build-ibmstreams")} + onDidClick: () => { + this.dismissNotification(errorNotification); + this.dismissNotification(n); + atom.workspace.open("atom://config/packages/build-ibmstreams"); + } }] } ); + return n; + } + + processButtons(btns) { + let buttons = {}; + if (Array.isArray(btns)) { + buttons.buttons = btns.map(obj => ({onDidClick: obj.callbackFn, text: obj.label})); + } + return buttons; + } + + joinMessageArray(msgArray) { + if (Array.isArray(msgArray)) { + return msgArray.join("\n").trimRight(); + } + return msgArray; + } + + dismissNotification(notification) { + if (notification && typeof(notification.dismiss) === "function") { + notification.dismiss(); + } } getLoggableMessage(messages: Array) { - return messages - .map(outputMsg => outputMsg.message_text) - .join("\n") - .trimRight(); + return this.joinMessageArray(messages.map(outputMsg => outputMsg.message_text)); } } diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 851b67d..d00d854 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -7,8 +7,8 @@ import * as path from "path"; import * as fs from "fs"; import * as _ from "underscore"; -import { Observable, of, empty, forkJoin } from "rxjs"; -import { switchMap, map, expand, filter, tap, debounceTime, mergeMap } from "rxjs/operators"; +import { Observable, of, empty, forkJoin, interval } from "rxjs"; +import { switchMap, map, expand, filter, tap, debounceTime, mergeMap, takeUntil } from "rxjs/operators"; import * as ncp from "copy-paste"; const request = require("request"); @@ -19,6 +19,7 @@ const defaultIgnoreFiles = [ ".project", ".classpath", "toolkit.xml", + ".build*zip", "___bundle.zip" ]; @@ -28,6 +29,8 @@ const defaultIgnoreDirectories = [ "samples", "opt/client", ".settings", + ".apt_generated", + ".build*", "___bundle" ]; @@ -67,15 +70,26 @@ export class SplBuilder { async buildSourceArchive(appRoot: string, toolkitRootPath: string, options: {useMakefile: boolean, makefilePath: string, fqn: string} = {useMakefile: false}) { const archiver = require("archiver"); + this.useMakefile = options.useMakefile; + if (options.makefilePath) { + this.makefilePath = options.makefilePath; + } + if (options.fqn) { + this.fqn = options.fqn; + } + const appRootContents = fs.readdirSync(appRoot); const makefilesFound = appRootContents.filter(entry => typeof(entry) === "string" && entry.toLowerCase() === "makefile"); const buildTarget = options.useMakefile ? " with makefile" : ` for ${options.fqn}`; - this.messageHandler.handleBuildProgressMessage(`Building application archive${buildTarget}...`, true); + this.messageHandler.handleInfoMessage(`Building application archive${buildTarget}...`); - const outputFilePath = `${appRoot}${path.sep}___bundle.zip`; + // temporary build archive filename is of format + // .build_[fqn].zip or .build_make_[parent_dir].zip for makefile build + // eg: .build_sample.Vwap.zip , .build_make_Vwap.zip + const outputFilePath = `${appRoot}${path.sep}.build_${options.useMakefile ? "make_"+appRoot.split(path.sep).pop() : options.fqn.replace("::",".")}.zip`; - // delete existing ___bundle.zip file before creating new one + // delete existing build archive file before creating new one // TODO: handle if file is open better (windows file locks) try { if (fs.existsSync(outputFilePath)) { @@ -86,17 +100,14 @@ export class SplBuilder { const archive = archiver("zip", { zlib: { level: 9} // compression level }); - const self = this; - output.on("close", function() { + //const self = this; + output.on("close", () => { console.log("Application source archive built"); - self.messageHandler.handleBuildProgressMessage("Application archive created, submitting to build service...", true); + this.messageHandler.handleInfoMessage("Application archive created, submitting to build service..."); }); - // TODO: handle warnings/errors instead of throwing? archive.on("warning", function(err) { if (err.code === "ENOENT") { - // log warning } else { - // throw error throw err; } }); @@ -122,13 +133,27 @@ export class SplBuilder { // Add files rootContents .filter(item => fs.lstatSync(`${appRoot}/${item}`).isFile()) - .filter(item => !_.some(ignoreFiles, name => item.includes(name))) + .filter(item => !_.some(ignoreFiles, name => { + if (name.includes("*")) { + const regex = new RegExp(name.replace(".","\.").replace("*",".*")); + return regex.test(item); + } else { + return item.includes(name); + } + })) .forEach(item => archive.append(fs.readFileSync(`${appRoot}/${item}`), { name: `${newRoot}/${item}` })); // Add directories rootContents .filter(item => fs.lstatSync(`${appRoot}/${item}`).isDirectory()) - .filter(item => !_.some(ignoreDirs, name => item.endsWith(name))) + .filter(item => !_.some(ignoreDirs, name => { + if (name.includes("*")) { + const regex = new RegExp(name.replace(".","\.").replace("*",".*")); + return regex.test(item); + } else { + return item.includes(name); + } + })) .forEach(item => archive.directory(`${appRoot}/${item}`, `${newRoot}/${item}`)); toolkitPaths.forEach(tk => archive.directory(tk.tkPath, `toolkits/${tk.tk}`)); @@ -158,7 +183,7 @@ export class SplBuilder { const archiveStream = await archive.finalize(); } catch (err) { - this.messageHandler.handleError(err); + this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack, consoleErrorLog: false}); return Promise.reject(err); } @@ -176,8 +201,8 @@ export class SplBuilder { this.buildAndSubmitJob(input); } } else { - this.messageHandler.handleError("Unable to determine Streaming Analytics service credentials."); - this.messageHandler.handleCredentialsMissing(); + const errorNotification = this.messageHandler.handleError("Unable to determine Streaming Analytics service credentials."); + this.messageHandler.handleCredentialsMissing(errorNotification); throw new Error("Error parsing VCAP_SERVICES environment variable"); } } @@ -197,11 +222,24 @@ export class SplBuilder { ).subscribe( next => {}, err => { - console.log("build error\n", err); - this.messageHandler.handleError(err); - this.checkKnownErrors(err); + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + this.checkKnownErrors(err, errorNotification, this.buildAndDownloadBundle.bind(this), input); }, - downloadResult => console.log("download result\n",downloadResult), + complete => { + console.log("buildAndDownloadBundle observable complete"); + try { + if (input.filename && fs.existsSync(input.filename)) { + fs.unlinkSync(input.filename); + } + } catch (err) { + this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } + } ); } @@ -231,15 +269,29 @@ export class SplBuilder { ).subscribe( next => {}, err => { - console.log("build and submit via Console error\n", err); - this.messageHandler.handleError(err); - this.checkKnownErrors(err); + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + this.checkKnownErrors(err, errorNotification, this.buildAndSubmitJob.bind(this), input); }, - consoleResult => console.log("submit via Console result\n", consoleResult), + complete => { + console.log("buildAndSubmitJob observable complete"); + try { + if (input.filename && fs.existsSync(input.filename)) { + fs.unlinkSync(input.filename); + } + } catch (err) { + this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } + } ); } submit(streamingAnalyticsCredentials, input) { + console.log("submit(); input:",arguments); this.serviceCredentials = SplBuilder.parseServiceCredentials(streamingAnalyticsCredentials); const self = this; if (this.serviceCredentials.apikey && this.serviceCredentials.v2_rest_url) { @@ -268,31 +320,50 @@ export class SplBuilder { ).subscribe( next => {}, err => { - console.log("submit job error\n", err); - this.messageHandler.handleError(err); - this.checkKnownErrors(err); + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + this.checkKnownErrors(err, errorNotification, this.submit.bind(this), [streamingAnalyticsCredentials, input]); }, - consoleResult => console.log("submit result\n", consoleResult), + complete => console.log("submit .sab observable complete"), ); } else { - this.messageHandler.handleError("Unable to determine Streaming Analytics service credentials."); - this.messageHandler.handleCredentialsMissing(); + const errorNotification = this.messageHandler.handleError("Unable to determine Streaming Analytics service credentials."); + this.messageHandler.handleCredentialsMissing(errorNotification); throw new Error("Error parsing VCAP_SERVICES environment variable"); } } submitJobPrompt(consoleUrl, outputDir, submissionObservableFunc, submissionObservableInput) { + console.log("submitJobPrompt(); input:",arguments); + let submissionTarget = "the application(s)"; + if (typeof(this.useMakefile) === "boolean") { + if(this.useMakefile) { + submissionTarget = "the application(s) for the Makefile"; + } else if (this.fqn) { + submissionTarget = this.fqn; + } + } else { + if (submissionObservableInput.filename) { + submissionTarget = submissionObservableInput.filename.split(path.sep).pop(); + } + } - // Submission dialog - const dialogMessage = "Job submission"; - const dialogDetail = "Submit the application(s) to your instance with default configuration " + + // Submission notification + let submissionNotification = null; + const dialogMessage = `Job submission - ${this.useMakefile ? this.makefilePath : submissionTarget}`; + const dialogDetail = `Submit ${submissionTarget} to your instance with default configuration ` + "or use the Streaming Analytics Console to customize the submission time configuration."; const dialogButtons = [ { label: "Submit", callbackFn: () => { - this.messageHandler.handleSubmitProgressMessage("Submitting application to Streaming Analytics instance..."); + console.log("submitButtonCallback"); + this.messageHandler.handleInfoMessage("Submitting application to Streaming Analytics instance..."); submissionObservableFunc(submissionObservableInput).pipe( mergeMap(submitResult => { @@ -306,12 +377,12 @@ export class SplBuilder { if (Array.isArray(submitResult)) { submitResult.forEach(obj => { if (obj.body) { - this.messageHandler.handleSubmitSuccess(obj.body, notificationButtons); + this.messageHandler.handleSuccess(`Job ${obj.body.name} is ${obj.body.health}`, {notificationButtons: notificationButtons}); } }); } else { if (submitResult.body) { - this.messageHandler.handleSubmitSuccess(submitResult.body, notificationButtons); + this.messageHandler.handleSuccess(`Job ${submitResult.body.name} is ${submitResult.body.health}`, {notificationButtons: notificationButtons}); } } return of(submitResult); @@ -319,78 +390,65 @@ export class SplBuilder { ).subscribe( next => {}, err => { - console.log("default job submit error\n", err); - this.messageHandler.handleError(err); - this.checkKnownErrors(err); + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + console.log("submitPrompt error caught, submissionObservableFunc:",submissionObservableFunc, "submissionObservableInput:",submissionObservableInput); + this.checkKnownErrors(err, errorNotification, submissionObservableFunc.bind(this), submissionObservableInput); }, - consoleResult => console.log("submit result\n", consoleResult), + complete => console.log("job submission observable complete"), ); + this.messageHandler.dismissNotification(submissionNotification); } }, { - label: "Submit via Console", + label: "Submit via Streaming Analytics Console", callbackFn: () => { - const submitMsg = () => { - this.messageHandler.handleSuccess( - "Submit via Console", - `Use the Streaming Analytics Console to submit.`, - true, - true, - [ - { - label: "Copy output path", - callbackFn: () => ncp.copy(outputDir) - }, - { - label: "Open Streaming Analytics Console", - callbackFn: () => this.openUrlHandler(consoleUrl) - } - ] - ); - }; if (submissionObservableInput.filename && submissionObservableInput.filename.toLowerCase().endsWith(".sab")) { // sab is local already - submitMsg(); + this.openUrlHandler(consoleUrl); } else { // need to download bundles first - this.messageHandler.handleSubmitProgressMessage("Downloading application bundles for submission via Streaming analytics Console..."); + this.messageHandler.handleInfoMessage("Downloading application bundles for submission via Streaming analytics Console..."); this.downloadBundlesObservable(submissionObservableInput).pipe( map(downloadOutput => ( [ submissionObservableInput, downloadOutput ])), mergeMap(downloadResult => this.performBundleDownloads(downloadResult, null, outputDir)), ).subscribe( next => {}, err => { - console.log("Error downloading bundles for Console submit\n", err); - this.messageHandler.handleError(err); - this.checkKnownErrors(err); + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + this.checkKnownErrors(err, errorNotification); }, - submitMsg(), + complete => this.openUrlHandler(consoleUrl) ); } + this.messageHandler.dismissNotification(submissionNotification); } }, - { - label: "Cancel", - callbackFn: null - } ]; - this.messageHandler.showDialog(dialogMessage, dialogDetail, dialogButtons); - this.messageHandler.handleBuildProgressMessage(`Streaming Analytics Console URL: ${consoleUrl}`); - + submissionNotification = this.messageHandler.handleInfoMessage(dialogMessage,{detail: dialogDetail, notificationAutoDismiss: false, notificationButtons: dialogButtons}); } /** * poll build status for a specific build * @param input - * @param messageHandler IDE specific message handler callback object */ pollBuildStatus(input) { let prevBuildOutput = []; - this.messageHandler.handleBuildProgressMessage("Building...", true); + let buildMessage = `Building ${this.useMakefile? this.makefilePath : this.fqn}...`; + this.messageHandler.handleInfoMessage(buildMessage); return this.getBuildStatusObservable(input) .pipe( map((buildStatusResponse) => ({...input, ...buildStatusResponse.body})), @@ -402,7 +460,7 @@ export class SplBuilder { tap(s => { if (this._pollHandleMessage % 3 === 0) { const newOutput = this.getNewBuildOutput(s.output, prevBuildOutput); - this.messageHandler.handleBuildProgressMessage(newOutput, true); + this.messageHandler.handleInfoMessage(buildMessage, {detail: this.messageHandler.getLoggableMessage(newOutput)}); prevBuildOutput = s.output; } this._pollHandleMessage++; @@ -434,34 +492,94 @@ export class SplBuilder { buildStatusIsComplete(input, prevBuildOutput) { if (input.status === "failed") { + const failMessage = `Build failed - ${this.useMakefile ? this.makefilePath : this.fqn}`; this.lintHandler.lint(input); const newOutput = this.getNewBuildOutput(input.output, prevBuildOutput); - this.messageHandler.handleBuildFailure(newOutput); + this.messageHandler.handleError(failMessage, {detail: this.messageHandler.getLoggableMessage(newOutput)}); return true; } else if (input.status === "built") { + const successMessage = `Build succeeded - ${this.useMakefile ? this.makefilePath : this.fqn}`; this.lintHandler.lint(input); const newOutput = this.getNewBuildOutput(input.output, prevBuildOutput); - this.messageHandler.handleBuildSuccess(newOutput); + this.messageHandler.handleSuccess(successMessage, {detail: this.messageHandler.getLoggableMessage(newOutput)}); return true; } else { return false; } } - checkKnownErrors(err) { + checkKnownErrors(err, errorNotification, retryCallbackFunction = null, retryInput = null) { if (typeof(err) === "string") { if (err.includes("CDISB4090E")) { - // additional notification with button to open bluemix dashboard so the user can verify their + // additional notification with button to open IBM Cloud dashboard so the user can verify their // service is started. - this.messageHandler.handleError( + const n = this.messageHandler.handleError( "Verify that the streaming analytics service is started and able to handle requests.", - [{label: "Open Bluemix Dashboard", - callbackFn: ()=>{this.openUrlHandler("https://console.bluemix.net/dashboard/apps")} - }]); + { notificationButtons: [ + { + label: "Open IBM Cloud Dashboard", + callbackFn: ()=>{this.openUrlHandler("https://console.bluemix.net/dashboard/apps")} + }, + { + label: "Start service and retry", + callbackFn: ()=> this.startInstanceAndRetry(retryCallbackFunction, retryInput, [errorNotification, n]) + } + ]} + ); } } } + startInstanceAndRetry(retryCallbackFunction, retryInput, notifications) { + if (Array.isArray(notifications)) { + notifications.map(a => this.messageHandler.dismissNotification(a)); + } + + const startingNotification = this.messageHandler.handleInfoMessage("Streaming Analytics service is starting...", {notificationAutoDismiss: false}); + let startSuccessNotification = null; + let instanceState = null; + const poll = interval(8000); + + poll.pipe( + takeUntil(this.startInstanceObservable().pipe( + map(a => { + if (a && a.body && a.body.state){ + instanceState = a.body.state + } + })) + ), + ).subscribe( + next => {}, + err => { + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + this.checkKnownErrors(err, errorNotification, retryCallbackFunction, retryInput); + }, + startInstanceResult => { + this.messageHandler.dismissNotification(startingNotification); + if (instanceState === "STARTED") { + console.log("instanceRestartedSuccess",arguments); + console.log("retryCallbackFunction:",retryCallbackFunction); + console.log("retryCallbackInput:",retryInput); + this.messageHandler.handleSuccess("Streaming Analytics service started", {detail: "Service has been started. Retrying Build Service request..."}); + if (typeof(retryCallbackFunction) === "function" && retryInput) { + if (Array.isArray(retryInput)) { + retryCallbackFunction.apply(this, retryInput); + } else { + retryCallbackFunction(retryInput); + } + } + } else { + this.messageHandler.handleError("Error starting instance"); + } + console.log("startInstance observable complete"); + }); + } + getAccessTokenObservable() { const iamTokenRequestOptions = { method: "POST", @@ -479,6 +597,24 @@ export class SplBuilder { return SplBuilder.createObservableRequest(iamTokenRequestOptions); } + startInstanceObservable() { + console.log("startInstanceObservable entry"); + const startInstanceRequestOptions = { + method: "PATCH", + url: this.serviceCredentials.v2_rest_url, + instance_id: `${this.serviceCredentials.v2_rest_url.split("/").pop()}`, + json: true, + headers: { + "Authorization": `Bearer ${this.accessToken}`, + "Content-Type": "application/json" + }, + body: { + "state": "STARTED" + } + }; + return SplBuilder.createObservableRequest(startInstanceRequestOptions); + } + getBuildStatusObservable(input) { console.log("pollBuildStatusObservable input:", input); const buildStatusRequestOptions = { @@ -513,7 +649,7 @@ export class SplBuilder { file: { value: fs.createReadStream(input.filename), options: { - filename: "___bundle.zip", + filename: input.filename.split(path.sep).pop(), contentType: "application/zip" } } @@ -640,7 +776,19 @@ export class SplBuilder { fs.unlinkSync(outputFile); } fs.writeFileSync(outputFile, downloadOutput[index].body); - this.messageHandler.handleSuccess(`Application ${artifact.name} bundle downloaded to output directory`, outputFile, true, true); + const notificationButtons = [ + { + label: "Copy output path", + callbackFn: () => ncp.copy(outputDir) + } + ]; + this.messageHandler.handleSuccess( + `Application ${artifact.name} bundle downloaded to output directory`, + { + detail: outputFile, + notificationButtons: notificationButtons + } + ); return of(outputDir); } catch (err) { throw new Error(`Error downloading application .sab bundle\n${err}`); From 0b4f027d3633d78ebaceaf7a1c74a51401af9ad8 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Thu, 20 Dec 2018 15:38:31 -0500 Subject: [PATCH 04/12] Fix buggy retry for job submit; cleanup of unused code; consistent wording and proper capitalization; refactored function names for consistency. --- lib/MessageHandler.js | 35 +-------------------------- lib/spl-build-common.js | 52 ++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 60 deletions(-) diff --git a/lib/MessageHandler.js b/lib/MessageHandler.js index 346e111..9e270ac 100644 --- a/lib/MessageHandler.js +++ b/lib/MessageHandler.js @@ -3,11 +3,6 @@ "use strict"; "use babel"; -import path from "path"; - -const packageRoot = atom.packages.resolvePackagePath("build-ibmstreams"); -const STREAMING_ANALYTICS_ICON_PATH = `${packageRoot}${path.sep}assets${path.sep}streaming_analytics_200x200.png`; - export class MessageHandler { consoleService: null; @@ -15,7 +10,7 @@ export class MessageHandler { this.consoleService = service; } - handleInfoMessage( + handleInfo( message, { detail = null, @@ -113,34 +108,6 @@ export class MessageHandler { } } - showDialog(message, detail, buttonObjs) { - const nativeImage = require("electron").nativeImage; - - const labels = buttonObjs.map(obj => obj.label); - const callbacks = buttonObjs.map(obj => obj.callbackFn); - let buttons = {}; - labels.forEach((label, index) => { - buttons[label] = callbacks[index]; - }); - console.log("MessageHandler.showDialog() - input:\nmessage:",message,"\ndetail:",detail,"\nbuttonLabels:",labels,"\nbuttonCallbacks:",callbacks); - console.log("MessageHandler.showDialog() - before atom.confirm()"); - atom.confirm( - { - message: message, - detail: detail, - buttons: labels, - icon: STREAMING_ANALYTICS_ICON_PATH - }, - (chosen, checkboxChecked) => { - const callback = callbacks[chosen]; - if (typeof(callback) === "function") { - return callback(); - } - } - ); - console.log("MessageHandler.showDialog() - after atom.confirm()"); - } - handleCredentialsMissing(errorNotification) { const n = atom.notifications.addError( "Copy and paste the Streaming Analytics service credentials into the build-ibmstreams package settings page.", diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index d00d854..6138d5d 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -82,7 +82,7 @@ export class SplBuilder { const makefilesFound = appRootContents.filter(entry => typeof(entry) === "string" && entry.toLowerCase() === "makefile"); const buildTarget = options.useMakefile ? " with makefile" : ` for ${options.fqn}`; - this.messageHandler.handleInfoMessage(`Building application archive${buildTarget}...`); + this.messageHandler.handleInfo(`Building application archive${buildTarget}...`); // temporary build archive filename is of format // .build_[fqn].zip or .build_make_[parent_dir].zip for makefile build @@ -103,7 +103,7 @@ export class SplBuilder { //const self = this; output.on("close", () => { console.log("Application source archive built"); - this.messageHandler.handleInfoMessage("Application archive created, submitting to build service..."); + this.messageHandler.handleInfo("Application archive created, submitting to build service..."); }); archive.on("warning", function(err) { if (err.code === "ENOENT") { @@ -355,7 +355,7 @@ export class SplBuilder { // Submission notification let submissionNotification = null; const dialogMessage = `Job submission - ${this.useMakefile ? this.makefilePath : submissionTarget}`; - const dialogDetail = `Submit ${submissionTarget} to your instance with default configuration ` + + const dialogDetail = `Submit ${submissionTarget} to your service with default configuration ` + "or use the Streaming Analytics Console to customize the submission time configuration."; const dialogButtons = [ @@ -363,7 +363,7 @@ export class SplBuilder { label: "Submit", callbackFn: () => { console.log("submitButtonCallback"); - this.messageHandler.handleInfoMessage("Submitting application to Streaming Analytics instance..."); + this.messageHandler.handleInfo("Submitting application to Streaming Analytics service..."); submissionObservableFunc(submissionObservableInput).pipe( mergeMap(submitResult => { @@ -372,7 +372,7 @@ export class SplBuilder { label: "Open Streaming Analytics Console", callbackFn: () => this.openUrlHandler(consoleUrl) } - ] + ]; // when build+submit from makefile/spl file, potentially multiple objects coming back if (Array.isArray(submitResult)) { submitResult.forEach(obj => { @@ -397,7 +397,7 @@ export class SplBuilder { errorNotification = this.messageHandler.handleError(err); } console.log("submitPrompt error caught, submissionObservableFunc:",submissionObservableFunc, "submissionObservableInput:",submissionObservableInput); - this.checkKnownErrors(err, errorNotification, submissionObservableFunc.bind(this), submissionObservableInput); + this.checkKnownErrors(err, errorNotification, this.submitJobPrompt.bind(this), [consoleUrl, outputDir, submissionObservableFunc, submissionObservableInput]); }, complete => console.log("job submission observable complete"), ); @@ -414,7 +414,7 @@ export class SplBuilder { } else { // need to download bundles first - this.messageHandler.handleInfoMessage("Downloading application bundles for submission via Streaming analytics Console..."); + this.messageHandler.handleInfo("Downloading application bundles for submission via Streaming Analytics Console..."); this.downloadBundlesObservable(submissionObservableInput).pipe( map(downloadOutput => ( [ submissionObservableInput, downloadOutput ])), mergeMap(downloadResult => this.performBundleDownloads(downloadResult, null, outputDir)), @@ -437,7 +437,7 @@ export class SplBuilder { }, ]; - submissionNotification = this.messageHandler.handleInfoMessage(dialogMessage,{detail: dialogDetail, notificationAutoDismiss: false, notificationButtons: dialogButtons}); + submissionNotification = this.messageHandler.handleInfo(dialogMessage,{detail: dialogDetail, notificationAutoDismiss: false, notificationButtons: dialogButtons}); } @@ -448,7 +448,7 @@ export class SplBuilder { pollBuildStatus(input) { let prevBuildOutput = []; let buildMessage = `Building ${this.useMakefile? this.makefilePath : this.fqn}...`; - this.messageHandler.handleInfoMessage(buildMessage); + this.messageHandler.handleInfo(buildMessage); return this.getBuildStatusObservable(input) .pipe( map((buildStatusResponse) => ({...input, ...buildStatusResponse.body})), @@ -460,7 +460,7 @@ export class SplBuilder { tap(s => { if (this._pollHandleMessage % 3 === 0) { const newOutput = this.getNewBuildOutput(s.output, prevBuildOutput); - this.messageHandler.handleInfoMessage(buildMessage, {detail: this.messageHandler.getLoggableMessage(newOutput)}); + this.messageHandler.handleInfo(buildMessage, {detail: this.messageHandler.getLoggableMessage(newOutput)}); prevBuildOutput = s.output; } this._pollHandleMessage++; @@ -514,7 +514,7 @@ export class SplBuilder { // additional notification with button to open IBM Cloud dashboard so the user can verify their // service is started. const n = this.messageHandler.handleError( - "Verify that the streaming analytics service is started and able to handle requests.", + "Verify that the Streaming Analytics service is started and able to handle requests.", { notificationButtons: [ { label: "Open IBM Cloud Dashboard", @@ -522,7 +522,7 @@ export class SplBuilder { }, { label: "Start service and retry", - callbackFn: ()=> this.startInstanceAndRetry(retryCallbackFunction, retryInput, [errorNotification, n]) + callbackFn: ()=> this.startServiceAndRetry(retryCallbackFunction, retryInput, [errorNotification, n]) } ]} ); @@ -530,21 +530,21 @@ export class SplBuilder { } } - startInstanceAndRetry(retryCallbackFunction, retryInput, notifications) { + startServiceAndRetry(retryCallbackFunction, retryInput, notifications) { if (Array.isArray(notifications)) { notifications.map(a => this.messageHandler.dismissNotification(a)); } - const startingNotification = this.messageHandler.handleInfoMessage("Streaming Analytics service is starting...", {notificationAutoDismiss: false}); + const startingNotification = this.messageHandler.handleInfo("Streaming Analytics service is starting...", {notificationAutoDismiss: false}); let startSuccessNotification = null; - let instanceState = null; + let serviceState = null; const poll = interval(8000); poll.pipe( - takeUntil(this.startInstanceObservable().pipe( + takeUntil(this.startServiceObservable().pipe( map(a => { if (a && a.body && a.body.state){ - instanceState = a.body.state + serviceState = a.body.state } })) ), @@ -559,10 +559,10 @@ export class SplBuilder { } this.checkKnownErrors(err, errorNotification, retryCallbackFunction, retryInput); }, - startInstanceResult => { + startServiceResult => { this.messageHandler.dismissNotification(startingNotification); - if (instanceState === "STARTED") { - console.log("instanceRestartedSuccess",arguments); + if (serviceState === "STARTED") { + console.log("serviceRestartedSuccess",arguments); console.log("retryCallbackFunction:",retryCallbackFunction); console.log("retryCallbackInput:",retryInput); this.messageHandler.handleSuccess("Streaming Analytics service started", {detail: "Service has been started. Retrying Build Service request..."}); @@ -574,9 +574,9 @@ export class SplBuilder { } } } else { - this.messageHandler.handleError("Error starting instance"); + this.messageHandler.handleError("Error starting service"); } - console.log("startInstance observable complete"); + console.log("startService observable complete"); }); } @@ -597,9 +597,9 @@ export class SplBuilder { return SplBuilder.createObservableRequest(iamTokenRequestOptions); } - startInstanceObservable() { - console.log("startInstanceObservable entry"); - const startInstanceRequestOptions = { + startServiceObservable() { + console.log("startServiceObservable entry"); + const startServiceRequestOptions = { method: "PATCH", url: this.serviceCredentials.v2_rest_url, instance_id: `${this.serviceCredentials.v2_rest_url.split("/").pop()}`, @@ -612,7 +612,7 @@ export class SplBuilder { "state": "STARTED" } }; - return SplBuilder.createObservableRequest(startInstanceRequestOptions); + return SplBuilder.createObservableRequest(startServiceRequestOptions); } getBuildStatusObservable(input) { From 77af1c72d71682897895b766410f0b7e5ab1eaa6 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Fri, 21 Dec 2018 11:26:57 -0500 Subject: [PATCH 05/12] add commands to open Streaming Analytics console and IBM Cloud dashboard --- README.md | 6 ++--- lib/spl-build-common.js | 51 +++++++++++++++++++++++++++++++++++++---- lib/spl-build.js | 19 +++++++++++++++ 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6be59b7..d56af67 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,15 @@ This is the initial public release. For best results you should also install th ### Setup Instructions #### Build - Streaming Analytics Credentials -The build-ibmstreams package requires a running IBM Streaming Analytics service. SPL applications will be built and deployed on this service. If you need to create one, start here and follow the instructions to create an account. +The build-ibmstreams package requires a running IBM Streaming Analytics service. SPL applications will be built and deployed on this service. If you need to create one, start here and follow the instructions to create an account. Note:The service needs to support V2 of the rest api. -Once you have an account go to your dashboard and select the Streaming Analytic service you want to use. You need to make sure it is running and then copy your credentials to the clipboard. To get your credentials select Service Credentials from the actions on the left. From the credentials page, press View credentials for the one you want to use and press the copy button in the upper right side of the credentials to copy them to the clipboard. +Once you have an account go to your dashboard and select the Streaming Analytics service you want to use. You need to make sure it is running and then copy your credentials to the clipboard. To get your credentials select Service Credentials from the actions on the left. From the credentials page, press View credentials for the one you want to use and press the copy button in the upper right side of the credentials to copy them to the clipboard. In Atom there is a setting in the build-ibmstreams package for the credentials. Go to Atom->Preferences->Packages and press the Settings button on the build-ibmstreams package and paste your credentials into the setting. ![](./images/atomcredssetting.png) ### SPL Application build -![](./images/build.gif) \ No newline at end of file +![](./images/build.gif) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 6138d5d..41ff2ca 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -34,6 +34,10 @@ const defaultIgnoreDirectories = [ "___bundle" ]; +const buildConsoleUrl = (url, instanceId) => `${url}#application/dashboard/Application%20Dashboard?instance=${instanceId}`; + +const ibmCloudDashboardUrl = "https://cloud.ibm.com/resources"; + export class SplBuilder { static BUILD_ACTION = {DOWNLOAD: 0, SUBMIT: 1}; static SPL_MSG_REGEX = /^([\w.]+\/[\w.]+)\:(\d+)\:(\d+)\:\s+(\w{5}\d{4}[IWE])\s+((ERROR|WARN|INFO)\:.*)$/; @@ -257,7 +261,7 @@ export class SplBuilder { map(consoleResult => { const [ artifacts, consoleResponse ] = consoleResult; if (consoleResponse.body["streams_console"] && consoleResponse.body["id"]) { - const consoleUrl = `${consoleResponse.body["streams_console"]}#application/dashboard/Application%20Dashboard?instance=${consoleResponse.body["id"]}`; + const consoleUrl = buildConsoleUrl(consoleResponse.body["streams_console"], consoleResponse.body["id"]); this.submitJobPrompt(consoleUrl, outputDir, this.submitAppObservable.bind(this), artifacts); @@ -307,7 +311,7 @@ export class SplBuilder { map(consoleResult => { const [ submitInput, consoleResponse ] = consoleResult; if (consoleResponse.body["streams_console"] && consoleResponse.body["id"]) { - const consoleUrl = `${consoleResponse.body["streams_console"]}#application/dashboard/Application%20Dashboard?instance=${consoleResponse.body["id"]}`; + const consoleUrl = buildConsoleUrl(consoleResponse.body["streams_console"], consoleResponse.body["id"]); this.submitJobPrompt(consoleUrl, outputDir, this.submitSabObservable.bind(this), input); @@ -518,7 +522,7 @@ export class SplBuilder { { notificationButtons: [ { label: "Open IBM Cloud Dashboard", - callbackFn: ()=>{this.openUrlHandler("https://console.bluemix.net/dashboard/apps")} + callbackFn: ()=>{this.openUrlHandler(ibmCloudDashboardUrl)} }, { label: "Start service and retry", @@ -580,10 +584,49 @@ export class SplBuilder { }); } + openStreamingAnalyticsConsole(streamingAnalyticsCredentials) { + this.serviceCredentials = SplBuilder.parseServiceCredentials(streamingAnalyticsCredentials); + if (this.serviceCredentials.apikey && this.serviceCredentials.v2_rest_url) { + + this.getAccessTokenObservable().pipe( + mergeMap(response => { + this.accessToken = response.body.access_token; + return this.getConsoleUrlObservable(); + }), + map(response => { + if (response.body["streams_console"] && response.body["id"]) { + const consoleUrl = buildConsoleUrl(response.body["streams_console"], response.body["id"]); + this.openUrlHandler(consoleUrl); + } else { + this.messageHandler.handleError("Cannot retrieve Streaming Analytics Console URL"); + } + }) + ).subscribe( + next => {}, + err => { + let errorNotification = null; + if (err instanceof Error) { + errorNotification = this.messageHandler.handleError(err.name, {detail: err.message, stack: err.stack}); + } else { + errorNotification = this.messageHandler.handleError(err); + } + this.checkKnownErrors(err); + }, + complete => { + console.log("get Console URL observable complete"); + } + ); + } + } + + openCloudDashboard() { + this.openUrlHandler(ibmCloudDashboardUrl); + } + getAccessTokenObservable() { const iamTokenRequestOptions = { method: "POST", - url: "https://iam.bluemix.net/identity/token", + url: "https://iam.cloud.ibm.com/identity/token", json: true, headers: { Accept: "application/json", diff --git a/lib/spl-build.js b/lib/spl-build.js index 0553e05..5ba312c 100644 --- a/lib/spl-build.js +++ b/lib/spl-build.js @@ -62,6 +62,8 @@ export default { "spl-build:build-make-submit": () => this.buildMake(SplBuilder.BUILD_ACTION.SUBMIT), "spl-build:build-make-download": () => this.buildMake(SplBuilder.BUILD_ACTION.DOWNLOAD), "spl-build:submit": () => this.submit(), + "spl-build:open-console": () => this.openConsole(), + "spl-build:open-IBM-cloud-dashboard": () => this.openCloudDashboard() } ) ); @@ -322,6 +324,23 @@ export default { this.splBuilder.submit(this.streamingAnalyticsCredentials, {filename: selectedFilePath}); + }, + + openConsole() { + this.streamingAnalyticsCredentials = atom.config.get(CONF_STREAMING_ANALYTICS_CREDENTIALS); + this.consoleService = this.consumeConsoleService({id: name, name: name}); + this.messageHandler = new MessageHandler(this.consoleService); + this.lintHandler = new LintHandler(this.linterService, SplBuilder.SPL_MSG_REGEX, this.appRoot); + this.splBuilder = new SplBuilder(this.messageHandler, this.lintHandler, this.openUrlHandler); + this.splBuilder.openStreamingAnalyticsConsole(this.streamingAnalyticsCredentials); + }, + + openCloudDashboard() { + this.streamingAnalyticsCredentials = atom.config.get(CONF_STREAMING_ANALYTICS_CREDENTIALS); + this.consoleService = this.consumeConsoleService({id: name, name: name}); + this.messageHandler = new MessageHandler(this.consoleService); + this.splBuilder = new SplBuilder(this.messageHandler, null, this.openUrlHandler); + this.splBuilder.openCloudDashboard(); } }; From f1c05f1f980e0e35b3db003265b7484ad4838398 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Tue, 8 Jan 2019 14:16:05 -0500 Subject: [PATCH 06/12] If a compilation error is sent to linter, open Diagnostics view. --- lib/LintHandler.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/LintHandler.js b/lib/LintHandler.js index 38d2351..641c0fd 100644 --- a/lib/LintHandler.js +++ b/lib/LintHandler.js @@ -67,6 +67,9 @@ export class LintHandler { this.linter.setAllMessages(convertedMessages); + if (Array.isArray(convertedMessages) && convertedMessages.length > 0) { + atom.workspace.open("atom://nuclide/diagnostics"); + } } } } From 29f9a284d731921728904cbc20417a308904c643 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Tue, 8 Jan 2019 14:17:36 -0500 Subject: [PATCH 07/12] issue #31 Update compiler error message regex to match files with no namespace --- lib/spl-build-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 41ff2ca..df0a423 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -40,7 +40,7 @@ const ibmCloudDashboardUrl = "https://cloud.ibm.com/resources"; export class SplBuilder { static BUILD_ACTION = {DOWNLOAD: 0, SUBMIT: 1}; - static SPL_MSG_REGEX = /^([\w.]+\/[\w.]+)\:(\d+)\:(\d+)\:\s+(\w{5}\d{4}[IWE])\s+((ERROR|WARN|INFO)\:.*)$/; + static SPL_MSG_REGEX = /^([\w.]+(?:\/[\w.]+)?)\:(\d+)\:(\d+)\:\s+(\w{5}\d{4}[IWE])\s+((ERROR|WARN|INFO)\:.*)$/; static SPL_NAMESPACE_REGEX = /^\s*(?:\bnamespace\b)\s+([a-z|A-Z|0-9|\.|\_]+)\s*\;/gm; static SPL_MAIN_COMPOSITE_REGEX = /.*?(?:\bcomposite\b)(?:\s*|\/\/.*?|\/\*.*?\*\/)+([a-z|A-Z|0-9|\.|\_]+)(?:\s*|\/\/.*?|\/\*.*?\*\/)*\{/gm; static STATUS_POLL_FREQUENCY = 5000; From ac067d3f74af295d4a8c3d625a9f6b1b285b0c64 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Wed, 9 Jan 2019 15:54:56 -0500 Subject: [PATCH 08/12] Support comma or semi-colon separated list of toolkit root directories for toolkit path config --- lib/spl-build-common.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 41ff2ca..c3ada14 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -91,7 +91,7 @@ export class SplBuilder { // temporary build archive filename is of format // .build_[fqn].zip or .build_make_[parent_dir].zip for makefile build // eg: .build_sample.Vwap.zip , .build_make_Vwap.zip - const outputFilePath = `${appRoot}${path.sep}.build_${options.useMakefile ? "make_"+appRoot.split(path.sep).pop() : options.fqn.replace("::",".")}.zip`; + const outputFilePath = `${appRoot}${path.sep}.build_${options.useMakefile ? "make_"+appRoot.split(path.sep).pop() : options.fqn.replace("::",".")}_${Date.now()}.zip`; // delete existing build archive file before creating new one // TODO: handle if file is open better (windows file locks) @@ -124,7 +124,7 @@ export class SplBuilder { const toolkitPaths = SplBuilder.getToolkits(toolkitRootPath); let tkPathString = ""; - if (toolkitPaths) { + if (Array.isArray(toolkitPaths) && toolkitPaths.length > 0) { const rootContents = fs.readdirSync(appRoot); const newRoot = path.basename(appRoot); let ignoreFiles = defaultIgnoreFiles; @@ -859,15 +859,27 @@ export class SplBuilder { * */ static getToolkits(toolkitRootDir) { - let validToolkitPaths = null; + let validToolkitPaths = []; if (toolkitRootDir && toolkitRootDir.trim() !== "") { - if (fs.existsSync(toolkitRootDir)) { - let toolkitRootContents = fs.readdirSync(toolkitRootDir); - validToolkitPaths = toolkitRootContents - .filter(item => fs.lstatSync(`${toolkitRootDir}${path.sep}${item}`).isDirectory()) - .filter(dir => fs.readdirSync(`${toolkitRootDir}${path.sep}${dir}`).filter(tkDirItem => tkDirItem === "toolkit.xml").length > 0) - .map(tk => ({ tk: tk, tkPath: `${toolkitRootDir}${path.sep}${tk}` })); + let toolkitRoots = []; + + if (toolkitRootDir.includes(",") || toolkitRootDir.includes(";")) { + toolkitRoots.push(...toolkitRootDir.split(/[,;]/)); + } else { + toolkitRoots.push(toolkitRootDir); } + console.log("toolkitRoots:",toolkitRoots); + + toolkitRoots.forEach(toolkitRoot => { + if (fs.existsSync(toolkitRoot)) { + let toolkitRootContents = fs.readdirSync(toolkitRoot); + validToolkitPaths.push(...toolkitRootContents + .filter(item => fs.lstatSync(`${toolkitRoot}${path.sep}${item}`).isDirectory()) + .filter(dir => fs.readdirSync(`${toolkitRoot}${path.sep}${dir}`).filter(tkDirItem => tkDirItem === "toolkit.xml").length > 0) + .map(tk => ({ tk: tk, tkPath: `${toolkitRoot}${path.sep}${tk}` })) + ); + } + }); } return validToolkitPaths; } From 1a4b75b21ff7a00d71186d835b6cf48950fbafca Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Wed, 9 Jan 2019 16:01:44 -0500 Subject: [PATCH 09/12] remove temporary logging --- lib/spl-build-common.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index c3ada14..49b2525 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -868,7 +868,6 @@ export class SplBuilder { } else { toolkitRoots.push(toolkitRootDir); } - console.log("toolkitRoots:",toolkitRoots); toolkitRoots.forEach(toolkitRoot => { if (fs.existsSync(toolkitRoot)) { From 33741bfaa90236b06c9d87e4c08642ca3ebd66d7 Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Thu, 10 Jan 2019 14:42:40 -0500 Subject: [PATCH 10/12] fixed comment to reflect new temp file naming --- lib/spl-build-common.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/spl-build-common.js b/lib/spl-build-common.js index 49b2525..41102ea 100644 --- a/lib/spl-build-common.js +++ b/lib/spl-build-common.js @@ -89,8 +89,8 @@ export class SplBuilder { this.messageHandler.handleInfo(`Building application archive${buildTarget}...`); // temporary build archive filename is of format - // .build_[fqn].zip or .build_make_[parent_dir].zip for makefile build - // eg: .build_sample.Vwap.zip , .build_make_Vwap.zip + // .build_[fqn]_[time].zip or .build_make_[parent_dir]_[time].zip for makefile build + // eg: .build_sample.Vwap_1547066810853.zip , .build_make_Vwap_1547066810853.zip const outputFilePath = `${appRoot}${path.sep}.build_${options.useMakefile ? "make_"+appRoot.split(path.sep).pop() : options.fqn.replace("::",".")}_${Date.now()}.zip`; // delete existing build archive file before creating new one From 5c2b0667ac2198fba8da5b9381a921661178787c Mon Sep 17 00:00:00 2001 From: Christian Guy Date: Fri, 11 Jan 2019 16:22:54 -0500 Subject: [PATCH 11/12] cast line and column positions to numbers for compile messages --- lib/LintHandler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/LintHandler.js b/lib/LintHandler.js index 641c0fd..cb15e3f 100644 --- a/lib/LintHandler.js +++ b/lib/LintHandler.js @@ -57,7 +57,10 @@ export class LintHandler { location: { file: absolutePath, - position: [[parts[2]-1,parts[3]-1],[parts[2]-1,parts[3]]], // 0-indexed + position: [ + [parseInt(parts[2])-1 ,parseInt(parts[3])-1], + [parseInt(parts[2])-1,parseInt(parts[3])] + ], // 0-indexed }, excerpt: parts[4], description: parts[5], From b54b84a716e88b57add27498aa61545523ec52ff Mon Sep 17 00:00:00 2001 From: Pete Nicholls Date: Fri, 8 Feb 2019 09:57:58 -0500 Subject: [PATCH 12/12] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e94ac50..f124ca6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "build-ibmstreams", "main": "./lib/spl-build", - "version": "0.2.0", + "version": "0.3.0", "description": "IBM Streams build package [Beta]", "keywords": [], "repository": "https://github.com/IBMStreams/build-ibmstreams",