From 3e17cb783be073dc2aa46c27d76f243843fe4b3b Mon Sep 17 00:00:00 2001 From: mihay42 Date: Sun, 21 Jul 2024 12:16:31 -0700 Subject: [PATCH 1/6] Incremental checkin --- cli/mrcli-actions.js | 89 +++++++++++++++++++++++++++++++++++ cli/mrcli-billing.js | 3 -- cli/mrcli-user.js | 1 - cli/mrcli.js | 3 +- package.json | 2 +- src/api/gitHubServer.js | 50 ++++++++++++++++++++ src/api/github.js | 63 +++++++++++++++++++++++++ src/cli/interactionTypes.json | 2 +- 8 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 cli/mrcli-actions.js diff --git a/cli/mrcli-actions.js b/cli/mrcli-actions.js new file mode 100644 index 0000000..2f24bfd --- /dev/null +++ b/cli/mrcli-actions.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +/** + * A CLI utility used for accessing and reporting on mediumroast.io user objects + * @author Michael Hay + * @file actions.js + * @copyright 2024 Mediumroast, Inc. All rights reserved. + * @license Apache-2.0 + * @verstion 1.0.0 + */ + +// Import required modules +import { Billings } from '../src/api/gitHubServer.js' +import Environmentals from '../src/cli/env.js' +import CLIOutput from '../src/cli/output.js' + +// Related object type +const objectType = 'Actions' + +// Environmentals object +const environment = new Environmentals( + '2.0', + `${objectType}`, + `Command line interface to report on and update actions.`, + objectType +) + +/* + ----------------------------------------------------------------------- + + FUNCTIONS - Key functions needed for MAIN + + ----------------------------------------------------------------------- +*/ + + +/* + ----------------------------------------------------------------------- + + MAIN - Steps below represent the main function of the program + + ----------------------------------------------------------------------- +*/ + +// Create the environmental settings +let myProgram = environment.parseCLIArgs(true) + +// Remove command line options for reset_by_type, delete, update, and add_wizard by calling the removeArgByName method in the environmentals class +myProgram = environment.removeArgByName(myProgram, '--delete') +myProgram = environment.removeArgByName(myProgram, '--add_wizard') +myProgram = environment.removeArgByName(myProgram, '--reset_by_type') +myProgram = environment.removeArgByName(myProgram, '--report') +myProgram = environment.removeArgByName(myProgram, '--find_by_name') +myProgram = environment.removeArgByName(myProgram, '--find_by_x') +myProgram = environment.removeArgByName(myProgram, '--find_by_id') +myProgram = environment.removeArgByName(myProgram, '--report') +myProgram = environment.removeArgByName(myProgram, '--package') +myProgram = environment.removeArgByName(myProgram, '--splash') + +// Parse the command line arguments into myArgs and obtain the options +let myArgs = myProgram.parse(process.argv) +myArgs = myArgs.opts() + +const myConfig = environment.readConfig(myArgs.conf_file) +let myEnv = environment.getEnv(myArgs, myConfig) +const accessToken = await environment.verifyAccessToken() +const processName = 'mrcli-actions' + +// Output object +const output = new CLIOutput(myEnv, objectType) + +// Construct the controller objects +const actionsCtl = new Billings(accessToken, myEnv.gitHubOrg, processName) + +// Predefine the results variable +let [success, stat, results] = [null, null, null] + +if (myArgs.update) { + [success, stat, results] = await billingsCtl.getActionsBilling() + const myUserOutput = new CLIOutput(myEnv, 'ActionsBilling') + myUserOutput.outputCLI([results], myArgs.output) + process.exit() +} else { + [success, stat, results] = await billingsCtl.getAll() + const myUserOutput = new CLIOutput(myEnv, 'AllBilling') + myUserOutput.outputCLI(results, myArgs.output) + process.exit() +} + diff --git a/cli/mrcli-billing.js b/cli/mrcli-billing.js index f457e8c..eea4e80 100644 --- a/cli/mrcli-billing.js +++ b/cli/mrcli-billing.js @@ -13,7 +13,6 @@ import { Billings } from '../src/api/gitHubServer.js' import Environmentals from '../src/cli/env.js' import CLIOutput from '../src/cli/output.js' -import chalk from 'chalk' // Related object type const objectType = 'Billings' @@ -58,8 +57,6 @@ myProgram = environment.removeArgByName(myProgram, '--report') myProgram = environment.removeArgByName(myProgram, '--find_by_name') myProgram = environment.removeArgByName(myProgram, '--find_by_x') myProgram = environment.removeArgByName(myProgram, '--find_by_id') -myProgram = environment.removeArgByName(myProgram, '--update') -myProgram = environment.removeArgByName(myProgram, '--delete') myProgram = environment.removeArgByName(myProgram, '--report') myProgram = environment.removeArgByName(myProgram, '--package') myProgram = environment.removeArgByName(myProgram, '--splash') diff --git a/cli/mrcli-user.js b/cli/mrcli-user.js index 1c1a8ca..c2632b9 100755 --- a/cli/mrcli-user.js +++ b/cli/mrcli-user.js @@ -13,7 +13,6 @@ import { Users } from '../src/api/gitHubServer.js' import Environmentals from '../src/cli/env.js' import CLIOutput from '../src/cli/output.js' -import chalk from 'chalk' // Related object type const objectType = 'Users' diff --git a/cli/mrcli.js b/cli/mrcli.js index 319e2b5..d0c4ff5 100755 --- a/cli/mrcli.js +++ b/cli/mrcli.js @@ -14,7 +14,7 @@ import program from 'commander' program .name('mrcli') - .version('0.6.4') + .version('0.7.0') .description('mediumroast.io command line interface') .command('setup', 'setup the mediumroast.io system via the command line').alias('f') .command('interaction', 'manage and report on mediumroast.io interaction objects').alias('i') @@ -22,5 +22,6 @@ program .command('study', 'manage and report on mediumroast.io study objects').alias('s') .command('user', 'report on mediumroast.io users in GitHub').alias('u') .command('billing', 'report on GitHub actions and storage units consumed').alias('b') + .command('actions', 'report on and update GitHub actions').alias('a') program.parse(process.argv) \ No newline at end of file diff --git a/package.json b/package.json index 489336d..fc6d52d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mediumroast_js", - "version": "0.6.4", + "version": "0.7.0", "description": "A Command Line Interface (CLI) and Javascript SDK to interact with mediumroast.io.", "main": "cli/mrcli.js", "scripts": { diff --git a/src/api/gitHubServer.js b/src/api/gitHubServer.js index 75595de..419867d 100644 --- a/src/api/gitHubServer.js +++ b/src/api/gitHubServer.js @@ -474,5 +474,55 @@ class Interactions extends baseObjects { } } +class Actions extends baseObjects { + /** + * @constructor + * @classdesc A subclass of baseObjects that construct the interaction objects + * @param {String} token - the token for the GitHub application + * @param {String} org - the organization for the GitHub application + * @param {String} processName - the process name for the GitHub application + */ + constructor (token, org, processName) { + super(token, org, processName, 'Interactions') + } + + async updateObj(objToUpdate, dontWrite=false, system=false) { + // Destructure objToUpdate + const { name, key, value } = objToUpdate + // Define the attributes that can be updated by the user + const whiteList = [ + 'status', 'content_type', 'file_size', 'reading_time', 'word_count', 'page_count', 'description', 'abstract', + + 'region', 'country', 'city', 'state_province', 'zip_postal', 'street_address', 'latitude', 'longitude', + + 'public', 'groups' + ] + return await super.updateObj(name, key, value, dontWrite, system, whiteList) + } + + async getAll() { + const storageBillingsResp = await this.serverCtl.getStorageBillings() + const actionsBillingsResp = await this.serverCtl.getActionsBillings() + const allBillings = [ + { + resourceType: 'Storage', + includedUnits: Math.abs( + storageBillingsResp[2].estimated_paid_storage_for_month - + storageBillingsResp[2].estimated_storage_for_month + ) + ' GiB', + paidUnitsUsed: storageBillingsResp[2].estimated_paid_storage_for_month + ' GiB', + totalUnitsUsed: storageBillingsResp[2].estimated_storage_for_month + ' GiB' + }, + { + resourceType: 'Actions', + includedUnits: actionsBillingsResp[2].total_minutes_used + ' min', + paidUnitsUsed: actionsBillingsResp[2].total_paid_minutes_used + ' min', + totalUnitsUsed: actionsBillingsResp[2].total_minutes_used + actionsBillingsResp[2].total_paid_minutes_used + ' min' + } + ] + return [true, {status_code: 200, status_msg: `found all billings`}, allBillings] + } +} + // Export classes for consumers export { Studies, Companies, Interactions, Users, Billings } \ No newline at end of file diff --git a/src/api/github.js b/src/api/github.js index b1d0301..ba50330 100644 --- a/src/api/github.js +++ b/src/api/github.js @@ -903,6 +903,69 @@ class GitHubFunctions { // Return success with number of objects written return [true, {status_code: 200, status_msg: `Released [${repoMetadata.containers.length}] containers.`}, null] } + + // Use fs to read all the files in the actions directory recursively + generateActionsManifest(dir, filelist) { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename) + dir = dir || path.resolve(path.join(__dirname, './actions') ) + const files = fs.readdirSync(dir) + filelist = filelist || [] + files.forEach((file) => { + // Skip .DS_Store files and node_modules directories + if (file === '.DS_Store' || file === 'node_modules') { + return + } + if (fs.statSync(path.join(dir, file)).isDirectory()) { + filelist = generateActionsManifest(path.join(dir, file), filelist) + } + else { + // Substitute .github for the first part of the path, in the variable dir + // Log dir to the console including if there are any special characters + if (dir.includes('./')) { + dir = dir.replace('./', '') + } + // This will be the repository name + let dotGitHub = dir.replace(/.*(workflows|actions)/, '.github/$1') + + filelist.push({ + fileName: file, + containerName: dotGitHub, + srcURL: new URL(path.join(dir, file), import.meta.url) + }) + } + }) + return filelist + } + + async installActions() { + let actionsManifest = this.generateActionsManifest() + // Loop through the actionsManifest and install each action + await actionsManifest.forEach(async (action) => { + let status = false + let blobData + try { + // Read in the blob file + blobData = fs.readFileSync(action.srcURL, 'base64') + status = true + } catch (err) { + return [false, 'Unable to read file [' + action.fileName + '] because: ' + err, null] + } + if(status) { + // Install the action + const installResp = await this.writeBlob( + action.containerName, + action.fileName, + blobData, + 'main' + ) + } else { + return [false, 'Failed to read item [' + action.fileName + ']', null] + } + }) + return [true, 'All actions installed', null] + } + } export default GitHubFunctions \ No newline at end of file diff --git a/src/cli/interactionTypes.json b/src/cli/interactionTypes.json index 9d642c2..9b19775 100644 --- a/src/cli/interactionTypes.json +++ b/src/cli/interactionTypes.json @@ -1,5 +1,5 @@ { - "Book": { + "Book": { "author": {"consoleString": "Book's author", "value": "Unknown"}, "year": {"consoleString": "Book's published year", "value": "Unknown"}, "month": {"consoleString": "Book's published month", "value": "Unknown"}, From 23d220d59f81f0587841e3ef2e788e4ed8848d36 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Sun, 28 Jul 2024 16:08:51 +0900 Subject: [PATCH 2/6] Incremental checkin, cleanups, beging interactions dash --- cli/mrcli-interaction.js | 22 ++-- src/report/common.js | 100 +++++++++++++---- src/report/companies.js | 52 +++++---- src/report/dashboard.js | 219 ++++++++++++++++++++++++++++++++++++- src/report/interactions.js | 111 ++++++++++++++----- src/report/settings.js | 6 +- 6 files changed, 421 insertions(+), 89 deletions(-) diff --git a/cli/mrcli-interaction.js b/cli/mrcli-interaction.js index 8215783..37f42d7 100755 --- a/cli/mrcli-interaction.js +++ b/cli/mrcli-interaction.js @@ -4,9 +4,9 @@ * A CLI utility used for accessing and reporting on mediumroast.io interaction objects * @author Michael Hay * @file interactions.js - * @copyright 2022 Mediumroast, Inc. All rights reserved. + * @copyright 2024 Mediumroast, Inc. All rights reserved. * @license Apache-2.0 - * @version 3.0.0 + * @version 3.1.0 */ @@ -57,7 +57,7 @@ const objectType = 'Interactions' // Environmentals object const environment = new Environmentals( - '3.0.0', + '3.1.0', `${objectType}`, `Command line interface for mediumroast.io ${objectType} objects.`, objectType @@ -94,12 +94,10 @@ let results = Array() || [] // Process the cli options if (myArgs.report) { - console.error('ERROR (%d): Report not implemented.', -1) - process.exit(-1) // Retrive the interaction by Id - const [int_success, int_stat, int_results] = await interactionCtl.findById(myArgs.report) - // Retrive the company by Name + const [int_success, int_stat, int_results] = await interactionCtl.findByName(myArgs.report) const companyName = Object.keys(int_results[0].linked_companies)[0] + // Retrive the company by Name const [comp_success, comp_stat, comp_results] = await companyCtl.findByName(companyName) // Set the root name to be used for file and directory names in case of packaging const baseName = int_results[0].name.replace(/ /g,"_") @@ -112,8 +110,9 @@ if (myArgs.report) { const docController = new InteractionStandalone( int_results[0], // Interaction to report on comp_results[0], // The company associated to the interaction - 'mediumroast.io barrista robot', // The author - 'Mediumroast, Inc.' // The authoring company/org + 'Mediumroast for GitHub robot', // The author + 'Mediumroast, Inc.', // The authoring company/org + myEnv // The environment settings ) if(myArgs.package) { @@ -168,11 +167,6 @@ if (myArgs.report) { process.exit(-1) } -} else if (myArgs.find_by_id) { - console.error('ERROR (%d): Find by name not implemented.', -1) - process.exit(-1) - // Retrive the interaction by Id - [success, stat, results] = await interactionCtl.findById(myArgs.find_by_id) } else if (myArgs.find_by_name) { // Retrive the interaction by Name [success, stat, results] = await interactionCtl.findByName(myArgs.find_by_name) diff --git a/src/report/common.js b/src/report/common.js index 80bcea9..3417929 100644 --- a/src/report/common.js +++ b/src/report/common.js @@ -10,6 +10,8 @@ import docx from 'docx' import * as fs from 'fs' import boxPlot from 'box-plot' +import docxSettings from './settings.js' +import FilesystemOperators from '../cli/filesystem.js' // TODO Change class names to: GenericUtilities, DOCXUtilities and HTMLUtilities @@ -25,21 +27,23 @@ class DOCXUtilities { * with document generation. * @constructor * @classdesc Core utilities for generating elements in a Microsoft word DOCX file - * @param {String} font - * @param {Float} fontSize - * @param {Float} textFontSize - * @param {String} textFontColor + * @param {Object} env * @todo when we get to HTML report generation for the front end we will rename this class and create a new one for HTML - * @todo adopt settings.js */ - constructor (font, fontSize, textFontSize, textFontColor) { - this.font = font ? font : 'Avenir Next' - this.heavyFont = 'Avenir Next Heavy' - this.size = fontSize ? fontSize : 11 - this.textFontSize = textFontSize ? textFontSize : 22 - this.textFontColor = textFontColor ? textFontColor : '#41a6ce' - this.fontFactor = 1 + constructor (env) { + this.env = env + this.font = docxSettings.general.font + this.heavyFont = docxSettings.general.heavyFont + this.halfFontSize = docxSettings.general.halfFontSize + this.fullFontSize = docxSettings.general.fullFontSize + this.fontFactor = docxSettings.general.fontFactor + this.theme = this.env.theme + this.documentColor = docxSettings[this.theme].documentColor + this.textFontColor = `#${docxSettings[this.theme].textFontColor.toLowerCase()}` + this.titleFontColor = `#${docxSettings[this.theme].titleFontColor.toLowerCase()}` + this.tableBorderColor = `#${docxSettings[this.theme].tableBorderColor.toLowerCase()}` this.styling = this.initStyles() + this.fileSystem = new FilesystemOperators() this.regions = { AMER: 'Americas', EMEA: 'Europe, Middle East and Africa', @@ -47,6 +51,14 @@ class DOCXUtilities { } } + // Initials the working directories + initDirectories() { + const subdirs = ['interactions', 'images'] + for(const myDir in subdirs) { + this.fileSystem.safeMakedir(this.env.workDir + '/' + subdirs[myDir]) + } + } + // Initialize the common styles for the docx initStyles () { const hangingSpace = 0.18 @@ -54,7 +66,7 @@ class DOCXUtilities { default: { heading1: { run: { - size: this.textFontSize, + size: this.fullFontSize, bold: true, font: this.font, color: this.textFontColor @@ -68,7 +80,7 @@ class DOCXUtilities { }, heading2: { run: { - size: 0.75 * this.textFontSize, + size: 0.75 * this.fullFontSize, bold: true, font: this.font, color: this.textFontColor @@ -82,7 +94,7 @@ class DOCXUtilities { }, heading3: { run: { - size: 0.8 * this.textFontSize, + size: 0.8 * this.fullFontSize, bold: true, font: this.font, color: this.textFontColor @@ -97,12 +109,12 @@ class DOCXUtilities { listParagraph: { run: { font: this.font, - size: 1.5 * this.size, + size: 1.5 * this.halfFontSize, }, }, paragraph: { font: this.font, - size: this.size, + size: this.halfFontSize, } }, paragraphStyles: [ @@ -114,7 +126,7 @@ class DOCXUtilities { quickFormat: true, run: { font: this.font, - size: this.size, + size: this.halfFontSize, }, }, ], @@ -333,17 +345,59 @@ class DOCXUtilities { * @param {Integer} spaceAfter - an integer 1 or 0 to determine if there should be space after this element * @returns {Object} a docx paragraph object */ - makeParagraph (paragraph, size, bold, spaceAfter) { + // makeParagraph (paragraph, size, bold, spaceAfter) { + // return new docx.Paragraph({ + // children: [ + // new docx.TextRun({ + // text: paragraph, + // font: this.font, + // size: size ? size : 20, + // bold: bold ? bold : false, + // break: spaceAfter ? spaceAfter : 0 + // }) + // ] + // }) + // } + + /** + * + * @param {*} paragraph + * @param {*} size + * @param {*} color + * @param {*} spaceAfter + * @param {*} bold + * @param {*} center + * @param {*} font + * @returns + * @todo Replace the report/common.js makeParagraph method with this one during refactoring + * @todo Add an options object in a future release when refactoring + * @todo Review the NOTICE section and at a later date work on all TODOs there + */ + makeParagraph ( + paragraph, + spaceAfter=0, + bold=false, + center=false, + font="Avenir Next", + italics=false, + underline=false + ) { + const fontSize = 2 * this.fullFontSize // Font size is measured in half points, multiply by to is needed return new docx.Paragraph({ + alignment: center ? docx.AlignmentType.CENTER : docx.AlignmentType.LEFT, children: [ new docx.TextRun({ text: paragraph, font: this.font, - size: size ? size : 20, - bold: bold ? bold : false, - break: spaceAfter ? spaceAfter : 0 + size: this.fullFontSize, // Default font size size 10pt or 2 * 10 = 20 + bold: bold ? bold : false, // Bold is off by default + italics: italics ? italics : false, // Italics off by default + underline: underline ? underline : false, // Underline off by default + break: spaceAfter ? spaceAfter : 0, // Defaults to no trailing space after the paragraph + color: this.textFontColor }) - ] + ], + }) } diff --git a/src/report/companies.js b/src/report/companies.js index 184148f..7e56863 100644 --- a/src/report/companies.js +++ b/src/report/companies.js @@ -17,7 +17,22 @@ import FilesystemOperators from '../cli/filesystem.js' import { Utilities as CLIUtilities } from '../cli/common.js' import { getMostSimilarCompany } from './tools.js' -class CompanySection { +class BaseCompanyReport { + constructor(company, env) { + this.company = company + this.env = env + this.company.stock_symbol === 'Unknown' && this.company.cik === 'Unknown' ? + this.companyType = 'Private' : + this.companyType = 'Public' + this.util = new DOCXUtilities(env) + this.baseDir = this.env.outputDir + this.workDir = this.env.workDir + this.baseName = company.name.replace(/ /g,"_") + } + +} + +class CompanySection extends BaseCompanyReport { /** * A high level class to create sections for a Company report using either * Microsoft DOCX format or eventually HTML format. Right now the only available @@ -29,14 +44,8 @@ class CompanySection { * @todo Since the ingestion function detects the companyType this property is deprecated and should be removed * @todo separate this class into a separate file */ - constructor(company, baseDir) { - this.company = company - this.company.stock_symbol === 'Unknown' && this.company.cik === 'Unknown' ? - this.companyType = 'Private' : - this.companyType = 'Public' - this.util = new DOCXUtilities() - this.baseDir = baseDir - this.baseName = company.name.replace(/ /g,"_") + constructor(company, env) { + super(company, env) } // Create a URL on Google maps to search for the address @@ -244,7 +253,7 @@ class CompanySection { const competitor = competitors[myComp] // Construct the object to create company related document sections - const comp = new CompanySection(competitor.company) + const comp = new CompanySection(competitor.company, this.env) // Create a section for the most/least similar interactions const interact = new InteractionSection( @@ -312,7 +321,7 @@ class CompanySection { } -class CompanyStandalone { +class CompanyStandalone extends BaseCompanyReport { /** * A high level class to create a complete document for a Company report using either * Microsoft DOCX format or eventually HTML format. Right now the only available @@ -328,16 +337,13 @@ class CompanyStandalone { * @todo Adapt to settings.js for consistent application of settings, follow dashboard.js */ constructor(company, interactions, competitors, env, creator, author) { + super(company, env) this.objectType = 'Company' - this.env = env this.creator = creator this.author = author this.title = company.name + ' Company Report' - this.baseName = company.name.replace(/ /g,"_") - this.baseDir = this.env.workDir + '/' + this.baseName this.interactions = interactions this.competitors = competitors - this.company = company this.description = 'A Company report summarizing ' + company.name + ' and including relevant company data.' /* ChatGPT summary @@ -348,7 +354,7 @@ class CompanyStandalone { ' If this report document is produced as a package, instead of standalone, then the' + ' hyperlinks are active and will link to documents on the local folder after the' + ' package is opened.' - this.util = new DOCXUtilities() + this.util = new DOCXUtilities(env) this.fileSystem = new FilesystemOperators() // this.topics = this.util.rankTags(this.company.topics) this.comparison = company.comparison, @@ -383,11 +389,10 @@ class CompanyStandalone { */ async makeDOCX(fileName, isPackage) { // Initialize the working directories to create a package and/or download relevant images - this._initialize() + this.util.initDirectories() - // TODO when we remove fileName we can uncomment the item below - // const fileName = process.env.HOME + '/Documents/' + this.baseName + '.docx' - fileName = process.env.HOME + '/Documents/' + this.baseName + '.docx' + // Set the file name + fileName = fileName ? fileName : `${this.env.outputDir}/${this.baseName}.docx` // Capture the current date const date = new Date(); @@ -398,11 +403,12 @@ class CompanyStandalone { }) // Construct the company section - const companySection = new CompanySection(this.company, this.baseDir) + const companySection = new CompanySection(this.company, this.baseDir, env) const interactionSection = new InteractionSection( this.interactions, this.company.name, - this.objectType + this.objectType, + this.env ) const myDash = new CompanyDashbord(this.env) @@ -442,7 +448,7 @@ class CompanyStandalone { title: this.title, description: this.description, background: { - color: "0F0D0E", + color: this.util.documentColor, }, styles: {default: this.util.styling.default}, numbering: this.util.styling.numbering, diff --git a/src/report/dashboard.js b/src/report/dashboard.js index 531f331..17257a5 100644 --- a/src/report/dashboard.js +++ b/src/report/dashboard.js @@ -14,6 +14,220 @@ import DOCXUtilities from './common.js' import docxSettings from './settings.js' import { bubbleChart, radarChart } from './charts.js' +class Dashboards { + /** + * A high class meant to create an initial dashboard page for an MS Word document company report + * @constructor + * @classdesc To operate this class the constructor should be passed a the environmental setting for the object. + * @param {Object} env - Environmental variable settings for the CLI environment + * @param {String} theme - Governs the color of the dashboard, be either coffee or latte + */ + constructor(env) { + this.env = env + this.util = new DOCXUtilities(env) + this.themeStyle = docxSettings[env.theme] // Set the theme for the report + this.generalStyle = docxSettings.general // Pull in all of the general settings + + // Define specifics for table borders + this.noneStyle = { + style: this.generalStyle.noBorderStyle + } + this.borderStyle = { + style: this.generalStyle.tableBorderStyle, + size: this.generalStyle.tableBorderSize, + color: this.themeStyle.tableBorderColor + } + // No borders + this.noBorders = { + left: this.noneStyle, + right: this.noneStyle, + top: this.noneStyle, + bottom: this.noneStyle + } + // Right border only + this.rightBorder = { + left: this.noneStyle, + right: this.borderStyle, + top: this.noneStyle, + bottom: this.noneStyle + } + // Bottom border only + this.bottomBorder = { + left: this.noneStyle, + right: this.noneStyle, + top: this.noneStyle, + bottom: this.borderStyle + } + // Bottom and right borders + this.bottomAndRightBorders = { + left: this.noneStyle, + right: this.borderStyle, + top: this.noneStyle, + bottom: this.borderStyle + } + // Top and right borders + this.topAndRightBorders = { + left: this.noneStyle, + right: this.borderStyle, + top: this.borderStyle, + bottom: this.noneStyle + } + // All borders, helpful for debugging + this.allBorders = { + left: this.borderStyle, + right: this.borderStyle, + top: this.borderStyle, + bottom: this.borderStyle + } + + } + + // Following the _statisticsTable method in the CompanyDashboard class create a similar method for all dashboards + /** + * + * + * @param {*} statistics + * @returns + * @todo turn into a loop instead of having the code repeated + * @todo if the length of any number is greater than 3 digits shrink the font size by 15% and round down + * @todo add a check for the length of the title and shrink the font size by 15% and round down + * @todo add a check for the length of the value and shrink the font size by 15% and round down + */ + descriptiveStatisticsTable(statistics) { + let myRows = [] + for(const stat in statistics) { + myRows.push( + new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [ + this.util.makeParagraph( + statistics[stat].value, + this.generalStyle.metricFontSize, + this.themeStyle.titleFontColor, + 0, + true, + true, + this.generalStyle.heavyFont + ) + ], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + } + }), + ] + }), + new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [ + this.util.makeParagraph( + statistics[stat].title, + this.generalStyle.metricFontTitleSize, + this.themeStyle.titleFontColor, + 0, + false, + true + ) + ], + borders: this.noBorders, + margins: { + bottom: this.generalStyle.tableMargin, + top: this.generalStyle.tableMargin + } + }), + ] + }) + ) + } + return new docx.Table({ + columnWidths: [95], + rows: myRows, + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) + } + + /** + * + * @param {*} imageFile + * @param {*} height + * @param {*} width + * @returns + * @todo move to common + */ + insertImage (imageFile, height, width) { + const myFile = fs.readFileSync(imageFile) + return new docx.Paragraph({ + alignment: docx.AlignmentType.CENTER, + children: [ + new docx.ImageRun({ + data: myFile, + transformation: { + height: height, + width: width + } + }) + ] + }) + } +} + +class InteractionDashboard extends Dashboards { + /** + * A class meant to create an initial dashboard page for an MS Word document interaction report + * @constructor + * @classdesc To operate this class the constructor should be passed a the environmental setting for the object. + * @param {Object} env - Environmental variable settings for the CLI environment + */ + constructor(env) { + super(env) + } + + // Create the first row with two images and a nested table + // 40% | 40% | 20% + // bubble chart | radar chart | nested table for stats + firstRow (bubbleImage, radarImage, statisticsTable) { + return new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [this.insertImage(bubbleImage, 226, 259.2)], + borders: this.bottomAndRightBorders + }), + new docx.TableCell({ + children: [this.insertImage(radarImage, 226, 345.6)], + borders: this.bottomAndRightBorders + }), + new docx.TableCell({ + children: [statisticsTable], + borders: this.noBorders, + rowSpan: 5, + margins: { + left: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin, + bottom: this.generalStyle.tableMargin, + top: this.generalStyle.tableMargin + }, + verticalAlign: docx.VerticalAlign.CENTER, + }), + ] + }) + } + + async makeDashboard(interaction, company) { + // TODO Create the table around this data, note that we're also missing the cells of the first row, but we're writing the file + const statisticsTable = super.descriptiveStatisticsTable([ + {title: 'Proto-requirements', value: Object.keys(interaction.topics).length}, + {title: 'Estimated reading time (minutes)', value: interaction.reading_time}, + {title: 'Page count', value: interaction.page_count}, + ]) + return statisticsTable + } +} + class CompanyDashbord { /** * A high class meant to create an initial dashboard page for an MS Word document company report @@ -24,7 +238,7 @@ class CompanyDashbord { */ constructor(env) { this.env = env - this.util = new DOCXUtilities() + this.util = new DOCXUtilities(env) this.themeStyle = docxSettings[env.theme] // Set the theme for the report this.generalStyle = docxSettings.general // Pull in all of the general settings @@ -713,5 +927,6 @@ class CompanyDashbord { } export { - CompanyDashbord + CompanyDashbord, + InteractionDashboard } \ No newline at end of file diff --git a/src/report/interactions.js b/src/report/interactions.js index 2d7ff9b..cc92804 100644 --- a/src/report/interactions.js +++ b/src/report/interactions.js @@ -11,9 +11,27 @@ import docx from 'docx' import DOCXUtilities from './common.js' import { CompanySection } from './companies.js' +import { InteractionDashboard } from './dashboard.js' +class BaseInteractionsReport { + constructor(interactions, objectType, objectName, env) { -class InteractionSection { + // NOTE creation of a ZIP package is something we likely need some workspace for + // since the documents should be downloaded and then archived. Therefore, + // the CLI is a likely place to do this for now. Suspect for the web_ui + // we will need some server side logic to make this happen. + + this.interactions = interactions + this.objectName = objectName + this.objectType = objectType + this.env = env + this.util = new DOCXUtilities(env) + } + +} + + +class InteractionSection extends BaseInteractionsReport { /** * A high level class to create sections for an Interaction report using either * Microsoft DOCX format or eventually HTML format. Right now the only available @@ -25,18 +43,8 @@ class InteractionSection { * @param {String} objectName - the name of the object calling this class * @param {String} objectType - the type of object calling this class */ - constructor(interactions, objectName, objectType) { - - // NOTE creation of a ZIP package is something we likely need some workspace for - // since the documents should be downloaded and then archived. Therefore, - // the CLI is a likely place to do this for now. Suspect for the web_ui - // we will need some server side logic to make this happen. - - this.interactions = interactions - this.objectName = objectName - this.objectType = objectType - this.fontSize = 10 // We need to pass this in from the config file - this.util = new DOCXUtilities() + constructor(interactions, objectName, objectType, env) { + super(interactions, objectType, objectName, env) } /** @@ -182,7 +190,7 @@ class InteractionSection { // Create the abstract for the interaction this.util.makeParagraph( this.interactions[interaction].abstract, - this.fontSize * 1.5 + this.util.halfFontSize * 1.5 ), // NOTE: Early reviews by users show topcis are confusing // this.util.makeHeading2('Topics'), @@ -208,7 +216,7 @@ class InteractionSection { } } -class InteractionStandalone { +class InteractionStandalone extends BaseInteractionsReport { /** * A high level class to create a complete document for an Interaction report using either * Microsoft DOCX format or eventually HTML format. Right now the only available @@ -220,9 +228,12 @@ class InteractionStandalone { * @param {String} creator - A string defining the creator for this document * @param {String} authorCompany - A string containing the company who authored the document */ - constructor(interaction, company, creator, authorCompany) { + constructor(interaction, company, creator, authorCompany, env) { + super([interaction], 'Interaction', interaction.name, env) this.creator = creator + this.author = authorCompany this.authorCompany = authorCompany + this.authoredBy = 'Mediumroast for GitHub' this.title = interaction.name + ' Interaction Report' this.interaction = interaction this.company = company @@ -233,8 +244,7 @@ class InteractionStandalone { ' hyperlinks are active and will link to documents on the local folder after the' + ' package is opened.' this.abstract = interaction.abstract - this.util = new DOCXUtilities() - this.topics = this.util.rankTags(this.interaction.topics) + this.topics = this.util.rankTags(this.interaction.tags) } metadataTableDOCX (isPackage) { @@ -272,11 +282,25 @@ class InteractionStandalone { * @returns {Array} The result of the writeReport function that is an Array */ async makeDOCX(fileName, isPackage) { + // Initialize the working directories + this.util.initDirectories() + // If fileName isn't specified create a default - fileName = fileName ? fileName : process.env.HOME + '/Documents/' + this.interaction.name.replace(/ /g,"_") + '.docx' + fileName = fileName ? fileName : `${this.env.outputDir}/${this.interaction.name.replace(/ /g,"_")}.docx` + + // Capture the current date + const date = new Date(); + const preparedDate = date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric" + }) // Construct the company section - const companySection = new CompanySection(this.company) + const companySection = new CompanySection(this.company, this.env) + + // Construct the dashboard section + const myDash = new InteractionDashboard(this.env) // Set up the default options for the document const myDocument = [].concat( @@ -295,15 +319,52 @@ class InteractionStandalone { // Construct the document const myDoc = new docx.Document ({ creator: this.creator, - company: this.authorCompany, + company: this.author, title: this.title, description: this.description, + background: { + color: this.util.documentColor, + }, styles: {default: this.util.styling.default}, numbering: this.util.styling.numbering, - sections: [{ - properties: {}, - children: myDocument, - }], + sections: [ + { + properties: { + page: { + size: { + orientation: docx.PageOrientation.LANDSCAPE, + }, + }, + }, + headers: { + default: this.util.makeHeader(this.interaction.name, 'Interaction dashboard for: ', true) + }, + footers: { + default: new docx.Footer({ + children: [this.util.makeFooter(`Authored by: ${this.authoredBy}`, `Prepared on: ${preparedDate}`, true)] + }) + }, + children: [ + await myDash.makeDashboard( + this.company, + this.competitors, + this.baseDir + ) + ], + }, + { + properties: {}, + headers: { + default: this.util.makeHeader(this.interaction.name, 'Company comparison detail prepared for: ') + }, + footers: { + default: new docx.Footer({ + children: [this.util.makeFooter('Authored by: mediumroast.io', 'Prepared on: ' + preparedDate)] + }) + }, + children: myDocument, + } + ], }) // Persist the document to storage diff --git a/src/report/settings.js b/src/report/settings.js index 5eedde1..cb2f171 100644 --- a/src/report/settings.js +++ b/src/report/settings.js @@ -2,7 +2,9 @@ import docx from 'docx' const docxSettings = { general: { - fontSize: 10, + halfFontSize: 11, + fullFontSize: 22, + fontFactor: 1, dashFontSize: 9, tableFontSize: 8, titleFontSize: 18, @@ -27,7 +29,7 @@ const docxSettings = { tableBorderColor: "4A7E92", // Light Blue documentColor: "0F0D0E", // Coffee black titleFontColor: "41A6CE", // Saturated Light Blue - fontColor: "DCE9F6", // Ultra light Blue + textFontColor: "DCE9F6", // Ultra light Blue chartAxisLineColor: "#374246", chartAxisFontColor: "rgba(71,121,140, 0.7)", chartAxisTickFontColor: "rgba(149,181,192, 0.6)", From 2b53823035d3215d3b15c249bd29717cf7d95d52 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Mon, 29 Jul 2024 07:44:14 +0900 Subject: [PATCH 3/6] Incremental checkin: right stats, beginning of name, desc --- src/report/common.js | 47 +++++++++-------- src/report/companies.js | 14 +++-- src/report/dashboard.js | 101 +++++++++++++++++++++++++++++-------- src/report/interactions.js | 20 +++++--- src/report/settings.js | 1 + 5 files changed, 128 insertions(+), 55 deletions(-) diff --git a/src/report/common.js b/src/report/common.js index 3417929..15aff8c 100644 --- a/src/report/common.js +++ b/src/report/common.js @@ -32,6 +32,7 @@ class DOCXUtilities { */ constructor (env) { this.env = env + this.generalSettings = docxSettings.general this.font = docxSettings.general.font this.heavyFont = docxSettings.general.heavyFont this.halfFontSize = docxSettings.general.halfFontSize @@ -286,7 +287,10 @@ class DOCXUtilities { * @param {String} documentType * @param {Boolean} landscape */ - makeHeader(itemName, documentType, landscape=false) { + makeHeader(itemName, documentType, options={}) { + const { + landscape = false + } = options let separator = "\t".repeat(3) if (landscape) { separator = "\t".repeat(4)} return new docx.Header({ @@ -297,13 +301,12 @@ class DOCXUtilities { new docx.TextRun({ children: [documentType], font: this.font, - size: 20 + size: this.generalSettings.headerFontSize }), new docx.TextRun({ children: [itemName], font: this.heavyFont, - size: 20, - color: "c7701e" + size: this.generalSettings.headerFontSize }) ], }), @@ -373,28 +376,30 @@ class DOCXUtilities { * @todo Add an options object in a future release when refactoring * @todo Review the NOTICE section and at a later date work on all TODOs there */ - makeParagraph ( - paragraph, - spaceAfter=0, - bold=false, - center=false, - font="Avenir Next", - italics=false, - underline=false - ) { - const fontSize = 2 * this.fullFontSize // Font size is measured in half points, multiply by to is needed + makeParagraph (paragraph,options={}) { + const { + fontSize, + bold=false, + fontColor, + font='Avenir Next', + center=false, + italics=false, + underline=false, + spaceAfter=0, + } = options + // const fontSize = 2 * this.fullFontSize // Font size is measured in half points, multiply by to is needed return new docx.Paragraph({ alignment: center ? docx.AlignmentType.CENTER : docx.AlignmentType.LEFT, children: [ new docx.TextRun({ text: paragraph, - font: this.font, - size: this.fullFontSize, // Default font size size 10pt or 2 * 10 = 20 + font: font ? font : this.font, + size: fontSize ? fontSize : this.fullFontSize, // Default font size size 10pt or 2 * 10 = 20 bold: bold ? bold : false, // Bold is off by default italics: italics ? italics : false, // Italics off by default underline: underline ? underline : false, // Underline off by default break: spaceAfter ? spaceAfter : 0, // Defaults to no trailing space after the paragraph - color: this.textFontColor + color: fontColor ? fontColor : this.textFontColor }) ], @@ -604,14 +609,14 @@ class DOCXUtilities { size: 20, type: docx.WidthType.PERCENTAGE }, - children: [this.makeParagraph(name, this.fontFactor * this.fontSize, true)] + children: [this.makeParagraph(name, {fontSize: this.fontFactor * this.fontSize, bold: true})] }), new docx.TableCell({ width: { size: 80, type: docx.WidthType.PERCENTAGE }, - children: [this.makeParagraph(data, this.fontFactor * this.fontSize)] + children: [this.makeParagraph(data, {fontSize: this.fontFactor * this.fontSize})] }) ] }) @@ -633,14 +638,14 @@ class DOCXUtilities { size: 10, type: docx.WidthType.PERCENTAGE }, - children: [this.makeParagraph(id, 16, bold ? true : false)] + children: [this.makeParagraph(id, {fontSize: 16, bold: bold ? true : false})] }), new docx.TableCell({ width: { size: 90, type: docx.WidthType.PERCENTAGE }, - children: [this.makeParagraph(description, 16, bold ? true : false)] + children: [this.makeParagraph(description, {fontSize: 16, bold: bold ? true : false})] }) ] }) diff --git a/src/report/companies.js b/src/report/companies.js index 7e56863..73dbc8c 100644 --- a/src/report/companies.js +++ b/src/report/companies.js @@ -341,6 +341,7 @@ class CompanyStandalone extends BaseCompanyReport { this.objectType = 'Company' this.creator = creator this.author = author + this.authoredBy = 'Mediumroast for GitHub' this.title = company.name + ' Company Report' this.interactions = interactions this.competitors = competitors @@ -401,6 +402,11 @@ class CompanyStandalone extends BaseCompanyReport { month: "long", day: "numeric" }) + + // Set up strings for headers and footers + const preparedOn = `Prepared on: ${preparedDate}` + const authoredBy = `Authored by: ${this.authoredBy}` + const preparedFor = `${this.authoredBy} report for: ` // Construct the company section const companySection = new CompanySection(this.company, this.baseDir, env) @@ -462,11 +468,11 @@ class CompanyStandalone extends BaseCompanyReport { }, }, headers: { - default: this.util.makeHeader(this.company.name, 'Company comparison dashboard prepared for: ', true) + default: this.util.makeHeader(this.company.name, preparedFor, {landscape: true}) }, footers: { default: new docx.Footer({ - children: [this.util.makeFooter('Authored by: mediumroast.io', 'Prepared on: ' + preparedDate, true)] + children: [this.util.makeFooter(authoredBy, preparedOn, {landscape: true})] }) }, children: [ @@ -480,11 +486,11 @@ class CompanyStandalone extends BaseCompanyReport { { properties: {}, headers: { - default: this.util.makeHeader(this.company.name, 'Company comparison detail prepared for: ') + default: this.util.makeHeader(this.company.name, preparedFor) }, footers: { default: new docx.Footer({ - children: [this.util.makeFooter('Authored by: mediumroast.io', 'Prepared on: ' + preparedDate)] + children: [this.util.makeFooter(authoredBy, preparedOn)] }) }, children: myDocument, diff --git a/src/report/dashboard.js b/src/report/dashboard.js index 17257a5..75d6c56 100644 --- a/src/report/dashboard.js +++ b/src/report/dashboard.js @@ -103,12 +103,13 @@ class Dashboards { children: [ this.util.makeParagraph( statistics[stat].value, - this.generalStyle.metricFontSize, - this.themeStyle.titleFontColor, - 0, - true, - true, - this.generalStyle.heavyFont + { + fontSize: this.generalStyle.metricFontSize, + fontColor: this.themeStyle.titleFontColor, + font: this.generalStyle.heavyFont, + bold: true, + center: true + } ) ], borders: this.bottomBorder, @@ -124,11 +125,12 @@ class Dashboards { children: [ this.util.makeParagraph( statistics[stat].title, - this.generalStyle.metricFontTitleSize, - this.themeStyle.titleFontColor, - 0, - false, - true + { + fontSize: this.generalStyle.metricFontSize/2, + fontColor: this.themeStyle.titleFontColor, + bold: false, + center: true + } ) ], borders: this.noBorders, @@ -151,6 +153,24 @@ class Dashboards { }) } + // Using DOCXUtilties basicRow method create a similar method for all dashboards that returns a table with a single row + /** + * + * @param {*} data + * @returns + */ + simpleDescriptiveTable(title, text) { + const myRows = [this.util.basicRow(title, text)] + return new docx.Table({ + columnWidths: [95], + rows: myRows, + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) + } + /** * * @param {*} imageFile @@ -190,21 +210,39 @@ class InteractionDashboard extends Dashboards { // Create the first row with two images and a nested table // 40% | 40% | 20% // bubble chart | radar chart | nested table for stats - firstRow (bubbleImage, radarImage, statisticsTable) { + firstRow (interactionData, statisticsData) { return new docx.TableRow({ children: [ new docx.TableCell({ - children: [this.insertImage(bubbleImage, 226, 259.2)], - borders: this.bottomAndRightBorders + children: [interactionData.name], + borders: this.bottomBorder, + rowSpan: 1, + columnSpan:2, + margins: { + left: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin, + bottom: this.generalStyle.tableMargin, + top: this.generalStyle.tableMargin + }, + verticalAlign: docx.VerticalAlign.CENTER, }), new docx.TableCell({ - children: [this.insertImage(radarImage, 226, 345.6)], - borders: this.bottomAndRightBorders + children: [interactionData.description], + borders: this.bottomBorder, + rowSpan: 1, + columnSpan:2, + margins: { + left: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin, + bottom: this.generalStyle.tableMargin, + top: this.generalStyle.tableMargin + }, + verticalAlign: docx.VerticalAlign.CENTER, }), new docx.TableCell({ - children: [statisticsTable], - borders: this.noBorders, - rowSpan: 5, + children: [statisticsData], + borders: this.allBorders, + rowSpan: 1, margins: { left: this.generalStyle.tableMargin, right: this.generalStyle.tableMargin, @@ -219,12 +257,31 @@ class InteractionDashboard extends Dashboards { async makeDashboard(interaction, company) { // TODO Create the table around this data, note that we're also missing the cells of the first row, but we're writing the file - const statisticsTable = super.descriptiveStatisticsTable([ - {title: 'Proto-requirements', value: Object.keys(interaction.topics).length}, + + const statsData = super.descriptiveStatisticsTable([ + {title: 'Interaction type', value: interaction.interaction_type}, {title: 'Estimated reading time (minutes)', value: interaction.reading_time}, {title: 'Page count', value: interaction.page_count}, + {title: 'Region', value: interaction.region}, + {title: 'Proto-requirements', value: Object.keys(interaction.topics).length}, ]) - return statisticsTable + const interactionNameTable = super.simpleDescriptiveTable('Interaction Name', interaction.name) + const interactionDescriptionTable = super.simpleDescriptiveTable('Interaction Description', interaction.description) + const myRows = this.firstRow({name: interactionNameTable, description: interactionDescriptionTable}, statsData) + const myTable = new docx.Table({ + columnWidths: [40, 20], + rows: [myRows], + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + }, + height: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) + + return myTable } } diff --git a/src/report/interactions.js b/src/report/interactions.js index cc92804..9388ea7 100644 --- a/src/report/interactions.js +++ b/src/report/interactions.js @@ -190,7 +190,7 @@ class InteractionSection extends BaseInteractionsReport { // Create the abstract for the interaction this.util.makeParagraph( this.interactions[interaction].abstract, - this.util.halfFontSize * 1.5 + {fontSize: this.util.halfFontSize * 1.5} ), // NOTE: Early reviews by users show topcis are confusing // this.util.makeHeading2('Topics'), @@ -296,6 +296,11 @@ class InteractionStandalone extends BaseInteractionsReport { day: "numeric" }) + // Define header/footer content + const preparedOn = `Prepared on: ${preparedDate}` + const authoredBy = `Authored by: ${this.authoredBy}` + const preparedFor = `${this.objectType}: ` + // Construct the company section const companySection = new CompanySection(this.company, this.env) @@ -337,29 +342,28 @@ class InteractionStandalone extends BaseInteractionsReport { }, }, headers: { - default: this.util.makeHeader(this.interaction.name, 'Interaction dashboard for: ', true) + default: this.util.makeHeader(this.interaction.name, preparedFor, {landscape: true}) }, footers: { default: new docx.Footer({ - children: [this.util.makeFooter(`Authored by: ${this.authoredBy}`, `Prepared on: ${preparedDate}`, true)] + children: [this.util.makeFooter(authoredBy, preparedOn, {landscape: true})] }) }, children: [ await myDash.makeDashboard( - this.company, - this.competitors, - this.baseDir + this.interaction, + this.company ) ], }, { properties: {}, headers: { - default: this.util.makeHeader(this.interaction.name, 'Company comparison detail prepared for: ') + default: this.util.makeHeader(this.interaction.name, preparedFor) }, footers: { default: new docx.Footer({ - children: [this.util.makeFooter('Authored by: mediumroast.io', 'Prepared on: ' + preparedDate)] + children: [this.util.makeFooter(authoredBy, preparedOn)] }) }, children: myDocument, diff --git a/src/report/settings.js b/src/report/settings.js index cb2f171..64945b3 100644 --- a/src/report/settings.js +++ b/src/report/settings.js @@ -4,6 +4,7 @@ const docxSettings = { general: { halfFontSize: 11, fullFontSize: 22, + headerFontSize: 20, fontFactor: 1, dashFontSize: 9, tableFontSize: 8, From b8414e1809fb1a62343e0861284ff9c473920ed7 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Mon, 29 Jul 2024 20:04:56 +0900 Subject: [PATCH 4/6] Data in interactions dash is there, formatting problems --- src/report/dashboard.js | 185 +++++++++++++++++++++++++++++++++++----- 1 file changed, 163 insertions(+), 22 deletions(-) diff --git a/src/report/dashboard.js b/src/report/dashboard.js index 75d6c56..a5bec51 100644 --- a/src/report/dashboard.js +++ b/src/report/dashboard.js @@ -153,6 +153,40 @@ class Dashboards { }) } + twoCellRow (name, data) { + // return the row + return new docx.TableRow({ + children: [ + new docx.TableCell({ + width: { + size: 20, + type: docx.WidthType.PERCENTAGE + }, + children: [this.util.makeParagraph(name, {fontSize: this.fontFactor * this.fontSize, bold: true})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin + }, + padding: this.generalStyle.tableMargin + }), + new docx.TableCell({ + width: { + size: 80, + type: docx.WidthType.PERCENTAGE + }, + children: [this.util.makeParagraph(data, {fontSize: this.fontFactor * this.fontSize})], + borders: this.bottomAndRightBorders, + margins: { + top: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin + }, + padding: this.generalStyle.tableMargin + }) + ] + }) + } + // Using DOCXUtilties basicRow method create a similar method for all dashboards that returns a table with a single row /** * @@ -160,10 +194,17 @@ class Dashboards { * @returns */ simpleDescriptiveTable(title, text) { - const myRows = [this.util.basicRow(title, text)] + const myRows = this.twoCellRow(title, text) return new docx.Table({ columnWidths: [95], - rows: myRows, + borders: this.noBorders, + margins: { + left: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin, + bottom: this.generalStyle.tableMargin, + top: this.generalStyle.tableMargin + }, + rows: [myRows], width: { size: 100, type: docx.WidthType.PERCENTAGE @@ -207,17 +248,13 @@ class InteractionDashboard extends Dashboards { super(env) } - // Create the first row with two images and a nested table - // 40% | 40% | 20% - // bubble chart | radar chart | nested table for stats firstRow (interactionData, statisticsData) { return new docx.TableRow({ children: [ new docx.TableCell({ - children: [interactionData.name], - borders: this.bottomBorder, - rowSpan: 1, - columnSpan:2, + children: [interactionData.name, interactionData.description], + borders: this.noBorders, + columnSpan:3, margins: { left: this.generalStyle.tableMargin, right: this.generalStyle.tableMargin, @@ -227,10 +264,9 @@ class InteractionDashboard extends Dashboards { verticalAlign: docx.VerticalAlign.CENTER, }), new docx.TableCell({ - children: [interactionData.description], - borders: this.bottomBorder, - rowSpan: 1, - columnSpan:2, + children: [statisticsData], + borders: this.noBorders, + rowSpan: 3, margins: { left: this.generalStyle.tableMargin, right: this.generalStyle.tableMargin, @@ -239,10 +275,18 @@ class InteractionDashboard extends Dashboards { }, verticalAlign: docx.VerticalAlign.CENTER, }), + ] + }) + } + + secondRow (companyData) { + return new docx.TableRow({ + children: [ new docx.TableCell({ - children: [statisticsData], - borders: this.allBorders, + children: [companyData], + borders: this.noBorders, rowSpan: 1, + columnSpan:3, margins: { left: this.generalStyle.tableMargin, right: this.generalStyle.tableMargin, @@ -255,22 +299,119 @@ class InteractionDashboard extends Dashboards { }) } + _getTopTwoTopics(topics) { + // Convert the topics object to an array of entries + const topicsArray = Object.entries(topics) + + // Sort the array based on the frequency property in descending order + topicsArray.sort((a, b) => b[1].frequency - a[1].frequency) + + // Slice the array to get the top 2 entries + const top2TopicsArray = topicsArray.slice(0, 2) + + // Convert the array back to an object + const top2Topics = Object.fromEntries(top2TopicsArray); + + return top2Topics + } + + thirdRowHeader () { + return new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [this.util.makeParagraph('Id', {fontSize: this.fontFactor * this.fontSize, bold: true})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + } + }), + new docx.TableCell({ + children: [this.util.makeParagraph('Frequency', {fontSize: this.fontFactor * this.fontSize, bold: true})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + } + }), + new docx.TableCell({ + children: [this.util.makeParagraph('Description', {fontSize: this.fontFactor * this.fontSize, bold: true})], + borders: this.bottomAndRightBorders, + margins: { + top: this.generalStyle.tableMargin + } + }), + ] + }) + } + + thirdRow (top2Topics) { + // Create a table row for the header + let myRows = [this.thirdRowHeader()] + + // TODO set width of columns to 33.33% each + + // Change to two column with Frequency and Description/Label + + // The other items are contained withing their own tables making the sizing of the columns difficult + + // Loop through the top 2 topics and create a table row for each topic + for (const topic in top2Topics) { + myRows.push( + new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [this.util.makeParagraph(topic, {fontSize: this.fontFactor * this.fontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + } + }), + new docx.TableCell({ + children: [this.util.makeParagraph(top2Topics[topic].frequency, {fontSize: this.fontFactor * this.fontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + } + }), + new docx.TableCell({ + children: [this.util.makeParagraph(top2Topics[topic].label, {fontSize: this.fontFactor * this.fontSize})], + borders: this.bottomAndRightBorders, + margins: { + top: this.generalStyle.tableMargin + } + }), + ] + }) + ) + } + return myRows + } + async makeDashboard(interaction, company) { // TODO Create the table around this data, note that we're also missing the cells of the first row, but we're writing the file + // Add key metadata for the interaction const statsData = super.descriptiveStatisticsTable([ - {title: 'Interaction type', value: interaction.interaction_type}, - {title: 'Estimated reading time (minutes)', value: interaction.reading_time}, - {title: 'Page count', value: interaction.page_count}, + {title: 'Type', value: interaction.interaction_type}, + {title: 'Est. reading time (min)', value: interaction.reading_time}, + {title: 'Page(s)', value: interaction.page_count}, {title: 'Region', value: interaction.region}, {title: 'Proto-requirements', value: Object.keys(interaction.topics).length}, ]) - const interactionNameTable = super.simpleDescriptiveTable('Interaction Name', interaction.name) - const interactionDescriptionTable = super.simpleDescriptiveTable('Interaction Description', interaction.description) - const myRows = this.firstRow({name: interactionNameTable, description: interactionDescriptionTable}, statsData) + + // Add the name and description of the interaction + const interactionNameTable = super.simpleDescriptiveTable('Name', interaction.name) + const interactionDescriptionTable = super.simpleDescriptiveTable('Description', interaction.description) + let myRows = [this.firstRow({name: interactionNameTable, description: interactionDescriptionTable}, statsData)] + + // Add the company data + myRows.push(this.secondRow(super.simpleDescriptiveTable(`Linked company: ${company.name}`, company.description))) + + // Add the top two topics + const top2Topics = this._getTopTwoTopics(interaction.topics) + myRows = myRows.concat(this.thirdRow(top2Topics)) const myTable = new docx.Table({ columnWidths: [40, 20], - rows: [myRows], + rows: myRows, width: { size: 100, type: docx.WidthType.PERCENTAGE From 180dd7881005eeac5891d39df2765631c7d9c027 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Sat, 3 Aug 2024 11:36:20 +0900 Subject: [PATCH 5/6] Interaction dash, tags, and cleanup --- src/report/common.js | 106 +++++++++++---- src/report/dashboard.js | 262 ++++++++++++++++++------------------- src/report/interactions.js | 8 +- src/report/settings.js | 3 + 4 files changed, 206 insertions(+), 173 deletions(-) diff --git a/src/report/common.js b/src/report/common.js index 15aff8c..69a5f62 100644 --- a/src/report/common.js +++ b/src/report/common.js @@ -33,6 +33,7 @@ class DOCXUtilities { constructor (env) { this.env = env this.generalSettings = docxSettings.general + this.themeSettings = docxSettings[this.env.theme] this.font = docxSettings.general.font this.heavyFont = docxSettings.general.heavyFont this.halfFontSize = docxSettings.general.halfFontSize @@ -305,7 +306,7 @@ class DOCXUtilities { }), new docx.TextRun({ children: [itemName], - font: this.heavyFont, + font: this.font, size: this.generalSettings.headerFontSize }) ], @@ -398,39 +399,14 @@ class DOCXUtilities { bold: bold ? bold : false, // Bold is off by default italics: italics ? italics : false, // Italics off by default underline: underline ? underline : false, // Underline off by default - break: spaceAfter ? spaceAfter : 0, // Defaults to no trailing space after the paragraph + break: spaceAfter ? spaceAfter : 0, // Defaults to no trailing space color: fontColor ? fontColor : this.textFontColor }) - ], - + ] }) } - // - /** - * @function makeTextrun - * @description Create a text run with or without space after - * @param {String} text - text/prose for the textrun - * @param {Integer} spaceAfter - an integer 1 or 0 to determine if there should be space after this element - * @returns {Object} a docx textrun object - */ - makeTextrun(text, spaceAfter=false) { - const myFontSize = 16 - if (spaceAfter) { - return new docx.TextRun({ - text: text, - font: this.font, - size: myFontSize, - break: 1 - }) - } else { - return new docx.TextRun({ - text: text, - font: this.font, - size: myFontSize - }) - } - } + /** * @function pageBreak @@ -862,6 +838,78 @@ class DOCXUtilities { return myTable } + _tagCell(tag) { + return new docx.TableCell({ + margins: { + top: this.generalSettings.tagMargin, + right: this.generalSettings.tagMargin, + bottom: this.generalSettings.tagMargin, + left: this.generalSettings.tagMargin + }, + borders: { + top: { size: 20, color: this.themeSettings.documentColor, style: docx.BorderStyle.SINGLE }, // 2 points thick, black color + bottom: { size: 20, color: this.themeSettings.documentColor, style: docx.BorderStyle.SINGLE }, + left: { size: 20, color: this.themeSettings.documentColor, style: docx.BorderStyle.SINGLE }, + right: { size: 20, color: this.themeSettings.documentColor, style: docx.BorderStyle.SINGLE }, + }, + shading: {fill: this.themeSettings.tagColor}, + children: [this.makeParagraph(tag, {fontSize: this.fontSize, fontColor: this.themeSettings.tagFontColor, center: true})], + verticalAlign: docx.AlignmentType.CENTER, + }) + } + + _distributeTags(tagsList) { + // Calculate the number of lists as the ceiling of the square root of the length of tagsList + const numLists = Math.ceil(Math.sqrt(tagsList.length)) + + // Initialize result array with numLists empty arrays + const result = Array.from({ length: numLists }, () => []) + // Initialize lengths array with numLists zeros + const lengths = Array(numLists).fill(0) + + // Sort tagsList in descending order based on the length of the tags + tagsList.sort((a, b) => b.length - a.length) + + // Distribute tags + tagsList.forEach(tag => { + // Find the index of the child list with the minimum total character length + const minIndex = lengths.indexOf(Math.min(...lengths)) + // Add the tag to this child list + result[minIndex].push(tag); + // Update the total character length of this child list + lengths[minIndex] += tag.length + }) + + return result; + } + + tagsTable(tags) { + // Get the length of the tags + const tagsList = Object.keys(tags) + const distributedTags = this._distributeTags(tagsList) + let myRows = [] + distributedTags.forEach(tags => { + let cells = [] + tags.forEach(tag => { + cells.push(this._tagCell(tag)) + }) + myRows.push(new docx.TableRow({ + children: cells + })) + }) + // define the table with the summary theme information + const myTable = new docx.Table({ + columnWidths: Array(distributedTags.length).fill(100/distributedTags.length), + rows: myRows, + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) + + return myTable + } + // Create an introductory section diff --git a/src/report/dashboard.js b/src/report/dashboard.js index a5bec51..a118b6a 100644 --- a/src/report/dashboard.js +++ b/src/report/dashboard.js @@ -145,6 +145,7 @@ class Dashboards { } return new docx.Table({ columnWidths: [95], + borders: this.noBorders, rows: myRows, width: { size: 100, @@ -168,7 +169,6 @@ class Dashboards { top: this.generalStyle.tableMargin, right: this.generalStyle.tableMargin }, - padding: this.generalStyle.tableMargin }), new docx.TableCell({ width: { @@ -181,7 +181,6 @@ class Dashboards { top: this.generalStyle.tableMargin, right: this.generalStyle.tableMargin }, - padding: this.generalStyle.tableMargin }) ] }) @@ -194,7 +193,6 @@ class Dashboards { * @returns */ simpleDescriptiveTable(title, text) { - const myRows = this.twoCellRow(title, text) return new docx.Table({ columnWidths: [95], borders: this.noBorders, @@ -204,7 +202,7 @@ class Dashboards { bottom: this.generalStyle.tableMargin, top: this.generalStyle.tableMargin }, - rows: [myRows], + rows: [this.twoCellRow(title, text)], width: { size: 100, type: docx.WidthType.PERCENTAGE @@ -212,6 +210,16 @@ class Dashboards { }) } + // Create a utility that takes text as an input, and an integer called numSentences, splits the text into sentences and returns the first numSentences as a string + shortenText(text, numSentences=2) { + const sentences = text.split('.') + let shortText = '' + for (let i=0; i b[1].frequency - a[1].frequency) // Slice the array to get the top 2 entries - const top2TopicsArray = topicsArray.slice(0, 2) + const top2TopicsArray = topicsArray.slice(0, topicCount) // Convert the array back to an object const top2Topics = Object.fromEntries(top2TopicsArray); @@ -315,82 +272,104 @@ class InteractionDashboard extends Dashboards { return top2Topics } - thirdRowHeader () { - return new docx.TableRow({ - children: [ - new docx.TableCell({ - children: [this.util.makeParagraph('Id', {fontSize: this.fontFactor * this.fontSize, bold: true})], - borders: this.bottomBorder, - margins: { - top: this.generalStyle.tableMargin - } - }), - new docx.TableCell({ - children: [this.util.makeParagraph('Frequency', {fontSize: this.fontFactor * this.fontSize, bold: true})], - borders: this.bottomBorder, - margins: { - top: this.generalStyle.tableMargin - } - }), - new docx.TableCell({ - children: [this.util.makeParagraph('Description', {fontSize: this.fontFactor * this.fontSize, bold: true})], - borders: this.bottomAndRightBorders, - margins: { - top: this.generalStyle.tableMargin - } - }), - ] - }) - } - - thirdRow (top2Topics) { - // Create a table row for the header - let myRows = [this.thirdRowHeader()] - - // TODO set width of columns to 33.33% each - - // Change to two column with Frequency and Description/Label - - // The other items are contained withing their own tables making the sizing of the columns difficult - + _priorityTopicsTable (top2Topics) { + let myTopics = [] // Loop through the top 2 topics and create a table row for each topic for (const topic in top2Topics) { + myTopics.push( + super.simpleDescriptiveTable('Priority Proto-requirement', top2Topics[topic].label) + ) + } + return myTopics + } + + _mergeLeftContents (contents) { + let myRows = [] + for (const content in contents) { myRows.push( new docx.TableRow({ children: [ new docx.TableCell({ - children: [this.util.makeParagraph(topic, {fontSize: this.fontFactor * this.fontSize})], - borders: this.bottomBorder, - margins: { - top: this.generalStyle.tableMargin - } + children: [contents[content]], + borders: this.noBorders, + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + }, }), + ] + }) + ) + } + return new docx.Table({ + columnWidths: [95], + borders: this.noBorders, + margins: { + left: this.generalStyle.tableMargin, + right: this.generalStyle.tableMargin, + bottom: this.generalStyle.tableMargin, + top: this.generalStyle.tableMargin + }, + rows: myRows, + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) + } + + // Create the dashboard shell which will contain all of the outputs + _createDashboardShell (leftContents, rightContents) { + return new docx.Table({ + columnWidths: [80, 20], + rows: [ + new docx.TableRow({ + children: [ new docx.TableCell({ - children: [this.util.makeParagraph(top2Topics[topic].frequency, {fontSize: this.fontFactor * this.fontSize})], - borders: this.bottomBorder, - margins: { - top: this.generalStyle.tableMargin - } + children: [leftContents], + borders: this.noBorders }), new docx.TableCell({ - children: [this.util.makeParagraph(top2Topics[topic].label, {fontSize: this.fontFactor * this.fontSize})], - borders: this.bottomAndRightBorders, - margins: { - top: this.generalStyle.tableMargin - } + children: [rightContents], + borders: this.noBorders }), ] }) - ) - } - return myRows + ], + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + }, + height: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) } async makeDashboard(interaction, company) { - // TODO Create the table around this data, note that we're also missing the cells of the first row, but we're writing the file - - // Add key metadata for the interaction - const statsData = super.descriptiveStatisticsTable([ + // Define the right contents + // Add key metadata for the interaction to fit within the right contents + /* + ------------ + | Meta | + | -------- | + | Data | + | | + | Meta | + | -------- | + | Data | + | | + | Meta | + | -------- | + | Data | + | | + | Meta | + | -------- | + | Data | + ------------ + */ + const rightContents = super.descriptiveStatisticsTable([ {title: 'Type', value: interaction.interaction_type}, {title: 'Est. reading time (min)', value: interaction.reading_time}, {title: 'Page(s)', value: interaction.page_count}, @@ -398,31 +377,38 @@ class InteractionDashboard extends Dashboards { {title: 'Proto-requirements', value: Object.keys(interaction.topics).length}, ]) - // Add the name and description of the interaction + // Create individual tables for name, description and company + /* + ------------------------------ + | | | + ------------------------------ + + */ const interactionNameTable = super.simpleDescriptiveTable('Name', interaction.name) - const interactionDescriptionTable = super.simpleDescriptiveTable('Description', interaction.description) - let myRows = [this.firstRow({name: interactionNameTable, description: interactionDescriptionTable}, statsData)] - - // Add the company data - myRows.push(this.secondRow(super.simpleDescriptiveTable(`Linked company: ${company.name}`, company.description))) + const interactionDescriptionTable = super.simpleDescriptiveTable('Description', super.shortenText(interaction.description)) + const associatedCompanyTable = super.simpleDescriptiveTable(`Linked company: ${company.name}`, super.shortenText(company.description)) - // Add the top two topics const top2Topics = this._getTopTwoTopics(interaction.topics) - myRows = myRows.concat(this.thirdRow(top2Topics)) - const myTable = new docx.Table({ - columnWidths: [40, 20], - rows: myRows, - width: { - size: 100, - type: docx.WidthType.PERCENTAGE - }, - height: { - size: 100, - type: docx.WidthType.PERCENTAGE - } - }) + const protorequirementsTable = this._priorityTopicsTable(top2Topics)[0] + const leftContents = this._mergeLeftContents([interactionNameTable, interactionDescriptionTable, associatedCompanyTable, protorequirementsTable]) - return myTable + + + // Create and return the dashboard shell with left and right contents + /* + ---------------------------------------------- + | 80% | 20% | + | | | + | | | + | | | + | | | + | | | + | | | + | | | + ---------------------------------------------- + */ + + return this._createDashboardShell(leftContents, rightContents) } } diff --git a/src/report/interactions.js b/src/report/interactions.js index 9388ea7..1cbef03 100644 --- a/src/report/interactions.js +++ b/src/report/interactions.js @@ -311,14 +311,10 @@ class InteractionStandalone extends BaseInteractionsReport { const myDocument = [].concat( this.util.makeIntro(this.introduction), [ - this.util.makeHeading1('Interaction Detail'), - this.metadataTableDOCX(isPackage), - this.util.makeHeading1('Topics'), - this.util.topicTable(this.topics), this.util.makeHeading1('Abstract'), this.util.makeParagraph(this.abstract), - this.util.makeHeading1('Company Detail'), - companySection.makeFirmographicsDOCX() + this.util.makeHeading1('Tags'), + this.util.tagsTable(this.topics), ]) // Construct the document diff --git a/src/report/settings.js b/src/report/settings.js index 64945b3..e27a190 100644 --- a/src/report/settings.js +++ b/src/report/settings.js @@ -21,6 +21,7 @@ const docxSettings = { tableBorderStyle: docx.BorderStyle.SINGLE, noBorderStyle: docx.BorderStyle.NIL, tableMargin: docx.convertInchesToTwip(0.1), + tagMargin: docx.convertInchesToTwip(0.08), font: "Avenir Next", heavyFont: "Avenir Next Heavy", lightFont: "Avenir Next Light" @@ -28,6 +29,8 @@ const docxSettings = { }, coffee: { tableBorderColor: "4A7E92", // Light Blue + tagColor: "4A7E92", // Light Blue + tagFontColor: "0F0D0E", // Coffee black documentColor: "0F0D0E", // Coffee black titleFontColor: "41A6CE", // Saturated Light Blue textFontColor: "DCE9F6", // Ultra light Blue From 9117c8fe87a5da0d11a6c8c9ffa424a44731bda8 Mon Sep 17 00:00:00 2001 From: mihay42 Date: Sat, 3 Aug 2024 20:31:55 +0900 Subject: [PATCH 6/6] Interaction report complete --- src/report/common.js | 58 ++++++---------- src/report/dashboard.js | 6 +- src/report/interactions.js | 131 ++++++++++++++++++++++++++++++++++--- src/report/settings.js | 38 +++++++++-- 4 files changed, 176 insertions(+), 57 deletions(-) diff --git a/src/report/common.js b/src/report/common.js index 69a5f62..ec35b85 100644 --- a/src/report/common.js +++ b/src/report/common.js @@ -286,11 +286,12 @@ class DOCXUtilities { * @description Generate a header with an item's name and the document type fields * @param {String} itemName * @param {String} documentType - * @param {Boolean} landscape + * @param {Object} options */ makeHeader(itemName, documentType, options={}) { const { - landscape = false + landscape = false, + fontColor = this.textFontColor, } = options let separator = "\t".repeat(3) if (landscape) { separator = "\t".repeat(4)} @@ -302,12 +303,14 @@ class DOCXUtilities { new docx.TextRun({ children: [documentType], font: this.font, - size: this.generalSettings.headerFontSize + size: this.generalSettings.headerFontSize, + color: fontColor ? fontColor : this.textFontColor }), new docx.TextRun({ children: [itemName], font: this.font, - size: this.generalSettings.headerFontSize + size: this.generalSettings.headerFontSize, + color: fontColor ? fontColor : this.textFontColor }) ], }), @@ -315,7 +318,11 @@ class DOCXUtilities { }) } - makeFooter(documentAuthor, datePrepared, landscape=false) { + makeFooter(documentAuthor, datePrepared, options={}) { + const { + landscape = false, + fontColor = this.textFontColor, + } = options let separator = "\t" if (landscape) { separator = "\t".repeat(2)} return new docx.Paragraph({ @@ -324,17 +331,20 @@ class DOCXUtilities { new docx.TextRun({ children: ['Page ', docx.PageNumber.CURRENT, ' of ', docx.PageNumber.TOTAL_PAGES, separator], font: this.font, - size: 18 + size: this.generalSettings.footerFontSize, + color: fontColor ? fontColor : this.textFontColor }), new docx.TextRun({ children: ['|', separator, documentAuthor, separator], font: this.font, - size: 18 + size: this.generalSettings.footerFontSize, + color: fontColor ? fontColor : this.textFontColor }), new docx.TextRun({ children: ['|', separator, datePrepared], font: this.font, - size: 18 + size: this.generalSettings.footerFontSize, + color: fontColor ? fontColor : this.textFontColor }) ] }) @@ -344,39 +354,9 @@ class DOCXUtilities { * @function makeParagraph * @description For a section of prose create a paragraph * @param {String} paragraph - text/prose for the paragraph - * @param {Integer} size - font size for the paragrah - * @param {Boolean} bold - a boolean value for determining if the text should be bolded - * @param {Integer} spaceAfter - an integer 1 or 0 to determine if there should be space after this element + * @param {Object} objects - an object that contains the font size, color, and other styling options * @returns {Object} a docx paragraph object */ - // makeParagraph (paragraph, size, bold, spaceAfter) { - // return new docx.Paragraph({ - // children: [ - // new docx.TextRun({ - // text: paragraph, - // font: this.font, - // size: size ? size : 20, - // bold: bold ? bold : false, - // break: spaceAfter ? spaceAfter : 0 - // }) - // ] - // }) - // } - - /** - * - * @param {*} paragraph - * @param {*} size - * @param {*} color - * @param {*} spaceAfter - * @param {*} bold - * @param {*} center - * @param {*} font - * @returns - * @todo Replace the report/common.js makeParagraph method with this one during refactoring - * @todo Add an options object in a future release when refactoring - * @todo Review the NOTICE section and at a later date work on all TODOs there - */ makeParagraph (paragraph,options={}) { const { fontSize, diff --git a/src/report/dashboard.js b/src/report/dashboard.js index a118b6a..829eb27 100644 --- a/src/report/dashboard.js +++ b/src/report/dashboard.js @@ -163,7 +163,7 @@ class Dashboards { size: 20, type: docx.WidthType.PERCENTAGE }, - children: [this.util.makeParagraph(name, {fontSize: this.fontFactor * this.fontSize, bold: true})], + children: [this.util.makeParagraph(name, {fontSize: this.generalStyle.dashFontSize, bold: true})], borders: this.bottomBorder, margins: { top: this.generalStyle.tableMargin, @@ -175,7 +175,7 @@ class Dashboards { size: 80, type: docx.WidthType.PERCENTAGE }, - children: [this.util.makeParagraph(data, {fontSize: this.fontFactor * this.fontSize})], + children: [this.util.makeParagraph(data, {fontSize: this.generalStyle.dashFontSize})], borders: this.bottomAndRightBorders, margins: { top: this.generalStyle.tableMargin, @@ -321,7 +321,7 @@ class InteractionDashboard extends Dashboards { // Create the dashboard shell which will contain all of the outputs _createDashboardShell (leftContents, rightContents) { return new docx.Table({ - columnWidths: [80, 20], + columnWidths: [70, 30], rows: [ new docx.TableRow({ children: [ diff --git a/src/report/interactions.js b/src/report/interactions.js index 1cbef03..e30d353 100644 --- a/src/report/interactions.js +++ b/src/report/interactions.js @@ -12,6 +12,7 @@ import docx from 'docx' import DOCXUtilities from './common.js' import { CompanySection } from './companies.js' import { InteractionDashboard } from './dashboard.js' +import docxSettings from './settings.js' class BaseInteractionsReport { constructor(interactions, objectType, objectName, env) { @@ -26,8 +27,119 @@ class BaseInteractionsReport { this.objectType = objectType this.env = env this.util = new DOCXUtilities(env) + this.themeStyle = docxSettings[env.theme] // Set the theme for the report + this.generalStyle = docxSettings.general // Pull in all of the general settings + + // Define specifics for table borders + this.noneStyle = { + style: this.generalStyle.noBorderStyle + } + this.borderStyle = { + style: this.generalStyle.tableBorderStyle, + size: this.generalStyle.tableBorderSize, + color: this.themeStyle.tableBorderColor + } + this.bottomBorder = { + left: this.noneStyle, + right: this.noneStyle, + top: this.noneStyle, + bottom: this.borderStyle + } + } + + protorequirementsTable(topics) { + let myRows = [ + new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [this.util.makeParagraph('Id', {bold: true, fontSize: this.generalStyle.dashFontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + }, + width: { + size: 5, + type: docx.WidthType.PERCENTAGE + }, + }), + new docx.TableCell({ + children: [this.util.makeParagraph('Frequency', {bold: true, fontSize: this.generalStyle.dashFontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + }, + width: { + size: 15, + type: docx.WidthType.PERCENTAGE + } + }), + new docx.TableCell({ + children: [this.util.makeParagraph('Proto-requirement', {bold: true, fontSize: this.generalStyle.dashFontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + }, + width: { + size: 80, + type: docx.WidthType.PERCENTAGE + }, + }) + ] + }) + ] + for (const topic in topics) { + myRows.push( + new docx.TableRow({ + children: [ + new docx.TableCell({ + children: [this.util.makeParagraph(topic, {fontSize: this.generalStyle.dashFontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + }, + width: { + size: 5, + type: docx.WidthType.PERCENTAGE + }, + }), + new docx.TableCell({ + children: [this.util.makeParagraph(topics[topic].frequency, {fontSize: this.generalStyle.dashFontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + }, + width: { + size: 15, + type: docx.WidthType.PERCENTAGE + }, + }), + new docx.TableCell({ + children: [this.util.makeParagraph(topics[topic].label, {fontSize: this.generalStyle.dashFontSize})], + borders: this.bottomBorder, + margins: { + top: this.generalStyle.tableMargin + }, + width: { + size: 80, + type: docx.WidthType.PERCENTAGE + }, + }) + ] + }) + ) + } + // define the table with the summary theme information + const myTable = new docx.Table({ + columnWidths: [5, 20, 75], + rows: myRows, + width: { + size: 100, + type: docx.WidthType.PERCENTAGE + } + }) + + return myTable } - } @@ -202,7 +314,7 @@ class InteractionSection extends BaseInteractionsReport { references.splice(0,0, // Section intro this.util.makeParagraph( - 'The mediumroast.io system automatically generated this section.' + + 'The Mediumroast for GitHub automatically generated this section.' + ' It includes key metadata from each interaction associated to the object ' + this.objectName + '. If this report document is produced as a package, instead of standalone, then the' + ' hyperlinks are active and will link to documents on the local folder after the' + @@ -237,14 +349,15 @@ class InteractionStandalone extends BaseInteractionsReport { this.title = interaction.name + ' Interaction Report' this.interaction = interaction this.company = company - this.description = 'An Interaction report summarizing ' + interaction.name + ' and including relevant company data.' - this.introduction = 'The mediumroast.io system automatically generated this document.' + - ' It includes key metadata for this Interaction object and relevant metadata from the associated company.' + - ' If this report document is produced as a package, instead of standalone, then the' + + this.description = `An Interaction report summarizing ${interaction.name}.` + this.introduction = 'This document was automatically generated by Mediumroast for GitHub.' + + ' It includes an abstract, tags and proto-requirement for this Interaction.' + + ' If this report is produced as a package, then the' + ' hyperlinks are active and will link to documents on the local folder after the' + ' package is opened.' this.abstract = interaction.abstract - this.topics = this.util.rankTags(this.interaction.tags) + this.tags = this.util.rankTags(this.interaction.tags) + this.topics = this.interaction.topics } metadataTableDOCX (isPackage) { @@ -314,7 +427,9 @@ class InteractionStandalone extends BaseInteractionsReport { this.util.makeHeading1('Abstract'), this.util.makeParagraph(this.abstract), this.util.makeHeading1('Tags'), - this.util.tagsTable(this.topics), + this.util.tagsTable(this.tags), + this.util.makeHeading1('Proto-Requirements'), + super.protorequirementsTable(this.topics), ]) // Construct the document diff --git a/src/report/settings.js b/src/report/settings.js index e27a190..59dadc4 100644 --- a/src/report/settings.js +++ b/src/report/settings.js @@ -4,9 +4,10 @@ const docxSettings = { general: { halfFontSize: 11, fullFontSize: 22, - headerFontSize: 20, + headerFontSize: 18, + footerFontSize: 18, fontFactor: 1, - dashFontSize: 9, + dashFontSize: 22, tableFontSize: 8, titleFontSize: 18, companyNameFontSize: 11.5, @@ -33,7 +34,22 @@ const docxSettings = { tagFontColor: "0F0D0E", // Coffee black documentColor: "0F0D0E", // Coffee black titleFontColor: "41A6CE", // Saturated Light Blue - textFontColor: "DCE9F6", // Ultra light Blue + textFontColor: "41A6CE", // Ultra light Blue + chartAxisLineColor: "#374246", + chartAxisFontColor: "rgba(71,121,140, 0.7)", + chartAxisTickFontColor: "rgba(149,181,192, 0.6)", + chartItemFontColor: "rgba(149,181,192, 0.9)", + chartSeriesColor: "rgb(71,113,128)", + chartSeriesBorderColor: "rgba(149,181,192, 0.9)", + highlightFontColor: "" + }, + espresso: { + tableBorderColor: "C7701E", // Orange + tagColor: "C7701E", // Light Blue + tagFontColor: "0F0D0E", // Coffee black + documentColor: "0F0D0E", // Coffee black + titleFontColor: "C7701E", // Saturated Light Blue + textFontColor: "C7701E", // Ultra light Blue chartAxisLineColor: "#374246", chartAxisFontColor: "rgba(71,121,140, 0.7)", chartAxisTickFontColor: "rgba(149,181,192, 0.6)", @@ -43,10 +59,18 @@ const docxSettings = { highlightFontColor: "" }, latte: { - tableBorderColor: "4A7E92", // TODO find the right color - documentColor: "0F0D0E", // TODO find the right color - titleFontColor: "41A6CE", // TODO find the right color - fontColor: "DCE9F6", // TODO find the right color + tableBorderColor: "25110f", // Orange + tagColor: "25110f", // Light Blue + tagFontColor: "F1F0EE", // Coffee black + documentColor: "F1F0EE", // Coffee black + titleFontColor: "25110f", // Saturated Light Blue + textFontColor: "25110f", // Ultra light Blue + chartAxisLineColor: "#374246", + chartAxisFontColor: "rgba(71,121,140, 0.7)", + chartAxisTickFontColor: "rgba(149,181,192, 0.6)", + chartItemFontColor: "rgba(149,181,192, 0.9)", + chartSeriesColor: "rgb(71,113,128)", + chartSeriesBorderColor: "rgba(149,181,192, 0.9)", highlightFontColor: "" } }