diff --git a/agent/hooks/post-build/000-run-build b/agent/hooks/post-build/000-run-build index 61f02fa0..ef2fe70f 100644 --- a/agent/hooks/post-build/000-run-build +++ b/agent/hooks/post-build/000-run-build @@ -4,8 +4,8 @@ nvm install v18 # Environment variables are not passed to our run-build hook/script. Hard code them in the script export NODE_ENV=production -export LoggingLevel=info -export LoggingLevelConsole=warn +export LOGGING_LEVEL=info +export LOGGING_LEVEL_CONSOLE=warn # Now run whatever node or npm commands you need to run npm run build diff --git a/agent/package.json b/agent/package.json index 4ec7d7ce..ce21b2ba 100644 --- a/agent/package.json +++ b/agent/package.json @@ -1,6 +1,6 @@ { "name": "@fs/ppaas-agent", - "version": "3.3.5", + "version": "3.3.6", "description": "Agent Service for running pewpew tests", "main": "dist/src/app.js", "scripts": { diff --git a/common/integration/s3.spec.ts b/common/integration/s3.spec.ts index 520aa4f8..7feaa898 100644 --- a/common/integration/s3.spec.ts +++ b/common/integration/s3.spec.ts @@ -114,7 +114,7 @@ describe("S3Util Integration", () => { before (async () => { // This test was failing until we reset everything. I don't know why and it bothers me. - s3Config.s3Client = undefined as any; + s3Config.s3Client = undefined; initS3(); defaultTagKey = initTags(); expect(defaultTagKey, "defaultTagKey").to.not.equal(undefined); diff --git a/common/integration/sqs.spec.ts b/common/integration/sqs.spec.ts index f5fd8aa1..45951ed8 100644 --- a/common/integration/sqs.spec.ts +++ b/common/integration/sqs.spec.ts @@ -96,7 +96,7 @@ describe("SqsUtil Integration", () => { before(async () => { // reset everything in case the mocks ran. - sqsConfig.sqsClient = undefined as any; + sqsConfig.sqsClient = undefined; initSqs(); log("QUEUE_URL_TEST=" + [...QUEUE_URL_TEST], LogLevel.DEBUG); log("QUEUE_URL_SCALE=" + [...QUEUE_URL_SCALE_IN], LogLevel.DEBUG); diff --git a/common/package.json b/common/package.json index 46b4ad3b..667bb99a 100644 --- a/common/package.json +++ b/common/package.json @@ -1,6 +1,6 @@ { "name": "@fs/ppaas-common", - "version": "3.3.5", + "version": "3.3.6", "description": "Common Code for the PewPewController and PewPewAgent", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -15,7 +15,7 @@ "testonly": "nyc mocha ./dist/test --timeout 30000 -r dotenv-flow/config", "integration": "npm run build && nyc mocha ./dist/integration --timeout 300000 -r dotenv-flow/config", "coverage": "npm run build && nyc mocha ./dist/test ./dist/integration --timeout 300000 -r dotenv-flow/config", - "prepare": "npm run buildonly", + "prepare": "tsc --project tsconfig.lib.json", "clean": "rimraf dist/" }, "repository": { @@ -35,6 +35,7 @@ "dependencies": { "@aws-sdk/client-s3": "^3.363.0", "@aws-sdk/client-sqs": "^3.363.0", + "@aws-sdk/credential-providers": "^3.363.0", "@aws-sdk/ec2-metadata-service": "^3.363.0", "@aws-sdk/lib-storage": "^3.363.0", "bunyan": "~1.8.0", diff --git a/common/src/util/s3.ts b/common/src/util/s3.ts index b2df0189..d64be03b 100644 --- a/common/src/util/s3.ts +++ b/common/src/util/s3.ts @@ -26,13 +26,14 @@ import { S3ServiceException, Tag as S3Tag } from "@aws-sdk/client-s3"; +import { IS_RUNNING_IN_AWS, getPrefix } from "./util"; import { LogLevel, log } from "./log"; import { createGzip, gunzip as zlibGunzip} from "zlib"; import { createReadStream, writeFile as fsWriteFile } from "fs"; import { S3File } from "../../types"; import { Upload } from "@aws-sdk/lib-storage"; import { constants as bufferConstants } from "node:buffer"; -import { getPrefix } from "./util"; +import { fromIni } from "@aws-sdk/credential-providers"; import { promisify } from "util"; import stream from "stream"; const { MAX_STRING_LENGTH } = bufferConstants; @@ -47,8 +48,8 @@ export let BUCKET_URL: string; export let KEYSPACE_PREFIX: string; // let REGION_ENDPOINT: string | undefined; // Export for testing so we can reset s3 -export const config: { s3Client: S3Client } = { - s3Client: undefined as unknown as S3Client +export const config: { s3Client: (() => S3Client) | undefined } = { + s3Client: undefined }; /** * ADDITIONAL_TAGS_ON_ALL if set via environment variable is expected to be a comma delimited list of key=value pairs @@ -69,10 +70,10 @@ export const defaultTestExtraFileTags = (): Map => new Map(TEST_ /** * Initializes the S3 object using environment variables. Runs later so it doesn't throw on start-up */ -export function init (): void { +export function init (): S3Client { if (BUCKET_NAME && config.s3Client) { // If we've already set the BUCKET_NAME then we've done this already. - return; + return config.s3Client(); } // Where is your application name, system name, and service name concatenated with underscores, capitalized, and all dashes replaced with underscores. // The s3 service name is s3 in the application which is then capitalized to _S3_ below @@ -101,12 +102,6 @@ export function init (): void { // Since we don't have a private s3 bucket. This will be populated. If we ever move to a private we don't want to tack on the trailing / KEYSPACE_PREFIX = keyspacePrefix.length > 0 && !keyspacePrefix.endsWith("/") ? (keyspacePrefix + "/") : keyspacePrefix; - // Create an S3 service object - config.s3Client = new S3Client({ - // params: { Bucket: BUCKET_NAME }, - region: "us-east-1" - }); - if (process.env.ADDITIONAL_TAGS_ON_ALL && ADDITIONAL_TAGS_ON_ALL.size === 0) { try { for (const keyPair of process.env.ADDITIONAL_TAGS_ON_ALL.split(",")) { @@ -124,6 +119,26 @@ export function init (): void { throw error; } } + + // Create an S3 service object + if (IS_RUNNING_IN_AWS) { + // Create a fixed client that will be returned every time. + const s3Client = new S3Client({ + // params: { Bucket: BUCKET_NAME }, + region: "us-east-1" + }); + config.s3Client = () => s3Client; + } else { + // https://github.com/aws/aws-sdk-js-v3/issues/3396 + // When not running in AWS, use the fromIni rather than automatic configuration, don't cache the credentials, + // and create a new instance every time + config.s3Client = () => new S3Client({ + // params: { Bucket: BUCKET_NAME }, + credentials: fromIni({ ignoreCache: true }), + region: "us-east-1" + }); + } + return config.s3Client(); } let accessCallback: (date: Date) => void | undefined; @@ -459,7 +474,7 @@ export async function listObjects (options?: string | ListObjectsOptions): Promi ({ prefix, maxKeys, continuationToken } = options || {}); } log(`listObjects(${prefix}, ${maxKeys}, ${continuationToken})`, LogLevel.DEBUG); - init(); + const s3Client = init(); if (!prefix || !prefix.startsWith(KEYSPACE_PREFIX)) { prefix = KEYSPACE_PREFIX + (prefix || ""); } @@ -471,7 +486,7 @@ export async function listObjects (options?: string | ListObjectsOptions): Promi }; try { log("listObjects request", LogLevel.DEBUG, params); - const result: ListObjectsV2CommandOutput = await config.s3Client.send(new ListObjectsV2Command(params)); + const result: ListObjectsV2CommandOutput = await s3Client.send(new ListObjectsV2Command(params)); log("listObjects succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return result; @@ -483,7 +498,7 @@ export async function listObjects (options?: string | ListObjectsOptions): Promi // export for testing export async function getObject (key: string, lastModified?: Date): Promise { - init(); + const s3Client = init(); if (!key || !key.startsWith(KEYSPACE_PREFIX)) { key = KEYSPACE_PREFIX + (key || ""); } @@ -494,7 +509,7 @@ export async function getObject (key: string, lastModified?: Date): Promise { - init(); + const s3Client = init(); if (!file.key || !file.key.startsWith(KEYSPACE_PREFIX)) { file.key = KEYSPACE_PREFIX + (file.key || ""); } @@ -548,7 +563,7 @@ export async function uploadObject (file: S3File): Promise ({ Key, Value })), params }); @@ -582,7 +597,7 @@ export interface CopyObjectOptions { * @returns {CopyObjectCommandOutput} */ export async function copyObject ({ sourceFile, destinationFile, tags }: CopyObjectOptions): Promise { - init(); + const s3Client = init(); if (!sourceFile.key || !sourceFile.key.startsWith(KEYSPACE_PREFIX)) { sourceFile.key = KEYSPACE_PREFIX + (sourceFile.key || ""); } @@ -614,7 +629,7 @@ export async function copyObject ({ sourceFile, destinationFile, tags }: CopyObj }; try { log("copyObject request", LogLevel.DEBUG, Object.assign({}, params, { Body: undefined })); // Log it without the body - const result: CopyObjectCommandOutput = await config.s3Client.send(new CopyObjectCommand(params)); + const result: CopyObjectCommandOutput = await s3Client.send(new CopyObjectCommand(params)); log("copyObject succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return result; @@ -626,7 +641,7 @@ export async function copyObject ({ sourceFile, destinationFile, tags }: CopyObj // export for testing export async function deleteObject (s3FileKey: string): Promise { - init(); + const s3Client = init(); if (!s3FileKey || !s3FileKey.startsWith(KEYSPACE_PREFIX)) { s3FileKey = KEYSPACE_PREFIX + (s3FileKey || ""); } @@ -636,7 +651,7 @@ export async function deleteObject (s3FileKey: string): Promise { - init(); + const s3Client = init(); if (!s3FileKey || !s3FileKey.startsWith(KEYSPACE_PREFIX)) { s3FileKey = KEYSPACE_PREFIX + (s3FileKey || ""); } @@ -657,7 +672,7 @@ export async function getObjectTagging (s3FileKey: string): Promise { - init(); + const s3Client = init(); if (!key || !key.startsWith(KEYSPACE_PREFIX)) { key = KEYSPACE_PREFIX + (key || ""); } @@ -694,7 +709,7 @@ export async function putObjectTagging ({ key, tags }: PutObjectTaggingOptions): }; try { log("putObjectTagging request", LogLevel.DEBUG, params); - const result: PutObjectTaggingCommandOutput = await config.s3Client.send(new PutObjectTaggingCommand(params)); + const result: PutObjectTaggingCommandOutput = await s3Client.send(new PutObjectTaggingCommand(params)); log(`putObjectTagging ${key} succeeded`, LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return result; diff --git a/common/src/util/sqs.ts b/common/src/util/sqs.ts index 277504c9..4523f0f7 100644 --- a/common/src/util/sqs.ts +++ b/common/src/util/sqs.ts @@ -1,6 +1,7 @@ import { AGENT_APPLICATION_NAME, AGENT_ENV, + IS_RUNNING_IN_AWS, SYSTEM_NAME, getPrefix } from "./util"; @@ -26,17 +27,18 @@ import { } from "@aws-sdk/client-sqs"; import { LogLevel, log } from "./log"; import { SqsQueueType } from "../../types"; +import { fromIni } from "@aws-sdk/credential-providers"; export const QUEUE_URL_TEST: Map = new Map(); export const QUEUE_URL_SCALE_IN: Map = new Map(); export let QUEUE_URL_COMMUNICATION: string; // Export for testing so we can reset sqs -export const config: { sqsClient: SQSClient } = { - sqsClient: undefined as unknown as SQSClient +export const config: { sqsClient: (() => SQSClient) | undefined } = { + sqsClient: undefined }; // Put this in an init that runs later so we don't throw on start-up. -export function init () { +export function init (): SQSClient { // Where is your application name, system name, and service name concatenated with underscores, capitalized, and all dashes replaced with underscores. // The prefix for the injected keys would be the same as above since it is based upon the owning application's application and service names. // TL;DR - Scale is owned by the pewpewagent(s) and we inject the environment variable AGENT_ENV to the controller as a string delimited array @@ -113,11 +115,24 @@ export function init () { } // Create an SQS service object - if (config.sqsClient as unknown === undefined) { - config.sqsClient = new SQSClient({ - region: "us-east-1" - }); + if (config.sqsClient === undefined) { + if (IS_RUNNING_IN_AWS) { + // Create a fixed client that will be returned every time. + const sqsClient = new SQSClient({ + region: "us-east-1" + }); + config.sqsClient = () => sqsClient; + } else { + // https://github.com/aws/aws-sdk-js-v3/issues/3396 + // When not running in AWS, use the fromIni rather than automatic configuration, don't cache the credentials, + // and create a new instance every time + config.sqsClient = () => new SQSClient({ + credentials: fromIni({ ignoreCache: true }), + region: "us-east-1" + }); + } } + return config.sqsClient(); } let accessCallback: (date: Date) => void | undefined; @@ -145,7 +160,7 @@ function callAccessCallback (date: Date) { * @param sqsQueueName {string} name of the queue if there is more than one (controller) */ export function getQueueUrl (sqsQueueType: SqsQueueType, sqsQueueName?: string): string { - init(); + init(); // We have to call init to populate QUEUE_URL_TEST let queueUrl: string | undefined; log(`getQueueUrl(${sqsQueueType}, ${JSON.stringify(sqsQueueName)})`, LogLevel.DEBUG); if (sqsQueueType === SqsQueueType.Communications) { @@ -179,7 +194,7 @@ export function getQueueUrl (sqsQueueType: SqsQueueType, sqsQueueName?: string): * @returns SQSMessage or undefined */ export async function getNewTestToRun (): Promise { - init(); + init(); // We have to call init to populate QUEUE_URL_TEST // If you try to call this from a controller (that has multiple queues) we should throw. if (QUEUE_URL_TEST.size !== 1) { log(`Only Agents with a single QUEUE_URL_TEST can getNewTestsToRun(): QUEUE_URL_TEST.size=${QUEUE_URL_TEST.size}`, LogLevel.ERROR, QUEUE_URL_TEST); @@ -207,7 +222,7 @@ export async function getNewTestToRun (): Promise { * @returns SQSMessage or undefined */ export async function getCommunicationMessage (): Promise { - init(); + init(); // We have to call init to populate QUEUE_URL_COMMUNICATION const params: ReceiveMessageCommandInput = { AttributeNames: [ "All" @@ -231,7 +246,7 @@ export async function getCommunicationMessage (): Promise, sqsQueueName: string): Promise { - init(); + init(); // We have to call init to populate QUEUE_URL_TEST const messageKeys = Object.keys(messageAttributes); if (messageKeys.length === 0) { throw new Error("You must specify at least one Message Attribute"); @@ -263,7 +278,7 @@ export async function sendNewTestToRun (messageAttributes: Record): Promise { - init(); + init(); // We have to call init to populate QUEUE_URL_COMMUNICATION const messageKeys = Object.keys(messageAttributes); if (messageKeys.length === 0) { throw new Error("You must specify at least one Message Attribute"); @@ -304,7 +319,6 @@ export function deleteMessageByHandle ({ messageHandle, sqsQueueType, sqsQueueNa if (sqsQueueType === undefined) { throw new Error("sqsQueueType must be provided"); } - init(); const queueUrl: string = getQueueUrl(sqsQueueType, sqsQueueName); const params: DeleteMessageCommandInput = { QueueUrl: queueUrl, @@ -323,7 +337,6 @@ export function deleteMessageByHandle ({ messageHandle, sqsQueueType, sqsQueueNa if (sqsQueueType === undefined) { throw new Error("sqsQueueType must be provided"); } - init(); const queueUrl: string = getQueueUrl(sqsQueueType, sqsQueueName); const params: ChangeMessageVisibilityCommandInput = { QueueUrl: queueUrl, @@ -339,7 +352,6 @@ export function deleteMessageByHandle ({ messageHandle, sqsQueueType, sqsQueueNa * @param sqsQueueName {string} name of the queue. required for the non-communication queues */ export async function getQueueAttributesMap (sqsQueueType: SqsQueueType, sqsQueueName?: string): Promise | undefined> { - init(); const queueUrl: string = getQueueUrl(sqsQueueType, sqsQueueName); const params: GetQueueAttributesCommandInput = { QueueUrl: queueUrl, @@ -354,7 +366,7 @@ export async function getQueueAttributesMap (sqsQueueType: SqsQueueType, sqsQueu * @returns SQSMessage or undefined */ export async function getTestScalingMessage (): Promise { - init(); + init(); // We have to call init to populate QUEUE_URL_TEST // If you try to call this from a controller (that has multiple queues) we should throw. if (QUEUE_URL_SCALE_IN.size !== 1) { log(`Only Agents with a single QUEUE_URL_SCALE_IN can getTestScalingMessage(): QUEUE_URL_SCALE_IN.size=${QUEUE_URL_SCALE_IN.size}`, LogLevel.ERROR, QUEUE_URL_SCALE_IN); @@ -383,7 +395,7 @@ export async function getTestScalingMessage (): Promise * @returns messageId {string} */ export async function sendTestScalingMessage (sqsQueueName?: string): Promise { - init(); + init(); // We have to call init to populate QUEUE_URL_SCALE_IN const messageAttributes: Record = { Scale: { DataType: "String", @@ -419,7 +431,6 @@ export async function sendTestScalingMessage (sqsQueueName?: string): Promise { - init(); try { const scalingMessage: SQSMessage | undefined = await getTestScalingMessage(); log("refreshTestScalingMessage getTestScalingMessage scalingMessage", LogLevel.DEBUG, scalingMessage); @@ -444,7 +455,6 @@ export async function refreshTestScalingMessage (): Promise * Agent: Helper function to delete a message from the scaling queue (test finished) */ export async function deleteTestScalingMessage (): Promise { - init(); try { const scalingMessage: SQSMessage | undefined = await getTestScalingMessage(); log("deleteTestScalingMessage getTestScalingMessage scalingMessage", LogLevel.DEBUG, scalingMessage); @@ -464,10 +474,10 @@ export async function deleteTestScalingMessage (): Promise { // Export for testing export async function receiveMessage (params: ReceiveMessageCommandInput): Promise { - init(); + const sqsClient = init(); try { log("receiveMessage request", LogLevel.DEBUG, params); - const result: ReceiveMessageCommandOutput = await config.sqsClient.send(new ReceiveMessageCommand(params)); + const result: ReceiveMessageCommandOutput = await sqsClient.send(new ReceiveMessageCommand(params)); log("receiveMessage succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return result; @@ -479,10 +489,10 @@ export async function receiveMessage (params: ReceiveMessageCommandInput): Promi // Export for testing export async function sendMessage (params: SendMessageCommandInput): Promise { - init(); + const sqsClient = init(); try { log("sendMessage request", LogLevel.DEBUG, params); - const result: SendMessageCommandOutput = await config.sqsClient.send(new SendMessageCommand(params)); + const result: SendMessageCommandOutput = await sqsClient.send(new SendMessageCommand(params)); log("sendMessage succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return result; @@ -494,10 +504,10 @@ export async function sendMessage (params: SendMessageCommandInput): Promise { - init(); + const sqsClient = init(); try { log("deleteMessage request", LogLevel.DEBUG, params); - const result: DeleteMessageCommandOutput = await config.sqsClient.send(new DeleteMessageCommand(params)); + const result: DeleteMessageCommandOutput = await sqsClient.send(new DeleteMessageCommand(params)); log("deleteMessage succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return; @@ -509,10 +519,10 @@ export async function deleteMessage (params: DeleteMessageCommandInput): Promise // Export for testing export async function changeMessageVisibility (params: ChangeMessageVisibilityCommandInput): Promise { - init(); + const sqsClient = init(); try { log("changeMessageVisibility request", LogLevel.DEBUG, params); - const result: ChangeMessageVisibilityCommandOutput = await config.sqsClient.send(new ChangeMessageVisibilityCommand(params)); + const result: ChangeMessageVisibilityCommandOutput = await sqsClient.send(new ChangeMessageVisibilityCommand(params)); log("changeMessageVisibility succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return; @@ -523,10 +533,10 @@ export async function changeMessageVisibility (params: ChangeMessageVisibilityCo } export async function getQueueAttributes (params: GetQueueAttributesCommandInput): Promise { - init(); + const sqsClient = init(); try { log("getQueueAttributes request", LogLevel.DEBUG, params); - const result: GetQueueAttributesCommandOutput = await config.sqsClient.send(new GetQueueAttributesCommand(params)); + const result: GetQueueAttributesCommandOutput = await sqsClient.send(new GetQueueAttributesCommand(params)); log("getQueueAttributes succeeded", LogLevel.DEBUG, result); callAccessCallback(new Date()); // Update the last timestamp return result; @@ -537,7 +547,6 @@ export async function getQueueAttributes (params: GetQueueAttributesCommandInput } export async function cleanUpQueue (sqsQueueType: SqsQueueType, sqsQueueName?: string | undefined): Promise { - init(); const QueueUrl: string = getQueueUrl(sqsQueueType, sqsQueueName); const receiveMessageParams: ReceiveMessageCommandInput = { AttributeNames: ["All"], diff --git a/common/test/mock.ts b/common/test/mock.ts index 4e63009b..52c0f373 100644 --- a/common/test/mock.ts +++ b/common/test/mock.ts @@ -75,12 +75,15 @@ export function mockS3 (): AwsStub s3Client; UNIT_TEST_BUCKET_NAME = s3.BUCKET_NAME; UNIT_TEST_BUCKET_URL = `${UNIT_TEST_BUCKET_NAME}.s3.us-east-1.amazonaws.com`; UNIT_TEST_KEYSPACE_PREFIX = s3.KEYSPACE_PREFIX; - _mockedS3Instance = mockClient(s3Config.s3Client); _mockedS3Instance.on(DeleteObjectCommand).resolves({}); // There's no parameters, so just mock it _mockedS3Instance.on(PutObjectTaggingCommand).resolves({}); @@ -91,7 +94,7 @@ export function mockS3 (): AwsStub sqsClient; log("mockSqs created", LogLevel.DEBUG, { mockedSqsInstance: _mockedSqsInstance, sqs: sqsConfig.sqsClient }); // Always mock deleteMessage so we don't accidentally call it behind the scenes. Don't expose the call like the others _mockedSqsInstance.on(DeleteMessageCommand).resolves({}); @@ -242,7 +248,7 @@ export function resetMockSqs (): void { if (_mockedSqsInstance !== undefined) { _mockedSqsInstance.reset(); _mockedSqsInstance.restore(); - sqsConfig.sqsClient = undefined as any; + sqsConfig.sqsClient = undefined; _mockedSqsInstance = undefined; } } diff --git a/common/tsconfig.lib.json b/common/tsconfig.lib.json new file mode 100644 index 00000000..2c74719d --- /dev/null +++ b/common/tsconfig.lib.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*", + "types/**/*", + ], + "exclude": ["node_modules", "dist", "test", "integration"] +} \ No newline at end of file diff --git a/controller/hooks/post-build/000-run-build b/controller/hooks/post-build/000-run-build index af7a8312..3f83f14d 100755 --- a/controller/hooks/post-build/000-run-build +++ b/controller/hooks/post-build/000-run-build @@ -9,8 +9,8 @@ nvm install v18 export NODE_ENV=production export BASE_PATH=/pewpew/load-test export AUTH_MODE=okta -export LoggingLevel=info -export LoggingLevelConsole=fatal +export LOGGING_LEVEL=info +export LOGGING_LEVEL_CONSOLE=fatal echo "BASE_PATH: \"${BASE_PATH}\"" # Now run whatever node or npm commands you need to run diff --git a/controller/package.json b/controller/package.json index 3b588ffa..2a82bb95 100644 --- a/controller/package.json +++ b/controller/package.json @@ -1,6 +1,6 @@ { "name": "@fs/ppaas-controller", - "version": "3.3.5", + "version": "3.3.6", "description": "Controller Service for running pewpew tests", "private": true, "scripts": { diff --git a/controller/pages/api/util/clientutil.ts b/controller/pages/api/util/clientutil.ts index 7b9297e9..0bc44b36 100644 --- a/controller/pages/api/util/clientutil.ts +++ b/controller/pages/api/util/clientutil.ts @@ -73,7 +73,7 @@ export function getHostUrl (req: IncomingMessage | undefined): string { */ export function formatError (error: unknown): string { // Check if it's an AxiosError - if ((error as AxiosError).isAxiosError) { + if ((error as AxiosError)?.isAxiosError) { const axiosError: AxiosError = error as AxiosError; log("formatError AxiosError", LogLevel.DEBUG, { config: axiosError.config , response: axiosError.response }); const methodText = `${axiosError.config?.method?.toUpperCase() || ""} ${axiosError.config?.url || "request"} failed`; diff --git a/controller/pages/api/util/log.ts b/controller/pages/api/util/log.ts index 6af7e8c1..9774e7cd 100644 --- a/controller/pages/api/util/log.ts +++ b/controller/pages/api/util/log.ts @@ -10,7 +10,7 @@ export enum LogLevel { // Have to check for null on this since the tsc test compile it will be, but nextjs will have a publicRuntimeConfig const publicRuntimeConfig: any = getConfig() && getConfig().publicRuntimeConfig ? getConfig().publicRuntimeConfig : {}; -const logDebug: boolean = publicRuntimeConfig.LoggingLevel === "debug"; +const logDebug: boolean = publicRuntimeConfig.LOGGING_LEVEL === "debug"; // Take a variable list of objects export function log (message: string, level: LogLevel = LogLevel.INFO, ...datas: any[]) { diff --git a/controller/pages/api/util/s3.ts b/controller/pages/api/util/s3.ts index 61158d35..1831195f 100644 --- a/controller/pages/api/util/s3.ts +++ b/controller/pages/api/util/s3.ts @@ -50,12 +50,12 @@ export async function getS3Response ({ request, response, filename, s3Folder, re // https://stackoverflow.com/questions/74699607/how-to-pipe-to-next-js-13-api-response if (redirectToS3) { try { - s3.init(); + const s3Client = s3.init(); const command = new GetObjectCommand({ Bucket: s3.BUCKET_NAME, Key: key.startsWith(s3.KEYSPACE_PREFIX) ? key : (s3.KEYSPACE_PREFIX + key) }); - const presignedUrl = await getSignedUrl(s3.config.s3Client, command, { expiresIn: 60 }); + const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 60 }); log(`${key} presignedUrl: ${presignedUrl}`, LogLevel.DEBUG, presignedUrl); response.writeHead(302, { Location: presignedUrl }); response.end(); diff --git a/controller/pages/index.tsx b/controller/pages/index.tsx index 745f36f4..a6ab235f 100644 --- a/controller/pages/index.tsx +++ b/controller/pages/index.tsx @@ -26,8 +26,6 @@ import axios, { AxiosResponse } from "axios"; import { formatError, formatPageHref, isTestData } from "./api/util/clientutil"; import Div from "../components/Div"; import { Layout } from "../components/Layout"; -// Must reference the PpaasTestId file directly or we pull in stuff that won't compile on the client -import { PpaasTestId } from "@fs/ppaas-common/dist/src/ppaastestid"; import { TestInfo } from "../components/TestInfo"; import { TestManager } from "./api/util/testmanager"; import { TestResults } from "../components/TestResults"; @@ -35,7 +33,6 @@ import { TestStatus } from "@fs/ppaas-common/dist/types"; import { TestsList } from "../components/TestsList"; import { authPage } from "./api/util/authserver"; import getConfig from "next/config"; -import { getPewPewErrors } from "./api/error/[yamlFile]/[dateString]"; import styled from "styled-components"; import { useRouter } from "next/router"; @@ -60,8 +57,6 @@ const TestStatusSection = styled(Div)` // What this returns or calls from the parents export interface TestStatusProps { testData: TestData | undefined; - pewpewStdErrors?: string[]; - pewpewStdErrorsTruncated?: boolean; allTests: AllTests | undefined; testIdSearch?: string; searchTestResult?: TestData[]; @@ -73,6 +68,8 @@ export interface TestStatusProps { export interface TestStatusState { testIdSearch: string; searchTestResult: TestData[] | undefined; + pewpewStdErrors?: string[]; + pewpewStdErrorsTruncated?: boolean; error: string | undefined; } @@ -81,8 +78,6 @@ const noTestsFoundEror = (searchString: string, searchExtension?: string | strin const TestStatusPage = ({ testData, - pewpewStdErrors, - pewpewStdErrorsTruncated, allTests, errorLoading, authPermission, @@ -93,6 +88,8 @@ const TestStatusPage = ({ const defaultState: TestStatusState = { testIdSearch: propsTestIdSearch || "", searchTestResult: propsSearchTestResult, + pewpewStdErrors: undefined, + pewpewStdErrorsTruncated: undefined, error: errorLoading }; const [state, setFormData] = useState(defaultState); @@ -135,6 +132,52 @@ const TestStatusPage = ({ } }, [testData?.status]); + // Lazy load the console errors on the client-side + useEffect(() => { + log("console errors useEffect", LogLevel.DEBUG, testData?.s3Folder); + if (testData) { + const url = formatPageHref(API_ERROR_FORMAT(testData.s3Folder)); + log("console errors url", LogLevel.DEBUG, url); + // If we're client-side the cookie gets passed automatically + axios.get(url).then((response: AxiosResponse) => { + log("console error data response: " + response.status, LogLevel.DEBUG, response.statusText); + let pewpewStdErrors: string[] | undefined; + let pewpewStdErrorsTruncated: boolean | undefined; + // Get the text contents + const pewpewErrorText: string | undefined = response.data; + log("pewpewErrorText", LogLevel.DEBUG, { type: typeof pewpewErrorText, length: pewpewErrorText?.length }); + if (pewpewErrorText && pewpewErrorText.length > 0) { + // Split and remove empty lines + pewpewStdErrors = pewpewErrorText.split("\n").filter((line: string) => line); + // https://fhjira.churchofjesuschrist.org/browse/SYSTEST-1115 + if (pewpewStdErrors.length > TEST_ERRORS_MAX_DISPLAYED) { + // Cap this at TEST_ERRORS_MAX_DISPLAYED length + pewpewStdErrors = pewpewStdErrors.slice(0, TEST_ERRORS_MAX_DISPLAYED); + // Set as truncated + pewpewStdErrorsTruncated = true; + } + // Truncate line length + pewpewStdErrors = pewpewStdErrors.map((line: string) => { + if (line.length > TEST_ERRORS_MAX_LINE_LENGTH) { + pewpewStdErrorsTruncated = true; + return line.substring(0, TEST_ERRORS_MAX_LINE_LENGTH) + " ..."; + } + return line; + }); + log("pewpewStdErrors", LogLevel.DEBUG, pewpewStdErrors.length); + setState({ pewpewStdErrors, pewpewStdErrorsTruncated }); + } else { + setState({ pewpewStdErrors: undefined, pewpewStdErrorsTruncated: undefined }); + } + }).catch((error: unknown) => { + log("Could not retrieve the console errors", LogLevel.WARN, error); + setState({ pewpewStdErrors: ["Could not retrieve the console errors:", formatError(error)], pewpewStdErrorsTruncated: true }); + }); + } else { + setState({ pewpewStdErrors: undefined, pewpewStdErrorsTruncated: undefined }); + } + }, [testData?.testId]); + let body: JSX.Element =
Unknown Error
; if (testData) { body = @@ -142,7 +185,7 @@ const TestStatusPage = ({ - {(testData.errors || pewpewStdErrors) && + {(testData.errors || state.pewpewStdErrors) && {testData.errors && Errors during Test @@ -151,14 +194,14 @@ const TestStatusPage = ({ } - {pewpewStdErrors && + {state.pewpewStdErrors && Pewpew Console Standard Errors - {pewpewStdErrorsTruncated && /** If pewpewStdErrors are truncated, link to full results */ + {state.pewpewStdErrorsTruncated && /** If pewpewStdErrors are truncated, link to full results */ Errors Truncated - Click for full log }
    - {pewpewStdErrors.map((error: string, index: number) =>
  • {error}
  • )} + {state.pewpewStdErrors.map((error: string, index: number) =>
  • {error}
  • )}
} @@ -296,13 +339,6 @@ export const getServerSideProps: GetServerSideProps = // If we get more than one testId, just return all, don't try to pick one if (ctx.query?.testId && !Array.isArray(ctx.query.testId)) { const testId: string = ctx.query.testId; - let ppaasTestId: PpaasTestId; - try { - ppaasTestId = PpaasTestId.getFromTestId(testId); - } catch (error) { - logServer(`Could not parse ${testId} into a PpaasTestId`, LogLevelServer.ERROR, error); - throw error; - } // If we're client-side the cookie gets passed. Server side it doesn't const testDataResponse: TestManagerResponse = await TestManager.getTest(testId); if (testDataResponse.status >= 300) { @@ -311,40 +347,9 @@ export const getServerSideProps: GetServerSideProps = // Convert it to json const testData: TestData = (testDataResponse as TestDataResponse).json; - // If we're client-side the cookie gets passed. Server side it doesn't - let pewpewStdErrors: string[] | undefined; - let pewpewStdErrorsTruncated: boolean | undefined; - // Get the text contents - const { yamlFile, dateString } = ppaasTestId; - const pewpewErrorText: string | undefined = await getPewPewErrors({ yamlFile, dateString }); - logServer("pewpewErrorText", LogLevelServer.DEBUG, pewpewErrorText); - if (pewpewErrorText && pewpewErrorText.length > 0) { - // Split and remove empty lines - pewpewStdErrors = pewpewErrorText.split("\n").filter((line: string) => line); - // https://fhjira.churchofjesuschrist.org/browse/SYSTEST-1115 - if (pewpewStdErrors.length > TEST_ERRORS_MAX_DISPLAYED) { - // Cap this at TEST_ERRORS_MAX_DISPLAYED length - pewpewStdErrors = pewpewStdErrors.slice(0, TEST_ERRORS_MAX_DISPLAYED); - // Set as truncated - pewpewStdErrorsTruncated = true; - } - // Truncate line length - pewpewStdErrors = pewpewStdErrors.map((line: string) => { - if (line.length > TEST_ERRORS_MAX_LINE_LENGTH) { - pewpewStdErrorsTruncated = true; - return line.substring(0, TEST_ERRORS_MAX_LINE_LENGTH) + " ..."; - } - return line; - }); - logServer("pewpewStdErrors", LogLevelServer.DEBUG, pewpewStdErrors); - } - - // if (testData.status === TestStatus.Scheduled) return { props: { testData, - pewpewStdErrors, - pewpewStdErrorsTruncated, allTests: undefined, errorLoading: undefined, authPermission: authPermissions.authPermission diff --git a/controller/test/mock.ts b/controller/test/mock.ts index 7780fcab..658ef98f 100644 --- a/controller/test/mock.ts +++ b/controller/test/mock.ts @@ -98,9 +98,12 @@ export function mockS3 (): AwsStub s3Client; _mockedS3Instance.on(DeleteObjectCommand).resolves({}); // There's no parameters, so just mock it _mockedS3Instance.on(PutObjectTaggingCommand).resolves({}); @@ -111,7 +114,7 @@ export function mockS3 (): AwsStub sqsClient; log("mockSqs created", LogLevel.DEBUG, { mockedSqsInstance: _mockedSqsInstance, sqs: sqsConfig.sqsClient }); // Always mock deleteMessage so we don't accidentally call it behind the scenes. Don't expose the call like the others _mockedSqsInstance.on(DeleteMessageCommand).resolves({}); @@ -270,7 +276,7 @@ export function mockSqs (): AwsStub void; defaultYaml: boolean; /** State of Authenticated checkbox */ - authenticated: boolean - urls: PewPewAPI[], + authenticated: boolean; + urls: PewPewAPI[]; + peakLoad?: string | undefined; } export interface EndpointsState { @@ -72,7 +73,7 @@ export interface EndpointsState { defaultHeaders: boolean; } -export const newUrl = (deaultHeaders: boolean, authenticated: boolean, point?: HarEndpoint): PewPewAPI => { +export const newUrl = (deaultHeaders: boolean, authenticated: boolean, peakLoad?: string, point?: HarEndpoint): PewPewAPI => { const pointHeaders: PewPewHeader[] = point?.headers.map(({ name, value }: HarHeader): PewPewHeader => ({ id: uniqueId(), name, value })) || []; const pewpewHeaders: PewPewHeader[] = deaultHeaders ? getDefaultHeaders(authenticated) @@ -80,16 +81,16 @@ export const newUrl = (deaultHeaders: boolean, authenticated: boolean, point?: H return { id: uniqueId(), url: point?.url || "", - hitRate: "1hpm", + hitRate: peakLoad ? "${" + peakLoad + "}" : "1hpm", headers: [...pewpewHeaders, ...pointHeaders], method: point?.method || "GET", authorization: null }; }; -export const Endpoints = ({ urls, ...props }: EndpointsProps) => { +export const Endpoints = ({ urls, peakLoad, ...props }: EndpointsProps) => { const defaultState: EndpointsState = { - hitRate: "", + hitRate: peakLoad ? "${" + peakLoad + "}" : "1hpm", defaultHeaders: props.defaultYaml }; /** Map to keep id's unique */ @@ -104,6 +105,10 @@ export const Endpoints = ({ urls, ...props }: EndpointsProps) => { updateState({ defaultHeaders: props.defaultYaml }); }, [props.defaultYaml]); + useEffect(() => { + updateState({ hitRate: peakLoad ? "${" + peakLoad + "}" : "1hpm" }); + }, [peakLoad]); + const handleClickDefault = (event: React.ChangeEvent) => { updateState({ defaultHeaders: event.target.checked }); // URLs will update via the props passed in @@ -112,7 +117,7 @@ export const Endpoints = ({ urls, ...props }: EndpointsProps) => { // Adds endpoint to array // Called from clicking add button, or when endpoints are sent from App.js through refs.child.updatePoints const addUrl = () => { - props.addUrl(newUrl(state.defaultHeaders, props.authenticated)); + props.addUrl(newUrl(state.defaultHeaders, props.authenticated, peakLoad ? peakLoad : "1hpm")); }; // Updates the hit rate for each endpoint when "update" button is pressed @@ -124,7 +129,6 @@ export const Endpoints = ({ urls, ...props }: EndpointsProps) => { props.changeUrl({ ...url, hitRate }); } } - updateState({ hitRate: "" }); }; // Updates the value of hit rate to be changed in all urls when update button is pressed or enter key is pressed diff --git a/guide/results-viewer-react/src/components/YamlEndpoints/story.tsx b/guide/results-viewer-react/src/components/YamlEndpoints/story.tsx index 9d3fff37..ebc4e42f 100644 --- a/guide/results-viewer-react/src/components/YamlEndpoints/story.tsx +++ b/guide/results-viewer-react/src/components/YamlEndpoints/story.tsx @@ -38,7 +38,8 @@ const props: EndpointsProps = { }, defaultYaml: false, urls: [], - authenticated: false + authenticated: false, + peakLoad: "1hpm" }; const propsLoaded: EndpointsProps = { diff --git a/guide/results-viewer-react/src/components/YamlLoadPatterns/index.tsx b/guide/results-viewer-react/src/components/YamlLoadPatterns/index.tsx index 60b416ca..c5a2a2dc 100644 --- a/guide/results-viewer-react/src/components/YamlLoadPatterns/index.tsx +++ b/guide/results-viewer-react/src/components/YamlLoadPatterns/index.tsx @@ -1,7 +1,7 @@ import { CSSTransition, TransitionGroup } from "react-transition-group"; import { Checkbox, Div, InputsDiv, Label, Span} from "../YamlStyles"; -import React, { useEffect, useRef, useState } from "react"; -import { PewPewLoadPattern } from "../../util/yamlwriter"; +import { PewPewLoadPattern, PewPewVars } from "../../util/yamlwriter"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import QuestionBubble from "../YamlQuestionBubble"; import { uniqueId } from "../../util/clientutil"; @@ -14,13 +14,14 @@ export interface LoadPatternProps { changePattern: (pewpewPattern: PewPewLoadPattern) => void; defaultYaml: boolean; patterns: PewPewLoadPattern[]; + vars: PewPewVars[]; } interface LoadPatternState { defaultPatterns: boolean; } -export const OVER_REGEX = new RegExp("^((((\\d+)\\s?(h|hr|hrs|hour|hours))\\s?)?(((\\d+)\\s?(m|min|mins|minute|minutes))\\s?)?(((\\d+)\\s?(s|sec|secs|second|seconds)))?)$"); +export const OVER_REGEX = new RegExp("^((((\\d+)\\s?(h|hr|hrs|hour|hours))\\s?)?(((\\d+)\\s?(m|min|mins|minute|minutes))\\s?)?(((\\d+)\\s?(s|sec|secs|second|seconds)))?)$|^\\$\\{[a-zA-Z][a-zA-Z0-9]*\\}$"); export const NUMBER_REGEX = new RegExp("^[+]?([0-9]+(?:[\\.][0-9]*)?|\\.[0-9]+)$"); export const PATTERNS = "patterns"; export const RAMP_PATTERN = "rampPattern"; @@ -30,19 +31,21 @@ export const newLoadPattern = (patternId: string = uniqueId()): PewPewLoadPatter export const newRampLoadPattern = (): PewPewLoadPattern => ({ id: RAMP_PATTERN, from: "10", to: "100", over: "15m" }); export const newLoadLoadPattern = (): PewPewLoadPattern => ({ id: LOAD_PATTERN, from: "100", to: "100", over: "15m" }); - const errorColor: React.CSSProperties = { color: "red" }; -export function LoadPatterns ({ defaultYaml, patterns, ...props }: LoadPatternProps) { +export function LoadPatterns ({ defaultYaml, patterns, vars, ...props }: LoadPatternProps) { const defaultState: LoadPatternState = { defaultPatterns: defaultYaml }; /** Map to keep id's unique */ - const loadPatternsMap = new Map(patterns.map((pewpewPattern) => ([pewpewPattern.id, pewpewPattern]))); + // const loadPatternsMap = new Map(patterns.map((pewpewPattern) => ([pewpewPattern.id, pewpewPattern]))); const [state, setState] = useState(defaultState); const updateState = (newState: Partial) => setState((oldState): LoadPatternState => ({ ...oldState, ...newState })); + const rampTime = useMemo(() => vars.find((pewpewVar) => pewpewVar.id === "rampTime"), [vars]); + const loadTime = useMemo(() => vars.find((pewpewVar) => pewpewVar.id === "loadTime"), [vars]); + useEffect(() => { switchDefault(defaultYaml); }, [defaultYaml]); @@ -52,23 +55,44 @@ export function LoadPatterns ({ defaultYaml, patterns, ...props }: LoadPatternPr }; const switchDefault = (newChecked: boolean) => { - if (newChecked && !loadPatternsMap.has(RAMP_PATTERN)) { + if (newChecked && !patterns.find((p) => p.id === RAMP_PATTERN)) { // Add it (will update the map when it comes back in via props) - props.addPattern(newRampLoadPattern()); - } else if (!newChecked && loadPatternsMap.has(RAMP_PATTERN)) { + props.addPattern(rampTime ? {...newRampLoadPattern(), over: "${" + rampTime.name + "}"} : newRampLoadPattern()); + } else if (!newChecked && patterns.find((p) => p.id === RAMP_PATTERN)) { // Remove it (will update the map when it comes back in via props) props.deletePattern(RAMP_PATTERN); } - if (newChecked && !loadPatternsMap.has(LOAD_PATTERN)) { + if (newChecked && !patterns.find((p) => p.id === LOAD_PATTERN)) { // Add it (will update the map when it comes back in via props) - props.addPattern(newLoadLoadPattern()); - } else if (!newChecked && loadPatternsMap.has(LOAD_PATTERN)) { + props.addPattern(loadTime ? {...newLoadLoadPattern(), over: "${" + loadTime.name + "}"} : newLoadLoadPattern()); + } else if (!newChecked && patterns.find((p) => p.id === LOAD_PATTERN)) { // Remove it (will update the map when it comes back in via props) props.deletePattern(LOAD_PATTERN); } updateState({ defaultPatterns: newChecked }); }; + useEffect(() => { + handleRampTimeChange(); + handleLoadTimeChange(); + }, [vars]); + + const handleRampTimeChange = () => { + if (!patterns.find((p) => p.id === RAMP_PATTERN)) { + props.addPattern(rampTime ? {...newRampLoadPattern(), over: "${" + rampTime.name + "}"} : newRampLoadPattern()); + } else { + changePattern(patterns.find((p) => p.id === RAMP_PATTERN) as PewPewLoadPattern, "over", rampTime ? "${" + rampTime.name + "}" : "15m"); + } + }; + + const handleLoadTimeChange = () => { + if (!patterns.find((p) => p.id === LOAD_PATTERN)) { + props.addPattern(loadTime ? {...newLoadLoadPattern(), over: "${" + loadTime.name + "}"} : newLoadLoadPattern()); + } else { + changePattern(patterns.find((p) => p.id === LOAD_PATTERN) as PewPewLoadPattern, "over", loadTime ? "${" + loadTime.name + "}" : "15m"); + } + }; + const changePattern = (pewpewPattern: PewPewLoadPattern, type: PewPewLoadPatternStringType, value: string) => { pewpewPattern[type] = value; props.changePattern(pewpewPattern); @@ -105,7 +129,7 @@ export function LoadPatterns ({ defaultYaml, patterns, ...props }: LoadPatternPr - {Array.from(loadPatternsMap.values()).map((pewpewPattern: PewPewLoadPattern) => { + {Array.from(patterns.values()).map((pewpewPattern: PewPewLoadPattern) => { // TODO: Do we want to check if they're greater than 0? const validFrom: boolean = !pewpewPattern.from || NUMBER_REGEX.test(pewpewPattern.from); const validTo: boolean = NUMBER_REGEX.test(pewpewPattern.to); diff --git a/guide/results-viewer-react/src/components/YamlLoadPatterns/story.tsx b/guide/results-viewer-react/src/components/YamlLoadPatterns/story.tsx index 756b11d3..4952e27a 100644 --- a/guide/results-viewer-react/src/components/YamlLoadPatterns/story.tsx +++ b/guide/results-viewer-react/src/components/YamlLoadPatterns/story.tsx @@ -23,7 +23,8 @@ const props: LoadPatternProps = { console.log("changing LoadPattern " + pewpewPattern.id, pewpewPattern); }, defaultYaml: false, - patterns: [] + patterns: [], + vars: [] }; const propsDefault: LoadPatternProps = { ...props, diff --git a/guide/results-viewer-react/src/components/YamlUrls/index.tsx b/guide/results-viewer-react/src/components/YamlUrls/index.tsx index 48b49e3a..da697616 100644 --- a/guide/results-viewer-react/src/components/YamlUrls/index.tsx +++ b/guide/results-viewer-react/src/components/YamlUrls/index.tsx @@ -44,7 +44,7 @@ export interface UrlState { passed: boolean; } -export const HIT_RATE_REGEX: RegExp = new RegExp("^(\\d+)hp(h|m|s)$"); +export const HIT_RATE_REGEX: RegExp = new RegExp("^(\\d+)hp(h|m|s)$|^\\$\\{[a-zA-Z][a-zA-Z0-9]*\\}$"); export const URLS = "urls"; const EMPTY_HEADER = "emptyHeader"; const DEFAULT_HEADERS = "defaultHeaders"; diff --git a/guide/results-viewer-react/src/components/YamlVars/VarInput.tsx b/guide/results-viewer-react/src/components/YamlVars/VarInput.tsx new file mode 100644 index 00000000..d957d81d --- /dev/null +++ b/guide/results-viewer-react/src/components/YamlVars/VarInput.tsx @@ -0,0 +1,47 @@ +import { Div, Label, Span} from "../YamlStyles"; +import { PewPewVars } from "../../util/yamlwriter"; +import { PewPewVarsStringType } from "."; +import QuestionBubble from "../YamlQuestionBubble"; +import React from "react"; + + +interface VarInputProps { + pewpewVar: PewPewVars; + changeVars:(pewpewVar: PewPewVars, type: PewPewVarsStringType, value: string) => void; + deleteVar: (varId: string) => void; +} + +const VarInput = ({ pewpewVar, changeVars, deleteVar }: VarInputProps): JSX.Element => { + + return ( +
+ + + + changeVars(pewpewVar, "name", event.target.value)} + name={pewpewVar.id} + value={pewpewVar.name} + id="name" + /> + + + + + changeVars(pewpewVar, "value", event.target.value)} + name={pewpewVar.id} + value={pewpewVar.value} + id="value" + /> + + +
+ ); +}; + +export default VarInput; \ No newline at end of file diff --git a/guide/results-viewer-react/src/components/YamlVars/index.tsx b/guide/results-viewer-react/src/components/YamlVars/index.tsx index e6ad7f7a..db203e3a 100644 --- a/guide/results-viewer-react/src/components/YamlVars/index.tsx +++ b/guide/results-viewer-react/src/components/YamlVars/index.tsx @@ -9,6 +9,7 @@ import { } from "../../util/yamlwriter"; import React, { useEffect, useRef, useState } from "react"; import QuestionBubble from "../YamlQuestionBubble"; +import VarInput from "./VarInput"; import { uniqueId } from "../../util/clientutil"; export type PewPewVarsStringType = "name" | "value"; @@ -176,57 +177,22 @@ export function Vars ({ authenticated, defaultYaml, ...props }: VarsProps) {
- - - - ) => switchDefault(SESSION_ID, event.target.checked)} checked={state.sessionId}/> - - - - - ) => switchDefault(RAMP_TIME, event.target.checked)} checked={state.rampTime}/> - - - - - ) => switchDefault(LOAD_TIME, event.target.checked)} checked={state.loadTime}/> - - - - - ) => switchDefault(PEAK_LOAD, event.target.checked)} checked={state.peakLoad}/> - + {[SESSION_ID, RAMP_TIME, LOAD_TIME, PEAK_LOAD].map((varName) => { + const stateKey = varName as DefaultVariablesType; + // TODO: Should these be read only? + return ( + + + + ) => switchDefault(varName as keyof DefaultVariables, event.target.checked)} checked={state[stateKey]}/> + + ); + })}
{Array.from(varsMap.values()).map((pewpewVar: PewPewVars) => ( -
- - - - changeVars(pewpewVar, "name", event.target.value)} - name={pewpewVar.id} - value={pewpewVar.name} - id="name" - /> - - - - - changeVars(pewpewVar, "value", event.target.value)} - name={pewpewVar.id} - value={pewpewVar.value} - id="value" - /> - - -
+
))}
diff --git a/guide/results-viewer-react/src/components/YamlViewer/index.tsx b/guide/results-viewer-react/src/components/YamlViewer/index.tsx index 3a8af568..7dfa0cde 100644 --- a/guide/results-viewer-react/src/components/YamlViewer/index.tsx +++ b/guide/results-viewer-react/src/components/YamlViewer/index.tsx @@ -16,16 +16,22 @@ interface Size { height: number | undefined; } -const minRows: number = 20; -const maxRows: number = 80; - export const YamlViewer = ({ yamlFilename, yamlContents, loading, error }: YamlViewerProps): JSX.Element => { const size: Size = useWindowSize(); if (!size.width || size.width > 800) { size.width = 800; } - const yamlRows = yamlContents ? yamlContents.split("\n").length : minRows; - const rowCount = Math.min(maxRows, yamlRows); + + const previewStyle: React.CSSProperties = { + maxHeight: "270px", + overflow: "auto", + border: "1px solid #ccc", + padding: "10px", + backgroundColor: "#2e3438", + textAlign: "start", + minWidth: "500px", + maxWidth: "700px" + }; return ( @@ -34,15 +40,11 @@ export const YamlViewer = ({ yamlFilename, yamlContents, loading, error }: YamlV } {error && Error: {error}} {(loading || yamlContents) && -