Skip to content

Commit

Permalink
Refactor (#7)
Browse files Browse the repository at this point in the history
* refactor(ui): remove unnecessary inject

* refactor(lib): simplify sort

* refactor(lib): remove usage of lodash and refactored filtering routine

* refactor(cli): removed unnecessary assertion
  • Loading branch information
hoonoh authored Oct 28, 2019
1 parent d576319 commit f83f9f0
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 94 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@
},
"dependencies": {
"aws-sdk": "^2.544.0",
"lodash": "^4.17.15",
"ora": "^4.0.2",
"patch-package": "^6.2.0",
"prompts": "^2.2.1",
Expand Down Expand Up @@ -121,6 +120,7 @@
"jest": "^24.9.0",
"jest-junit": "^9.0.0",
"jest-mock-console": "^1.0.0",
"lodash": "^4.17.15",
"nock": "^11.4.0",
"postinstall-postinstall": "^2.0.0",
"prettier": "^1.18.2",
Expand Down
6 changes: 2 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,8 @@ export const main = (argvInput?: string[]): Promise<void> =>
await getGlobalSpotPrices({
regions: region as Region[],
instanceTypes: instanceType as InstanceType[],
familyTypes: familyTypeSetArray.length
? (familyTypeSetArray as InstanceFamilyType[])
: undefined,
sizes: sizeSetArray.length ? (sizeSetArray as InstanceSize[]) : undefined,
familyTypes: familyTypeSetArray.length ? familyTypeSetArray : undefined,
sizes: sizeSetArray.length ? sizeSetArray : undefined,
limit,
priceMax,
productDescriptions: productDescriptionsSetArray.length
Expand Down
157 changes: 74 additions & 83 deletions src/lib/lib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { EC2 } from 'aws-sdk';
import { find, findIndex } from 'lodash';
import * as ora from 'ora';
import { table } from 'table';

Expand All @@ -10,42 +9,22 @@ import { generateInstantTypesFromFamilyTypeSize } from './utils';

const sortSpotPrice = (p1: EC2.SpotPrice, p2: EC2.SpotPrice): number => {
let rtn = 0;
const p1SpotPrice = p1.SpotPrice || 0;
const p2SpotPrice = p2.SpotPrice || 0;
if (p1SpotPrice < p2SpotPrice) {
rtn = -1;
} else if (p1SpotPrice > p2SpotPrice) {
rtn = 1;
}

// AWS SDK will always return instance type.
// If instance type data is not returned by aws api endpoint,
// it seems SDK will filter it out by default.
if (rtn === 0 && p1.InstanceType && p2.InstanceType) {
if (p1.InstanceType < p2.InstanceType) {
rtn = -1;
} else if (p1.InstanceType > p2.InstanceType) {
rtn = 1;
}
}

if (rtn === 0) {
const p1AvailabilityZone = p1.AvailabilityZone || '';
const p2AvailabilityZone = p2.AvailabilityZone || '';
if (p1AvailabilityZone < p2AvailabilityZone) {
rtn = -1;
} else if (p1AvailabilityZone > p2AvailabilityZone) {
rtn = 1;
const sort = (s1?: string, s2?: string): void => {
/* istanbul ignore else */
if (rtn === 0 && s1 && s2) {
if (s1 < s2) {
rtn = -1;
} else if (s1 > s2) {
rtn = 1;
}
}
}
};

if (rtn === 0 && p1.ProductDescription && p2.ProductDescription) {
if (p1.ProductDescription < p2.ProductDescription) {
rtn = -1;
} else if (p1.ProductDescription > p2.ProductDescription) {
rtn = 1;
}
}
sort(p1.SpotPrice, p2.SpotPrice);
sort(p1.InstanceType, p2.InstanceType);
sort(p1.AvailabilityZone, p2.AvailabilityZone);
sort(p1.ProductDescription, p2.ProductDescription);

return rtn;
};
Expand Down Expand Up @@ -152,8 +131,6 @@ export const getGlobalSpotPrices = async (options?: {

let { regions, instanceTypes } = options || {};

let rtn: EC2.SpotPrice[] = [];

if (regions === undefined) regions = defaultRegions;

if (familyTypes || sizes) {
Expand All @@ -178,7 +155,7 @@ export const getGlobalSpotPrices = async (options?: {
}).start();
}

await Promise.all(
const rtn: EC2.SpotPrice[] = await Promise.all(
regions.map(async region => {
try {
const regionsPrices = await getEc2SpotPrice({
Expand All @@ -188,12 +165,12 @@ export const getGlobalSpotPrices = async (options?: {
accessKeyId,
secretAccessKey,
});
rtn = [...rtn, ...regionsPrices];
/* istanbul ignore if */
if (spinner) {
spinnerText = `Retrieved data from ${region}...`;
spinner.text = spinnerText;
}
return regionsPrices;
} catch (error) {
/* istanbul ignore if */
if (error instanceof Ec2SpotPriceError && spinner) {
Expand All @@ -205,55 +182,69 @@ export const getGlobalSpotPrices = async (options?: {
} else {
console.error(error);
}
return [];
}
}),
);
/* istanbul ignore if */
if (spinner) spinner.succeed('All data retrieved!').stop();

rtn = rtn.reduce(
(list, cur) => {
if (priceMax && cur.SpotPrice && parseFloat(cur.SpotPrice) > priceMax) return list;
list.push(cur);
return list;
},
[] as EC2.SpotPrice[],
);
).then(results => {
/* istanbul ignore if */
if (spinner) spinner.succeed('All data retrieved!').stop();
return results
.reduce(
(finalList: EC2.SpotPrice[], curList: EC2.SpotPrice[]) => {
const curListFiltered = curList.filter(
// filter price info without region or price greater than priceMax
price => {
// 1. remove if data missing any of the required attributes
// 2. remove if price.SpotPrice is unavailable or price is higher than priceMax
if (
!price.AvailabilityZone ||
!price.SpotPrice ||
!price.InstanceType ||
(priceMax !== undefined && parseFloat(price.SpotPrice) > priceMax)
)
return false;

return true;
},
);
// look for duplicate and remove prev data if older than current
const curListReduced = curListFiltered.reduce(
(list, cur) => {
const duplicates = list.filter(
prevPrice =>
cur.AvailabilityZone &&
cur.AvailabilityZone === prevPrice.AvailabilityZone &&
cur.InstanceType &&
cur.InstanceType === prevPrice.InstanceType &&
cur.ProductDescription &&
cur.ProductDescription === prevPrice.ProductDescription,
);
if (duplicates.length) {
while (duplicates.length) {
const dupe = duplicates.pop();
if (dupe && cur.Timestamp && dupe.Timestamp && cur.Timestamp > dupe.Timestamp) {
list.splice(list.indexOf(dupe));
list.push(cur);
}
}
} else {
list.push(cur);
}
return list;
},
[] as EC2.SpotPrice[],
);
return finalList.concat(curListReduced);
},
[] as EC2.SpotPrice[],
)
.sort(sortSpotPrice);
});

// limit output
if (limit && rtn.length > limit) rtn.splice(limit);

// log output
rtn = rtn.sort(sortSpotPrice).reduce(
(list, price, idx, arr) => {
// since price info without price or region will be pointless..
if (!price.SpotPrice || !price.AvailabilityZone) return list;

// look for duplicate
let duplicate = find(list, {
InstanceType: price.InstanceType,
ProductDescription: price.ProductDescription,
AvailabilityZone: price.AvailabilityZone,
});

// if current price data timestamp is more recent, remove previous..
if (
duplicate &&
duplicate.Timestamp &&
price.Timestamp &&
duplicate.Timestamp < price.Timestamp
) {
list.splice(findIndex(list, price), 1);
duplicate = undefined;
}

if (duplicate === undefined) list.push(price);

// stop reduce loop if list has reached limit
if (limit && list.length >= limit) arr.splice(0);

return list;
},
[] as EC2.SpotPrice[],
);

if (!silent) {
if (rtn.length > 0) {
console.log(
Expand Down
11 changes: 5 additions & 6 deletions src/lib/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const ui = async (): Promise<Answers> => {
/* istanbul ignore next */
if (process.env.UI_INJECT) {
inject = JSON.parse(process.env.UI_INJECT);
if (inject) prompt.inject(inject.splice(0, 2));
if (inject) prompt.inject(inject);
}

const question1 = [
Expand Down Expand Up @@ -95,12 +95,12 @@ export const ui = async (): Promise<Answers> => {
/* istanbul ignore next */
if (inject) {
familyTypePreSelected.forEach(type => {
if (inject && typeof inject[0] === 'object' && !inject[0].includes(type))
inject[0].push(type);
if (inject && typeof inject[2] === 'object' && !inject[2].includes(type))
inject[2].push(type);
});
sizePreSelected.forEach(size => {
if (inject && typeof inject[1] === 'object' && !inject[1].includes(size))
inject[1].push(size);
if (inject && typeof inject[3] === 'object' && !inject[3].includes(size))
inject[3].push(size);
});
}

Expand Down Expand Up @@ -181,7 +181,6 @@ export const ui = async (): Promise<Answers> => {
];

/* istanbul ignore next */
if (inject) prompt.inject(inject);
const answer2: Answer2 = await prompt(question2, { onCancel });

return { ...answer1, ...answer2 };
Expand Down

0 comments on commit f83f9f0

Please sign in to comment.