Skip to content

Commit

Permalink
Adding toaster, dynamically checking pewpewVersion (#201)
Browse files Browse the repository at this point in the history
* adding files to dynamically show latest
pewpew version on Toaster

* cleaning up code for lintter to pass

* refactoring code to use tags in S3

* removing files from tsconfig

* fixing lint rules

* fixing spacing as local linter didnt catch those but the build did

* missed one space

* adding tests, cleaning up files

* removing trailing spaces failing on build

* adding DS_Store - make specific file to gitignore

* updating storybook to show pewpew latest version output
  • Loading branch information
bryan-e-lopez authored Mar 18, 2024
1 parent 00bfbdb commit a8bee2d
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ testmerge.json
.env.development.local*
.env.test.local*
.env.production.local*

.DS_Store
5 changes: 5 additions & 0 deletions controller/components/PewPewVersions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Danger } from "../Alert";
import Div from "../Div";
import React from "react";
import Toaster from "../Toaster";
import styled from "styled-components";

const VersionDiv = styled(Div)`
Expand All @@ -24,6 +25,7 @@ const VersionDivSelect = styled(Div)`
export interface VersionInitalProps {
pewpewVersion: string;
pewpewVersions: string[];
latestPewPewVersion: string;
loading: boolean;
error: boolean;
}
Expand All @@ -38,12 +40,14 @@ export const PewPewVersions = ({
name,
pewpewVersion,
onChange,
latestPewPewVersion: latestPewPewTag,
pewpewVersions = [],
loading,
error
}: VersionProps) => {
// console.log("PewPewVersions state", { pewpewVersions, loading, error });
let optionItems: JSX.Element[] | undefined;
const toasterMessage: string = "IMPORTANT: Please configure your YAML properly!!! The latest version of Pewpew is set to: " + latestPewPewTag;
if (pewpewVersions && pewpewVersions.length > 0) {
optionItems = pewpewVersions.map((version: string) => (<option value={version} key={version}>{version}</option>));
}
Expand All @@ -53,6 +57,7 @@ export const PewPewVersions = ({
{loading && <VersionDivSelect>Loading...</VersionDivSelect>}
{!loading && !error && <VersionDivSelect><select name={name} value={pewpewVersion} onChange={onChange}>{optionItems} </select></VersionDivSelect>}
{error && <VersionDivSelect><Danger>Could not load the current PewPew Versions</Danger></VersionDivSelect>}
<Toaster id="toaster" message={toasterMessage}/>
</VersionDiv>
);
};
Expand Down
5 changes: 4 additions & 1 deletion controller/components/PewPewVersions/initialProps.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { LogLevel, log } from "@fs/ppaas-common";
import { getCurrentPewPewLatestVersion, getPewPewVersionsInS3 } from "../../pages/api/util/pewpew";
import { API_PEWPEW } from "../../types";
import { VersionInitalProps } from ".";
import { getPewPewVersionsInS3 } from "../../pages/api/util/pewpew";
import { latestPewPewVersion } from "../../pages/api/util/clientutil";

export const getServerSideProps = async (): Promise<VersionInitalProps> => {
try {
const pewpewVersions: string[] = await getPewPewVersionsInS3();
const currentPewPewLatestVersion: string | undefined = await getCurrentPewPewLatestVersion();
log("getPewPewVersionsInS3", LogLevel.DEBUG, pewpewVersions);
// Grab the response
// console.log("PewPewVersions pewpewVersions: " + JSON.stringify(pewpewVersions), pewpewVersions);
Expand All @@ -16,6 +17,7 @@ export const getServerSideProps = async (): Promise<VersionInitalProps> => {
return {
pewpewVersion: latestPewPewVersion, // We always want to default to latest
pewpewVersions,
latestPewPewVersion: currentPewPewLatestVersion || "unknown",
loading: false,
error: false
};
Expand All @@ -25,6 +27,7 @@ export const getServerSideProps = async (): Promise<VersionInitalProps> => {
return {
pewpewVersion: "",
pewpewVersions: [],
latestPewPewVersion: "unknown",
loading: false,
error: true
};
Expand Down
5 changes: 3 additions & 2 deletions controller/components/PewPewVersions/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ const props: VersionProps = {
console.log("newVal: ", newVal);
},
pewpewVersions: ["0.1.1", "0.1.2", "0.1.3", "0.1.4", "0.1.5", latestPewPewVersion, "0.1.6"],
latestPewPewVersion: "0.1.6",
loading: false,
error: false
};
const propsLoading: VersionProps = { ...props, pewpewVersions: [], loading: true };
const propsError: VersionProps = { ...props, error: true };
const propsLoading: VersionProps = { ...props, pewpewVersions: [], latestPewPewVersion: "unknown", loading: true };
const propsError: VersionProps = { ...props, error: true, latestPewPewVersion: "unknown" };

export default {
title: "PewPewVersions"
Expand Down
2 changes: 2 additions & 0 deletions controller/components/StartTestForm/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const versionInitalPropsEmpty: VersionInitalProps = {
pewpewVersion: "",
loading: false,
pewpewVersions: [],
latestPewPewVersion: "unknown",
error: true
};
const queueInitialProps: QueueInitialProps = {
Expand All @@ -50,6 +51,7 @@ const versionInitalProps: VersionInitalProps = {
pewpewVersion: "",
loading: false,
pewpewVersions: [latestPewPewVersion, "0.5.10", "0.5.11", "0.5.12"],
latestPewPewVersion: "0.5.12",
error: false
};
let ppaasTestId: PpaasTestId;
Expand Down
49 changes: 49 additions & 0 deletions controller/components/Toaster/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useEffect } from "react";
import styled, { keyframes } from "styled-components";
import { Info } from "../Alert";

interface ToasterProps {
message: string; // Ensure message prop is of type string
duration?: number;
id: string; // Unique ID for the toaster
}

// Fade-in animation
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;

// Fade-out animation
const fadeOut = keyframes`
from { opacity: 1; }
to { opacity: 0; }
`;

const Container = styled(Info)`
position: fixed;
bottom: 20px;
right: 20px;
width: 30%;
font-size: 17px;
animation: ${fadeIn} 0.3s ease-in-out forwards;
&.fade-out { animation: ${fadeOut} 0.3s ease-in-out forwards; }
`;

const Toaster: React.FC<ToasterProps> = ({ id, message, duration = 7000 }) => {
useEffect(() => {
setTimeout(() => {
const toasterElement = document.getElementById(id);
if (toasterElement) {
toasterElement.classList.add("fade-out");
setTimeout(() => {
toasterElement.remove();
}, 300); // Wait for fade-out animation to complete before removing element
}
}, duration);
}, [id, duration]);
return <Container id={id}>{message}</Container>;
};

export default Toaster;
16 changes: 16 additions & 0 deletions controller/components/Toaster/story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Meta, StoryFn } from "@storybook/react";
import { GlobalStyle } from "../Layout";
import React from "react";
import Toaster from "./index";

export default {
title: "Toaster",
component: Toaster
} as Meta<typeof Toaster>;

export const Default: StoryFn = () => (
<React.Fragment>
<GlobalStyle />
<Toaster id="toaster" message={"Type your message here: "}/>
</React.Fragment>
);
74 changes: 65 additions & 9 deletions controller/integration/pewpew.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AuthPermission, AuthPermissions, ErrorResponse, PewPewVersionsResponse, TestManagerError } from "../types";
import type { File, Files } from "formidable";
import { LogLevel, log, logger } from "@fs/ppaas-common";
import { ParsedForm, createFormidableFile, unzipFile } from "../pages/api/util/util";
import { deletePewPew, getPewPewVersionsInS3, getPewpew, postPewPew } from "../pages/api/util/pewpew";
import { LogLevel, PpaasS3File, log, logger } from "@fs/ppaas-common";
import { PEWPEW_BINARY_FOLDER, ParsedForm, createFormidableFile, unzipFile } from "../pages/api/util/util";
import { VERSION_TAG_NAME, deletePewPew, getCurrentPewPewLatestVersion, getPewPewVersionsInS3, getPewpew, postPewPew } from "../pages/api/util/pewpew";
import { expect } from "chai";
import { latestPewPewVersion } from "../pages/api/util/clientutil";
import path from "path";
Expand All @@ -28,6 +28,7 @@ const pewpewZipFile: File = createFormidableFile(
);
let sharedPewPewVersions: string[] | undefined;
let uploadedPewPewVersion: string | undefined; // Used for delete
let currentPewPewLatestVersion: string | undefined;

describe("PewPew Util Integration", () => {
let files: Files = {};
Expand Down Expand Up @@ -75,7 +76,7 @@ describe("PewPew Util Integration", () => {
files
};
log("postPewPew parsedForm", LogLevel.DEBUG, parsedForm);
postPewPew(parsedForm, authAdmin).then((res: ErrorResponse) => {
postPewPew(parsedForm, authAdmin).then(async (res: ErrorResponse) => {
log("postPewPew res", LogLevel.DEBUG, res);
expect(res.status, JSON.stringify(res.json)).to.equal(200);
const body: TestManagerError = res.json;
Expand All @@ -97,6 +98,21 @@ describe("PewPew Util Integration", () => {
sharedPewPewVersions.push(version);
}
log("sharedPewPewVersions: " + sharedPewPewVersions, LogLevel.DEBUG);
try {
const pewpewFiles = await PpaasS3File.getAllFilesInS3({
s3Folder: `${PEWPEW_BINARY_FOLDER}/${version}`,
localDirectory: process.env.TEMP || "/tmp"
});
expect(pewpewFiles).to.not.equal(undefined);
expect(pewpewFiles.length).to.be.greaterThan(0);
const pewpewFile = pewpewFiles.find( (file) => file.filename === "pewpew");
expect(pewpewFile).to.not.equal(undefined);
expect(pewpewFile?.tags).to.not.equal(undefined);
expect(pewpewFile?.tags?.get(VERSION_TAG_NAME)).to.equal(version);
} catch (error) {
log("postPewPew error while checking latest tag: ", LogLevel.ERROR, error);
throw error;
}
done();
}).catch((error) => {
log("postPewPew error", LogLevel.ERROR, error);
Expand All @@ -112,7 +128,7 @@ describe("PewPew Util Integration", () => {
files
};
log("postPewPew parsedForm", LogLevel.DEBUG, parsedForm);
postPewPew(parsedForm, authAdmin).then((res: ErrorResponse) => {
postPewPew(parsedForm, authAdmin).then(async (res: ErrorResponse) => {
log("postPewPew res", LogLevel.DEBUG, res);
expect(res.status, JSON.stringify(res.json)).to.equal(200);
const body: TestManagerError = res.json;
Expand All @@ -121,14 +137,34 @@ describe("PewPew Util Integration", () => {
expect(body.message).to.not.equal(undefined);
expect(body.message).to.include("PewPew uploaded, version");
expect(body.message).to.include("as latest");
const version = latestPewPewVersion;
const match: RegExpMatchArray | null = body.message.match(/PewPew uploaded, version: (\d+\.\d+\.\d+)/);
log(`pewpew match: ${match}`, LogLevel.DEBUG, match);
expect(match, "pewpew match").to.not.equal(null);
expect(match!.length, "pewpew match.length").to.be.greaterThan(1);
const version: string = match![1];
// If this runs before the other acceptance tests populate the shared pewpew versions
if (!sharedPewPewVersions) {
sharedPewPewVersions = [version];
} else if (!sharedPewPewVersions.includes(version)) {
sharedPewPewVersions.push(version);
sharedPewPewVersions = [latestPewPewVersion];
} else if (!sharedPewPewVersions.includes(latestPewPewVersion)) {
sharedPewPewVersions.push(latestPewPewVersion);
}
log("sharedPewPewVersions: " + sharedPewPewVersions, LogLevel.DEBUG);
try {
const pewpewFiles = await PpaasS3File.getAllFilesInS3({
s3Folder: `${PEWPEW_BINARY_FOLDER}/${latestPewPewVersion}`,
localDirectory: process.env.TEMP || "/tmp"
});
expect(pewpewFiles).to.not.equal(undefined);
expect(pewpewFiles.length).to.be.greaterThan(0);
const pewpewFile = pewpewFiles.find( (file) => file.filename === "pewpew");
expect(pewpewFile).to.not.equal(undefined);
expect(pewpewFile?.tags).to.not.equal(undefined);
expect(pewpewFile?.tags?.get(VERSION_TAG_NAME)).to.equal(version);
} catch (error) {
log("postPewPew error while checking latest tag: ", LogLevel.ERROR, error);
throw error;
}
currentPewPewLatestVersion = version;
done();
}).catch((error) => {
log("postPewPew error", LogLevel.ERROR, error);
Expand All @@ -138,6 +174,20 @@ describe("PewPew Util Integration", () => {
});

describe("getPewpew", () => {
before(async () => {
try {
const pewpewFiles = await PpaasS3File.getAllFilesInS3({
s3Folder: `${PEWPEW_BINARY_FOLDER}/${latestPewPewVersion}`,
localDirectory: process.env.TEMP || "/tmp"
});
expect(pewpewFiles).to.not.equal(undefined);
const pewpewFile = pewpewFiles.find( (file) => file.filename === "pewpew");
expect(pewpewFile).to.not.equal(undefined);
} catch (error) {
log("getCurrentPewPewLatest Version failed: ", LogLevel.ERROR, error);
throw error;
}
});
it("getPewpew should respond 200 OK", (done: Mocha.Done) => {
expect(sharedPewPewVersions, "sharedPewPewVersions").to.not.equal(undefined);
getPewpew().then((res: ErrorResponse | PewPewVersionsResponse) => {
Expand All @@ -153,6 +203,12 @@ describe("PewPew Util Integration", () => {
done();
}).catch((error) => done(error));
});

it("getCurrentPewPewLatestVersion", async () => {
expect(currentPewPewLatestVersion, "currentPewPewLatestVersion").to.not.equal(undefined);
const result = await getCurrentPewPewLatestVersion();
expect(result, "result").to.equal(currentPewPewLatestVersion);
});
});

describe("deletePewPew", () => {
Expand Down
2 changes: 1 addition & 1 deletion controller/pages/admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ const Admin = ({ authPermission, versionInitalProps, error: propsError }: AdminP

export const getServerSideProps: GetServerSideProps =
async (ctx: GetServerSidePropsContext): Promise<GetServerSidePropsResult<AdminProps>> => {
let versionInitalProps: VersionInitalProps = { pewpewVersion: "", loading: false, pewpewVersions: [], error: true };
let versionInitalProps: VersionInitalProps = { pewpewVersion: "", loading: false, pewpewVersions: [], latestPewPewVersion: "", error: true };
try {
// Authenticate
const authPermissions: AuthPermissions | string = await authPage(ctx, AuthPermission.Admin);
Expand Down
38 changes: 36 additions & 2 deletions controller/pages/api/util/pewpew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ logger.config.LogFileName = "ppaas-controller";
const deleteS3 = s3.deleteObject;
export const PEWPEW_EXECUTABLE_NAME: string = "pewpew";
const PEWPEW_EXECUTABLE_NAME_WINDOWS: string = "pewpew.exe";
export const VERSION_TAG_NAME: string = "version";

/**
* Queries S3 for all objects in the pewpew/ folder that end with pewpew (not pewpew.exe).
Expand Down Expand Up @@ -74,6 +75,16 @@ export async function getPewpew (): Promise<ErrorResponse | PewPewVersionsRespon
}
}

// Store these as maps for fast lookup oldest can be found by lastRequested, lastUpdated, and lastChecked
// We can't use static variables since the memory spaces aren't shared between api and getServerSideProps
// https://stackoverflow.com/questions/70260701/how-to-share-data-between-api-route-and-getserversideprops
declare global {
// https://stackoverflow.com/questions/68481686/type-typeof-globalthis-has-no-index-signature
/** The pewpew version that 'latest' is currently set to */
// eslint-disable-next-line no-var
var currentLatestVersion: string | undefined;
}

/**
* defines the POST /api/pewpew route which posts new versions of pewpew to s3
* @param parsedForm ParsedForm with the data
Expand Down Expand Up @@ -134,13 +145,12 @@ export async function postPewPew (parsedForm: ParsedForm, authPermissions: AuthP
if (version === null) {
return { json: { message: `${match[1]} is not a valid semver version: ${version}` }, status: 400 };
}

const uploadPromises: Promise<PpaasS3File>[] = [];
const versionLogDisplay = latest ? `${version} as latest` : version;
const versionFolder = latest ? latestPewPewVersion : version;
log(PEWPEW_EXECUTABLE_NAME + " only upload, version: " + versionLogDisplay, LogLevel.DEBUG, files);
// Pass in an override Map to override the default tags and not set a "test" tag
const tags = new Map<string, string>([["pewpew", "true"]]);
const tags = new Map<string, string>([[PEWPEW_BINARY_FOLDER, "true"], [VERSION_TAG_NAME, version]]);
if (Array.isArray(files.additionalFiles)) {
for (const file of files.additionalFiles) {
uploadPromises.push(uploadFile(file, `${PEWPEW_BINARY_FOLDER}/${versionFolder}`, tags));
Expand All @@ -149,6 +159,16 @@ export async function postPewPew (parsedForm: ParsedForm, authPermissions: AuthP
uploadPromises.push(uploadFile(files.additionalFiles, `${PEWPEW_BINARY_FOLDER}/${versionFolder}`, tags));
}
await Promise.all(uploadPromises);
// If latest version is being updated:
if (latest) {
global.currentLatestVersion = version;
try {

log("Sucessfully saved PewPew's latest version to file. ", LogLevel.INFO);
} catch (error) {
log("Writing latest PewPew's latest version to file failed: ", LogLevel.ERROR, error);
}
}
log(PEWPEW_EXECUTABLE_NAME + " only uploaded, version: " + versionLogDisplay, LogLevel.INFO, { files, authPermissions: getLogAuthPermissions(authPermissions) });
return { json: { message: "PewPew uploaded, version: " + versionLogDisplay }, status: 200 };
} else {
Expand Down Expand Up @@ -215,3 +235,17 @@ export async function deletePewPew (query: Partial<Record<string, string | strin
throw error;
}
}

export async function getCurrentPewPewLatestVersion (): Promise<string | undefined> {
if (global.currentLatestVersion) {
return global.currentLatestVersion;
}
try {
const pewpewTags = await s3.getTags({s3Folder: `${PEWPEW_BINARY_FOLDER}/${latestPewPewVersion}`, filename: PEWPEW_EXECUTABLE_NAME});
global.currentLatestVersion = pewpewTags && pewpewTags.get(VERSION_TAG_NAME); // <- change to get the tag here
return global.currentLatestVersion;
} catch (error) {
log("Could not load latest pewpew in file", LogLevel.ERROR, error);
throw error;
}
}
Loading

0 comments on commit a8bee2d

Please sign in to comment.