Skip to content

Commit

Permalink
Release from Stage to Main (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
sukamat authored Aug 16, 2024
2 parents cf39652 + 87cd0bb commit 002ab35
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 231 deletions.
3 changes: 1 addition & 2 deletions .kodiak/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ notifications:
assignee:
name: raga
customfield_11800: MWPW-140779 # Jira epic security tickets will be assigned to.
customfield_11900:
customfield_12900:
value: "Unicorn" # Put your team name here; it must be a valid team in Jira.
watchers: # Everyone who is an admin on your repository should be a watcher.
- casalino
- jmichnow
- mauchley
- hwong
- sukamat
Expand Down
3 changes: 2 additions & 1 deletion actions/copy/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async function main(params) {
const project = new Project({ sharepoint });
const fgCopyActionHelper = new FgCopyActionHelper();
const helixUtils = new HelixUtils(appConfig);
const spConfig = await appConfig.getSpConfig();
const projectDetail = await project.getProjectDetails(projectExcelPath);

respPayload = 'Injecting sharepoint data';
Expand All @@ -77,7 +78,7 @@ async function main(params) {
statusMessage: respPayload
});

respPayload = await fgCopyActionHelper.floodgateContent(projectExcelPath, projectDetail, fgStatus, fgColor, { sharepoint, helixUtils });
respPayload = await fgCopyActionHelper.floodgateContent(projectExcelPath, projectDetail, fgStatus, fgColor, { sharepoint, helixUtils, spConfig });
} catch (err) {
await fgStatus.updateStatusToStateLib({
status: FgStatus.PROJECT_STATUS.COMPLETED_WITH_ERROR,
Expand Down
74 changes: 19 additions & 55 deletions actions/fgCopyActionHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,85 +17,49 @@
const {
handleExtension,
toUTCStr,
delay,
inParallel,
getAioLogger,
logMemUsage
} = require('./utils');
const FgStatus = require('./fgStatus');

const BATCH_REQUEST_COPY = 20;
const DELAY_TIME_COPY = 3000;

/**
* Floodgate action helper routines
*/
class FloodgateActionHelper {
async floodgateContent(projectExcelPath, projectDetail, fgStatus, fgColor, { sharepoint, helixUtils }) {
async floodgateContent(projectExcelPath, projectDetail, fgStatus, fgColor, { sharepoint, helixUtils, spConfig }) {
const logger = getAioLogger();
logger.info('Floodgating content started.');

async function copyFilesToFloodgateTree(fileInfo) {
const status = { success: false };
if (!fileInfo?.doc) return status;
const filePath = fileInfo.doc.filePath;
status.srcPath = filePath;
status.url = fileInfo.doc.url;
logger.info(`Copying ${filePath} to floodgated folder`);

try {
const srcPath = fileInfo.doc.filePath;
logger.info(`Copying ${srcPath} to floodgated folder`);

let copySuccess = false;
const destinationFolder = `${srcPath.substring(0, srcPath.lastIndexOf('/'))}`;
copySuccess = await sharepoint.copyFile(srcPath, destinationFolder, undefined, true);
if (copySuccess === false) {
logger.info(`Copy was not successful for ${srcPath}. Alternate copy option will be used`);
const file = await sharepoint.getFile(fileInfo.doc);
if (file) {
const destination = fileInfo.doc.filePath;
if (destination) {
// Save the file in the floodgate destination location
const saveStatus = await sharepoint.saveFile(file, destination, true);
if (saveStatus.success) {
copySuccess = true;
}
}
}
}
status.success = copySuccess;
status.srcPath = srcPath;
status.url = fileInfo.doc.url;
const content = await sharepoint.getFile(fileInfo.doc);
const copyStatus = await sharepoint.uploadFileByPath(spConfig, filePath, { content, mimeType: fileInfo.doc.mimeType }, true);
status.success = copyStatus.success;
status.locked = copyStatus.locked;
} catch (error) {
logger.error(`Error occurred when trying to copy files to floodgated content folder ${error.message}`);
logger.error(`Error copying files ${filePath} to fg content tree ${error.message}`);
}
return status;
}

// create batches to process the data
const contentToFloodgate = [...projectDetail.urls];
const batchArray = [];
for (let i = 0; i < contentToFloodgate.length; i += BATCH_REQUEST_COPY) {
const arrayChunk = contentToFloodgate.slice(i, i + BATCH_REQUEST_COPY);
batchArray.push(arrayChunk);
}
logMemUsage();
const projectUrlDetailsArr = [...projectDetail.urls];
await projectUrlDetailsArr.reduce(async (previous, current) => {
await previous;
await sharepoint.bulkCreateFolders(current, true);
}, Promise.resolve());

// process data in batches
const copyStatuses = [];
// Get the access token to cache, avoidid parallel hits to this in below loop.
await sharepoint.getSharepointAuth().getAccessToken();
for (let i = 0; i < batchArray.length; i += 1) {
// Log memory usage per batch as copy is a heavy operation. Can be removed after testing are done.
// Can be replaced with logMemUsageIter for regular logging
logMemUsage();
logger.info(`Batch create folder ${i} in progress`);
// eslint-disable-next-line no-await-in-loop
await sharepoint.bulkCreateFolders(batchArray[i], true);
logger.info(`Batch copy ${i} in progress`);
// eslint-disable-next-line no-await-in-loop
copyStatuses.push(...await Promise.all(
batchArray[i].map((files) => copyFilesToFloodgateTree(files[1])),
));
logger.info(`Batch copy ${i} completed`);
// eslint-disable-next-line no-await-in-loop, no-promise-executor-return
await delay(DELAY_TIME_COPY);
}
const copyStatuses = await inParallel(projectUrlDetailsArr, (item) => copyFilesToFloodgateTree(item[1]), logger, false, null, BATCH_REQUEST_COPY);
logger.info('Completed floodgating documents listed in the project excel');

logger.info('Previewing floodgated files... ');
Expand All @@ -106,7 +70,7 @@ class FloodgateActionHelper {
}
logger.info('Completed generating Preview for floodgated files.');
const failedCopies = copyStatuses.filter((status) => !status.success)
.map((status) => status.srcPath || 'Path Info Not available');
.map((status) => `${status.srcPath || 'Path Info Not available'}${status.locked ? ' (locked)': ''}`);
const failedPreviews = previewStatuses.filter((status) => !status.success)
.map((status) => status.path);
const fgErrors = failedCopies.length > 0 || failedPreviews.length > 0;
Expand Down
108 changes: 27 additions & 81 deletions actions/fgPromoteActionHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ const {
delay,
isFilePatternMatched,
getAioLogger,
logMemUsage
logMemUsage,
inParallel,
} = require('./utils');
const Sharepoint = require('./sharepoint');

const DELAY_TIME_PROMOTE = 3000;
const DELAY_TIME_PROMOTE = 100;
const MAX_CHILDREN = 5000;

class FgPromoteActionHelper {
Expand Down Expand Up @@ -81,8 +82,9 @@ class FgPromoteActionHelper {
fgFolders.push(itemPath);
} else {
const downloadUrl = `${downloadBaseURI}/${item.id}/content`;
const mimeType = item.file?.mimeType;
// eslint-disable-next-line no-await-in-loop
await batchManager.addFile({ fileDownloadUrl: downloadUrl, filePath: itemPath });
await batchManager.addFile({ fileDownloadUrl: downloadUrl, filePath: itemPath, mimeType });
}
} else {
logger.info(`Ignored from promote: ${itemPath}`);
Expand All @@ -92,100 +94,44 @@ class FgPromoteActionHelper {
}
}

/**
* Copies the Floodgated files back to the main content tree.
* Creates intermediate folders if needed.
*/
async promoteCopy(srcPath, destinationFolder, { sharepoint, sp }) {
const { baseURI } = sp.api.file.copy;
const rootFolder = baseURI.split('/').pop();
const payload = { ...sp.api.file.copy.payload, parentReference: { path: `${rootFolder}${destinationFolder}` } };
const options = await sharepoint.getAuthorizedRequestOption({
method: sp.api.file.copy.method,
body: JSON.stringify(payload),
});

// copy source is the pink directory for promote
const copyStatusInfo = await sharepoint.fetchWithRetry(`${sp.api.file.copy.fgBaseURI}${srcPath}:/[email protected]=replace`, options);
const statusUrl = copyStatusInfo.headers.get('Location');
let copySuccess = false;
let copyStatusJson = {};
while (statusUrl && !copySuccess && copyStatusJson.status !== 'failed') {
// eslint-disable-next-line no-await-in-loop
const status = await sharepoint.fetchWithRetry(statusUrl);
if (status.ok) {
// eslint-disable-next-line no-await-in-loop
copyStatusJson = await status.json();
copySuccess = copyStatusJson.status === 'completed';
}
async promoteFile(batchItem, { appConfig }) {
const logger = getAioLogger();
const sharepoint = new Sharepoint(appConfig);
const spConfig = await appConfig.getSpConfig();
const { fileDownloadUrl, filePath, mimeType } = batchItem.file;
const status = { success: false, srcPath: filePath };
try {
const content = await sharepoint.getFileUsingDownloadUrl(fileDownloadUrl);
const uploadStatus = await sharepoint.uploadFileByPath(spConfig, filePath, { content, mimeType });
status.success = uploadStatus.success;
status.locked = uploadStatus.locked;
} catch (error) {
logger.error(`Error promoting files ${fileDownloadUrl} at ${filePath} to main content tree ${error.message}`);
}
return copySuccess;
}
return status;
};

async promoteFloodgatedFiles(batchManager, appConfig) {
const logger = getAioLogger();
const sharepoint = new Sharepoint(appConfig);
const sp = await appConfig.getSpConfig();
// Pre check Access Token
await sharepoint.getSharepointAuth().getAccessToken();
const { promoteCopy } = this;

async function promoteFile(batchItem) {
const { fileDownloadUrl, filePath } = batchItem.file;
const status = { success: false, srcPath: filePath };
try {
let promoteSuccess = false;
const destinationFolder = `${filePath.substring(0, filePath.lastIndexOf('/'))}`;
const copyFileStatus = await promoteCopy(filePath, destinationFolder, { sharepoint, sp });
if (copyFileStatus) {
promoteSuccess = true;
} else {
const file = await sharepoint.getFileUsingDownloadUrl(fileDownloadUrl);
const saveStatus = await sharepoint.saveFile(file, filePath);
if (saveStatus.success) {
promoteSuccess = true;
}
}
status.success = promoteSuccess;
} catch (error) {
const errorMessage = `Error promoting files ${fileDownloadUrl} at ${filePath} to main content tree ${error.message}`;
logger.error(errorMessage);
status.success = false;
}
return status;
}

let i = 0;
let stepMsg = 'Getting all floodgated files to promote.';
// Get the batch files using the batchmanager for the assigned batch and process them
const currentBatch = await batchManager.getCurrentBatch();
const currBatchLbl = `Batch-${currentBatch.getBatchNumber()}`;
const allFloodgatedFiles = await currentBatch?.getFiles();
logger.info(`Files for the batch are ${allFloodgatedFiles.length}`);
// create batches to process the data
const batchArray = [];
const numBulkReq = appConfig.getNumBulkReq();
for (i = 0; i < allFloodgatedFiles.length; i += numBulkReq) {
const arrayChunk = allFloodgatedFiles.slice(i, i + numBulkReq);
batchArray.push(arrayChunk);
}
logger.info(`Files for the batch are ${allFloodgatedFiles.length}`);

// process data in batches
const promoteStatuses = [];
for (i = 0; i < batchArray.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
promoteStatuses.push(...await Promise.all(
batchArray[i].map((bi) => promoteFile(bi))
));
// eslint-disable-next-line no-await-in-loop, no-promise-executor-return
await delay(DELAY_TIME_PROMOTE);
}
const promoteStatuses = await inParallel(allFloodgatedFiles, this.promoteFile, logger, false, { appConfig }, numBulkReq);

stepMsg = `Completed promoting all documents in the batch ${currBatchLbl}`;
logger.info(stepMsg);

const failedPromotes = promoteStatuses.filter((status) => !status.success)
.map((status) => status.srcPath || 'Path Info Not available');
.map((status) => ({path: status.srcPath || 'Path Info Not available', message: `${status.locked ? 'locked': ''}`}));
logger.info(`Promote ${currBatchLbl}, Prm: ${failedPromotes?.length}`);

if (failedPromotes.length > 0) {
Expand Down Expand Up @@ -213,7 +159,7 @@ class FgPromoteActionHelper {
const promotedFiles = allFloodgatedFiles.map((e) => e.file.filePath);
const resultsContent = await currentBatch.getResultsContent() || {};
const failedPromotes = resultsContent.failedPromotes || [];
const prevPaths = promotedFiles.filter((item) => !failedPromotes.includes(item)).map((e) => handleExtension(e));
const prevPaths = promotedFiles.filter((item) => !failedPromotes.find(fpItem => fpItem.path === item)).map((e) => handleExtension(e));
logger.info(`Post promote files for ${currBatchLbl} are ${prevPaths?.length}`);

logger.info('Previewing promoted files.');
Expand All @@ -234,10 +180,10 @@ class FgPromoteActionHelper {
}

const failedPreviews = previewStatuses.filter((status) => !status.success)
.map((status) => status.path);
.map((status) => ({path: status.path}));
const failedPublishes = publishStatuses.filter((status) => !status.success)
.map((status) => status.path);
logger.info(`Post promote ${currBatchLbl}, Prm: ${failedPromotes?.length}, Prv: ${failedPreviews?.length}, Pub: ${failedPublishes?.length}`);
.map((status) => ({path: status.path}));
logger.info(`Post promote ${currBatchLbl}, Prm: ${failedPromotes?.length}, Prv: ${failedPreviews?.length}, Pub: ${failedPublishes?.length}`)

if (failedPromotes.length > 0 || failedPreviews.length > 0 || failedPublishes.length > 0) {
stepMsg = 'Error occurred when promoting floodgated content. Check project excel sheet for additional information.';
Expand Down
13 changes: 11 additions & 2 deletions actions/promote/triggerNTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ async function triggerPromoteWorkerAction(ow, params, fgStatus) {
});
}

function extractMessages(results) {
return results ? Object.fromEntries(
Object.entries(results).map(([key, value]) => [
key,
value.map(item => `${item.path}${item.message ? ` (${item.message})` : ''}`)
])
) : results;
};

/**
* Marks the proocess as complete and collects all errors and updates excel.
* @param {*} projectExcelPath Project excel where status needs to be updated
Expand All @@ -248,7 +257,6 @@ async function triggerPromoteWorkerAction(ow, params, fgStatus) {
*/
async function completePromote(projectExcelPath, actDtls, batchManager, fgStatus, { sharepoint }) {
let batchNumber;
let results;
const failedPromotes = [];
const failedPreviews = [];
const failedPublishes = [];
Expand All @@ -257,7 +265,8 @@ async function completePromote(projectExcelPath, actDtls, batchManager, fgStatus
try {
batchManager.initBatch({ batchNumber });
const batch = await batchManager.getCurrentBatch();
results = await batch.getResultsContent();
const resultContent = await batch.getResultsContent();
const results = extractMessages(resultContent);
if (results?.failedPromotes?.length > 0) {
failedPromotes.push(...results.failedPromotes);
}
Expand Down
13 changes: 11 additions & 2 deletions actions/promoteStatus/promoteStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,23 @@ async function main(args) {
payload.batchFiles = batchFilesContent?.map((e) => e.file?.filePath);
}

const extractMessages = (results) => {
return Object.fromEntries(
Object.entries(results).map(([key, value]) => [
key,
value.map(item => `${item.path}${item.message ? ` (${item.message})` : ''}`)
])
);
};

if (args.batchResults !== undefined) {
const brC = await currentBatch.getResultsContent();
if (brC) payload.batchResults = brC;
if (brC) payload.batchResults = extractMessages(brC);
}

if (args.promoteResults !== undefined) {
const prC = await batchManager.getResultsContent();
if (prC) payload.promoteResults = prC;
if (prC) payload.promoteResults = extractMessages(prC);
}
} catch (err) {
logger.error(err);
Expand Down
Loading

0 comments on commit 002ab35

Please sign in to comment.