diff --git a/javascriptv3/example_code/cross-services/wkflw-topics-queues/TopicsQueuesWkflw.js b/javascriptv3/example_code/cross-services/wkflw-topics-queues/TopicsQueuesWkflw.js index ce86e1035ef..c6b5e3ec508 100644 --- a/javascriptv3/example_code/cross-services/wkflw-topics-queues/TopicsQueuesWkflw.js +++ b/javascriptv3/example_code/cross-services/wkflw-topics-queues/TopicsQueuesWkflw.js @@ -52,7 +52,7 @@ export class TopicsQueuesWkflw { * @param {import('@aws-sdk/client-sns').SNSClient} snsClient * @param {import('@aws-sdk/client-sqs').SQSClient} sqsClient * @param {import('../../libs/prompter.js').Prompter} prompter - * @param {import('../../libs/slow-logger.js').Logger} logger + * @param {import('../../libs/logger.js').Logger} logger */ constructor(snsClient, sqsClient, prompter, logger) { this.snsClient = snsClient; @@ -72,7 +72,7 @@ export class TopicsQueuesWkflw { }); if (this.isFifo) { - this.prompter.logSeparator(MESSAGES.headerDedup); + this.logger.logSeparator(MESSAGES.headerDedup); await this.logger.log(MESSAGES.deduplicationNotice); await this.logger.log(MESSAGES.deduplicationDescription); this.autoDedup = await this.prompter.confirm({ @@ -88,7 +88,7 @@ export class TopicsQueuesWkflw { }); if (this.isFifo) { this.topicName += ".fifo"; - this.prompter.logSeparator(MESSAGES.headerFifoNaming); + this.logger.logSeparator(MESSAGES.headerFifoNaming); await this.logger.log(MESSAGES.appendFifoNotice); } @@ -184,7 +184,7 @@ export class TopicsQueuesWkflw { ); if (index !== 0) { - this.prompter.logSeparator(); + this.logger.logSeparator(); } await this.logger.log(MESSAGES.attachPolicyNotice); @@ -399,21 +399,21 @@ export class TopicsQueuesWkflw { console.clear(); try { - this.prompter.logSeparator(MESSAGES.headerWelcome); + this.logger.logSeparator(MESSAGES.headerWelcome); await this.welcome(); - this.prompter.logSeparator(MESSAGES.headerFifo); + this.logger.logSeparator(MESSAGES.headerFifo); await this.confirmFifo(); - this.prompter.logSeparator(MESSAGES.headerCreateTopic); + this.logger.logSeparator(MESSAGES.headerCreateTopic); await this.createTopic(); - this.prompter.logSeparator(MESSAGES.headerCreateQueues); + this.logger.logSeparator(MESSAGES.headerCreateQueues); await this.createQueues(); - this.prompter.logSeparator(MESSAGES.headerAttachPolicy); + this.logger.logSeparator(MESSAGES.headerAttachPolicy); await this.attachQueueIamPolicies(); - this.prompter.logSeparator(MESSAGES.headerSubscribeQueues); + this.logger.logSeparator(MESSAGES.headerSubscribeQueues); await this.subscribeQueuesToTopic(); - this.prompter.logSeparator(MESSAGES.headerPublishMessage); + this.logger.logSeparator(MESSAGES.headerPublishMessage); await this.publishMessages(); - this.prompter.logSeparator(MESSAGES.headerReceiveMessages); + this.logger.logSeparator(MESSAGES.headerReceiveMessages); await this.receiveAndDeleteMessages(); } catch (err) { console.error(err); diff --git a/javascriptv3/example_code/cross-services/wkflw-topics-queues/tests/TopicsQueuesWkflw.unit.test.js b/javascriptv3/example_code/cross-services/wkflw-topics-queues/tests/TopicsQueuesWkflw.unit.test.js index 08faa21a9ef..43c74258b04 100644 --- a/javascriptv3/example_code/cross-services/wkflw-topics-queues/tests/TopicsQueuesWkflw.unit.test.js +++ b/javascriptv3/example_code/cross-services/wkflw-topics-queues/tests/TopicsQueuesWkflw.unit.test.js @@ -72,6 +72,7 @@ const SNSClientMock = { const LoggerMock = { log: vi.fn(), + logSeparator: vi.fn(), }; describe("TopicsQueuesWkflw", () => { @@ -111,7 +112,6 @@ describe("TopicsQueuesWkflw", () => { new SQSClient({}), { confirm: () => Promise.resolve(true), - logSeparator: vi.fn(), }, LoggerMock, ); @@ -151,7 +151,6 @@ describe("TopicsQueuesWkflw", () => { { confirm: () => Promise.resolve(true), input: () => Promise.resolve("user-input"), - logSeparator: vi.fn(), }, LoggerMock, ); @@ -197,7 +196,6 @@ describe("TopicsQueuesWkflw", () => { it("should attach a policy to each of the SQS queues", async () => { const PrompterMock = { confirm: vi.fn(() => Promise.resolve(true)), - logSeparator: vi.fn(() => {}), }; const topicsQueuesWkflw = new TopicsQueuesWkflw( diff --git a/javascriptv3/example_code/libs/logger.js b/javascriptv3/example_code/libs/logger.js new file mode 100644 index 00000000000..73c1657df32 --- /dev/null +++ b/javascriptv3/example_code/libs/logger.js @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export class Logger { + /** + * @param {string} message + */ + log(message) { + console.log(message); + return Promise.resolve(); + } + + /** + * Log a horizontal rule to the console. If a message is provided, + * log a section header. + * @param {string?} message + */ + logSeparator(message) { + if (!message) { + console.log("\n", "*".repeat(80), "\n"); + } else { + console.log( + "\n", + "*".repeat(80), + "\n", + "** ", + message, + " ".repeat(80 - message.length - 8), + "**\n", + "*".repeat(80), + "\n", + ); + } + } +} diff --git a/javascriptv3/example_code/libs/prompter.js b/javascriptv3/example_code/libs/prompter.js index 5694ccb3be2..89ea125f54d 100644 --- a/javascriptv3/example_code/libs/prompter.js +++ b/javascriptv3/example_code/libs/prompter.js @@ -33,7 +33,7 @@ export class Prompter { checkContinue = async (prompt = "") => { const prefix = prompt && prompt + " "; let ok = await this.confirm({ - message: `${prefix}Continue?"}`, + message: `${prefix}Continue?`, }); if (!ok) throw new Error("Exiting..."); }; @@ -46,29 +46,6 @@ export class Prompter { return confirm(options); } - /** - * Log a horizontal rule to the console. If a message is provided, - * log a section header. - * @param {string?} message - */ - logSeparator(message) { - if (!message) { - console.log("\n", "*".repeat(80), "\n"); - } else { - console.log( - "\n", - "*".repeat(80), - "\n", - "** ", - message, - " ".repeat(80 - message.length - 8), - "**\n", - "*".repeat(80), - "\n", - ); - } - } - /** * @param {{ message: string, choices: { name: string, value: string }[]}} options */ diff --git a/javascriptv3/example_code/libs/scenario.js b/javascriptv3/example_code/libs/scenario.js new file mode 100644 index 00000000000..1fe98139eec --- /dev/null +++ b/javascriptv3/example_code/libs/scenario.js @@ -0,0 +1,115 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Prompter } from "./prompter.js"; +import { Logger } from "./logger.js"; +import { SlowLogger } from "./slow-logger.js"; + +class Step { + /** + * @param {string} name + */ + constructor(name) { + this.name = name; + } +} + +export class ScenarioOutput extends Step { + /** + * @param {string} name + * @param {string | (context: Record) => string} value + * @param {{ slow: boolean }} options + */ + constructor(name, value, options = { slow: true }) { + super(name); + this.value = value; + this.options = options; + this.slowLogger = new SlowLogger(20); + this.logger = new Logger(); + } + + /** + * @param {Record} context + */ + async handle(context) { + const output = + typeof this.value === "function" ? this.value(context) : this.value; + const logger = this.options.slow ? this.slowLogger : this.logger; + await logger.log(JSON.stringify(output)); + } +} + +export class ScenarioInput extends Step { + /** + * @param {string} name + * @param {string} prompt + * @param {{ type: "input" | "multi-select" | "select", choices: { name: string, value: string }[]} options + */ + constructor(name, prompt, options) { + super(name); + this.prompt = prompt; + this.options = options; + this.prompter = new Prompter(); + } + + /** + * @param {Record} context + */ + async handle(context) { + if (this.options.type === "multi-select") { + context[this.name] = await this.prompter.checkbox({ + message: this.prompt, + choices: this.options.choices, + }); + } else if (this.options.type === "select") { + context[this.name] = await this.prompter.select({ + message: this.prompt, + choices: this.options.choices, + }); + } else if (this.options.type === "input") { + context[this.name] = await this.prompter.input({ message: this.prompt }); + } else { + throw new Error( + `Error handling ScenarioInput, ${this.options.type} is not supported.`, + ); + } + } +} + +export class ScenarioAction extends Step { + /** + * + * @param {string} name + * @param {(context: Record) => Promise} action + */ + constructor(name, action) { + super(name); + this.action = action; + } + + async handle(context) { + await this.action(context); + } +} + +export class Scenario { + /** + * @type {Record} + */ + context = {}; + + /** + * @param {(ScenarioOutput | ScenarioInput | ScenarioAction)[]} steps + */ + constructor(steps = []) { + this.steps = steps; + } + + async run() { + for (const step of this.steps) { + await step.handle(this.context); + } + } +} diff --git a/javascriptv3/example_code/libs/slow-logger.js b/javascriptv3/example_code/libs/slow-logger.js index f619ec46bf6..0056c851cdc 100644 --- a/javascriptv3/example_code/libs/slow-logger.js +++ b/javascriptv3/example_code/libs/slow-logger.js @@ -3,35 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -// snippet-start:[javascript.v3.wkflw.topicsandqueues.logger] -export class Logger { - /** - * @param {string} message - */ - log(message) { - console.log(message); - return Promise.resolve(); - } -} +import { Logger } from "./logger.js"; +// snippet-start:[javascript.v3.wkflw.topicsandqueues.logger] export class SlowLogger extends Logger { constructor(delayInMs) { super(); this.delay = delayInMs; } - sleep() { + _sleep() { return new Promise((resolve) => setTimeout(resolve, this.delay)); } /** * @param {string} message */ - async logSlow(message) { + async _logSlow(message) { const chars = message.split(""); for (const c of chars) { process.stdout.write(c); - await this.sleep(); + await this._sleep(); } process.stderr.write("\n"); } @@ -49,12 +41,12 @@ export class SlowLogger extends Logger { let line = ""; for (const word of words) { if (line.length + word.length > maxWidth) { - await this.logSlow(line); + await this._logSlow(line); line = ""; } line += word + " "; } - await this.logSlow(line); + await this._logSlow(line); } } // snippet-end:[javascript.v3.wkflw.topicsandqueues.logger] diff --git a/javascriptv3/example_code/sagemaker/scenarios/wkflw-sagemaker-geospatial-pipeline/SageMakerPipelinesWkflw.js b/javascriptv3/example_code/sagemaker/scenarios/wkflw-sagemaker-geospatial-pipeline/SageMakerPipelinesWkflw.js index 0b9b7f2425e..e46970ba8e2 100644 --- a/javascriptv3/example_code/sagemaker/scenarios/wkflw-sagemaker-geospatial-pipeline/SageMakerPipelinesWkflw.js +++ b/javascriptv3/example_code/sagemaker/scenarios/wkflw-sagemaker-geospatial-pipeline/SageMakerPipelinesWkflw.js @@ -43,7 +43,7 @@ export class SageMakerPipelinesWkflw { /** * @param {import("libs/prompter.js").Prompter} prompter - * @param {import("libs/slow-logger.js").Logger} logger + * @param {import("libs/logger.js").Logger} logger * @param {{ IAM: import("@aws-sdk/client-iam").IAMClient, Lambda: import("@aws-sdk/client-lambda").LambdaClient, SageMaker: import("@aws-sdk/client-sagemaker").SageMakerClient, S3: import("@aws-sdk/client-s3").S3Client, SQS: import("@aws-sdk/client-sqs").SQSClient }} clients */ constructor(prompter, logger, clients) { @@ -61,7 +61,7 @@ export class SageMakerPipelinesWkflw { } finally { // Run all of the clean up functions. If any fail, we log the error and continue. // This ensures all clean up functions are run. - this.prompter.logSeparator(); + this.logger.logSeparator(); const doCleanUp = await this.prompter.confirm({ message: "Clean up resources?", }); @@ -77,10 +77,10 @@ export class SageMakerPipelinesWkflw { } async startWorkflow() { - this.prompter.logSeparator(MESSAGES.greetingHeader); + this.logger.logSeparator(MESSAGES.greetingHeader); await this.logger.log(MESSAGES.greeting); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.creatingRole.replace( "${ROLE_NAME}", @@ -105,7 +105,7 @@ export class SageMakerPipelinesWkflw { ), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.creatingRole.replace( @@ -132,7 +132,7 @@ export class SageMakerPipelinesWkflw { ), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); // Create an IAM policy that allows the AWS Lambda function to invoke SageMaker APIs. const { @@ -167,7 +167,7 @@ export class SageMakerPipelinesWkflw { await this.logger.log(MESSAGES.policyAttached); - this.prompter.logSeparator(); + this.logger.logSeparator(); // Create Lambda layer for SageMaker packages. const { versionArn: layerVersionArn, cleanUp: lambdaLayerCleanUp } = @@ -201,7 +201,7 @@ export class SageMakerPipelinesWkflw { ), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.creatingSQSQueue.replace("${QUEUE_NAME}", this.names.SQS_QUEUE), @@ -222,7 +222,7 @@ export class SageMakerPipelinesWkflw { MESSAGES.sqsQueueCreated.replace("${QUEUE_NAME}", this.names.SQS_QUEUE), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.configuringLambdaSQSEventSource @@ -247,7 +247,7 @@ export class SageMakerPipelinesWkflw { .replace("${QUEUE_NAME}", this.names.SQS_QUEUE), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); // Create an IAM policy that allows the SageMaker pipeline to invoke AWS Lambda // and send messages to the Amazon SQS queue. @@ -287,7 +287,7 @@ export class SageMakerPipelinesWkflw { await this.logger.log(MESSAGES.policyAttached); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.creatingPipeline.replace( @@ -312,7 +312,7 @@ export class SageMakerPipelinesWkflw { ), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.creatingS3Bucket.replace("${BUCKET_NAME}", this.names.S3_BUCKET), @@ -329,7 +329,7 @@ export class SageMakerPipelinesWkflw { MESSAGES.s3BucketCreated.replace("${BUCKET_NAME}", this.names.S3_BUCKET), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log( MESSAGES.uploadingInputData.replace( @@ -346,7 +346,7 @@ export class SageMakerPipelinesWkflw { await this.logger.log(MESSAGES.inputDataUploaded); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.prompter.checkContinue(MESSAGES.executePipeline); @@ -365,7 +365,7 @@ export class SageMakerPipelinesWkflw { sagemakerClient: this.clients.SageMaker, }); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log(MESSAGES.outputDelay); @@ -379,7 +379,7 @@ export class SageMakerPipelinesWkflw { }), ); - this.prompter.logSeparator(); + this.logger.logSeparator(); await this.logger.log(MESSAGES.outputDataRetrieved); console.log(output.split("\n").slice(0, 6).join("\n")); } diff --git a/javascriptv3/example_code/sagemaker/tests/wkflw-sagemaker-geospatial-pipeline.integration.test.js b/javascriptv3/example_code/sagemaker/tests/wkflw-sagemaker-geospatial-pipeline.integration.test.js index b5b86fd3a0b..042a39fda66 100644 --- a/javascriptv3/example_code/sagemaker/tests/wkflw-sagemaker-geospatial-pipeline.integration.test.js +++ b/javascriptv3/example_code/sagemaker/tests/wkflw-sagemaker-geospatial-pipeline.integration.test.js @@ -5,7 +5,7 @@ import { describe, test } from "vitest"; import { SageMakerPipelinesWkflw } from "../scenarios/wkflw-sagemaker-geospatial-pipeline/SageMakerPipelinesWkflw.js"; -import { Logger } from "libs/slow-logger.js"; +import { Logger } from "libs/logger.js"; import { IAMClient } from "@aws-sdk/client-iam"; import { SageMakerClient } from "@aws-sdk/client-sagemaker"; import { S3Client } from "@aws-sdk/client-s3";