diff --git a/generate-sales-report/.eslintrc.json b/generate-sales-report/.eslintrc.json deleted file mode 100644 index 1bc8c03..0000000 --- a/generate-sales-report/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../configs/appsscript.eslintrc.json" -} diff --git a/generate-sales-report/README.md b/generate-sales-report/README.md deleted file mode 100644 index c286ea3..0000000 --- a/generate-sales-report/README.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: Generate custom sales reports automatically -description: Connect to your sales data warehouse and automatically generate presentations for particular customers. -labels: Apps Script, Sheets, Slides, BigQuery -material_icon: pie_chart -create_time: 2019-12-10 -update_time: 2019-12-10 ---- - -Annual and quarterly reports are a standard part of business -for many enterprises. Luckily, automation can help eliminate the -repetition inherent in these periodic obligations. -If you find yourself needing to analyze and present findings -from large sets of stored data, consider using automation -to help streamline your reporting. - -This solution creates a tool that connects to sales data in -[BigQuery][bigquery], an analytics data warehouse, from directly within -Google Sheets. A configuration sheet allows a user to provide -parameters for the report, such as the _Account Name_ and -_Region_. With the click of a button, a customized report -with the latest sales data is automatically created in just -a matter of seconds! - -![summary](https://cdn.jsdelivr.net/gh/googleworkspace/solutions@main/generate-sales-report/demo.gif) - -Note: This solution requires a Google Workspace Enterprise account and a Google Cloud account and -project. - -## Technology highlights - -- Uses the [Sheets data connector for BigQuery][connector-support] - to access tables in a data warehouse from directly - within Google Sheets. -- Uses [Google Slides][google-slides] to create a templatized - report. -- Uses [Apps Script][apps-script] to - create a chart in Google Sheets, and merge it, along with - sales data, into the template report. - -## Try it - -### Set up a Google Cloud project -This solution requires a [Google Cloud account and project][gcp-account]. The service used in this solution, BigQuery, -has a [sandbox environment][sandbox] -that you can use to test this solution. - -1. [Sign in][sign-in] with your Google Workspace Business, Enterprise, -or Education Account credentials. -1. In the [Google Cloud Console][console], select or create a new Google Cloud project. - -### Create a templatized slide deck - -1. Make a copy of the template slide deck [here][slide-deck]. -1. Identify the unique ID of your Slides document. The ID can -be derived from the URL: `https://docs.google.com/presentation/d/`_**`slideId`**_`/edit` - -### Set up the configuration spreadsheet - -1. Make of copy of the template spreadsheet [here][spreadsheet]. -1. From the spreadsheet, open the script editor by selecting -**Extensions** > **Apps Script**. -1. Copy and paste your Slides document ID into line 1 of -`Constants.gs` replacing `YOUR_SLIDES_ID` and maintaining -the quotes. -1. Save the changes by navigating to **File > Save**. - -### Generate a new report - -1. Navigate to the Generator tab of your spreadsheet. -1. Choose an _Account Name_ and _Region_ in drop-down cells. -1. Navigate to the Data Results tab. At the bottom, next to -**Refresh**, click the three dots to expand the **More options** -menu. Select **Edit query**. -1. In the pop-up menu, expand **Query Settings** and make sure -your Google Cloud project is selected in the drop-down menu. -1. Click the **Connect** button. This will run the query. -In the future, you can run the query simply by clicking **Refresh**. -1. Return to the Generator tab and click on the large -Generate button at the bottom of the spreadsheet grid to -initiate the creation of the report. -1. When prompted, click the **Review permissions** button. -1. Select your Google Workspace account from the list. -1. Click the **Allow** button. -1. Once the script finishes executing, navigate to -[Google Drive][drive] and click on Recent in the -left-side navigation bar. Your newly minted report -will be at the top of the list! - -## Next steps - -To learn more about how a similar solution was built, -check out [this blog post][blog-post]. You can also view -the [full source code][github] of this solution on GitHub to -learn more about how it was built. - -You can read more about BigQuery in the -[product documentation][bigquery], and learn how to -load your own data [directly][load-data] or -[through solution providers][bq-providers]. - -[github]: https://github.com/googleworkspace/solutions/blob/main/generate-sales-report -[connector-support]: https://support.google.com/docs/answer/9077536 -[google-slides]: https://slides.google.com -[apps-script]: https://developers.google.com/apps-script/ -[gcp-account]: https://cloud.google.com/apis/docs/getting-started -[sandbox]: https://cloud.google.com/bigquery/docs/sandbox -[sign-in]: https://accounts.google.com/Login -[console]: https://console.cloud.google.com -[slide-deck]:https://docs.google.com/presentation/d/1w3TraCXAvtAx2BbYfF4FHIt976ILLqqffi5L5gRhDRA/copy -[spreadsheet]:https://docs.google.com/spreadsheets/d/17wEiZoXBOzMJPx1SkpVUcVylc9qaOHouEfNbccodNaI/copy -[drive]: https://drive.google.com -[blog-post]:https://cloud.google.com/blog/products/data-analytics/simplify-reporting-with-the-sheets-data-connector-for-bigquery-and-voila-automated-content-updates-for-g-suite -[bigquery]: https://cloud.google.com/bigquery/ -[load-data]: https://cloud.google.com/bigquery/docs/loading-data -[bq-providers]: https://cloud.google.com/bigquery/providers/ - diff --git a/generate-sales-report/demo.gif b/generate-sales-report/demo.gif deleted file mode 100644 index 67714f7..0000000 Binary files a/generate-sales-report/demo.gif and /dev/null differ diff --git a/generate-sales-report/src/Code.js b/generate-sales-report/src/Code.js deleted file mode 100644 index 63e0c56..0000000 --- a/generate-sales-report/src/Code.js +++ /dev/null @@ -1,245 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Default values -var ACCOUNT_NAME = 'Acme'; -var REGION = 'Midwest'; - -/** - * Creates a slide deck of sales opportunity data from the spreadsheet. - */ -function generateReport() { - var spreadsheet = SpreadsheetApp.getActive(); - var sheet = spreadsheet.getSheetByName('Data Results'); - var dataRange = sheet.getDataRange(); - var numRows = dataRange.getNumRows(); - ACCOUNT_NAME = spreadsheet.getRangeByName('AccountName').getValue(); - REGION = spreadsheet.getRangeByName('Region').getValue(); - - var presentationID = copyReportTemplate_(); - var presentation = SlidesApp.openById(presentationID); - - var slides = presentation.getSlides(); - introSlide_(slides[0], presentation); - leadsSlide_(slides[1], sheet, presentation, numRows); - - // Create a charts sheet to delete later - var tempSheet = spreadsheet.insertSheet('Charts Sheet', spreadsheet.getSheets().length); - stageSlide_(slides[2], presentation, sheet, tempSheet, numRows); - businessSlide_(slides[3], presentation, sheet, tempSheet, numRows); - topSlide_(slides[4], sheet); - nearSlide_(slides[5], sheet); - needsSlide_(slides[6], sheet); - - spreadsheet.deleteSheet(tempSheet); -} - -function copyReportTemplate_() { - var date = Utilities.formatDate(new Date(), 'GMT+1', 'MM-yyyy'); - var title = ACCOUNT_NAME + ' ' + REGION + '-' + date; - var template = DriveApp.getFileById(REPORT_SLIDES_TEMPLATE_ID); - var driveResponse = template.makeCopy(title); - return driveResponse.getId(); -} - -function introSlide_(slide, presentation) { - slide.replaceAllText('{{ACCOUNT_NAME}}', ACCOUNT_NAME); - slide.replaceAllText('{{REGION}}', REGION); - - var date = Utilities.formatDate(new Date(), 'GMT+1', 'yyyy-MM-dd'); - slide.replaceAllText('{{DATE}}', date); - - var imageUrl = DEFAULT_LOGO_IMAGE; - if (ACCOUNT_NAME == 'Acme') { - imageUrl = ACME_IMAGE; - } else if (ACCOUNT_NAME == 'Uniket') { - imageUrl = UNIKET_IMAGE; - } else { - imageUrl = GLOBAL_MEDIA_IMAGE; - } - - try { - var image = slide.insertImage(imageUrl, 100, 100, 100, 100); - } catch (e) { - var image = slide.insertImage(DEFAULT_LOGO_IMAGE, 100, 100, 100, 100); - } - - var imgWidth = image.getWidth(); - var imgHeight = image.getHeight(); - var pageWidth = presentation.getPageWidth(); - var pageHeight = presentation.getPageHeight(); - var newX = pageWidth - imgWidth - 20; - var newY = 20; - image.setLeft(newX).setTop(newY); -} - -function stageSlide_(slide, presentation, sheet, chartSheet, numRows) { - var chart = sheet.newChart() - .setChartType(Charts.ChartType.PIE) - .addRange(sheet.getRange('F1:F' + numRows)) - .addRange(sheet.getRange('C1:C' + numRows)) - .setOption('applyAggregateData', 0) - .setPosition(5, 5, 0, 0) - .build(); - - chartSheet.insertChart(chart); - var chartImage = slide.insertSheetsChartAsImage(chart, 200, 200, 400, 400); - var imgWidth = chartImage.getWidth(); - var imgHeight = chartImage.getHeight(); - var pageWidth = presentation.getPageWidth(); - var pageHeight = presentation.getPageHeight(); - var newX = pageWidth/2. - imgWidth/2.; - var newY = pageHeight/2. - imgHeight/2.; - chartImage.setLeft(newX).setTop(newY); -} - -function businessSlide_(slide, presentation, sheet, chartSheet, numRows) { - var chart = sheet.newChart() - .setChartType(Charts.ChartType.COLUMN) - .addRange(sheet.getRange('H1:H' + numRows)) - .addRange(sheet.getRange('C1:C' + numRows)) - .setOption('applyAggregateData', 0) - .setPosition(5, 5, 0, 0) - .build(); - - chartSheet.insertChart(chart); - var chartImage = slide.insertSheetsChartAsImage(chart, 200, 200, 400, 400); - var imgWidth = chartImage.getWidth(); - var imgHeight = chartImage.getHeight(); - var pageWidth = presentation.getPageWidth(); - var pageHeight = presentation.getPageHeight(); - var newX = pageWidth/2. - imgWidth/2.; - var newY = pageHeight/2. - imgHeight/2.; - chartImage.setLeft(newX).setTop(newY); -} - -function topSlide_(slide, sheet) { - var range = sheet.getDataRange().getValues(); - var won = []; - for (var i = 1; i < range.length; i++) { - var row = range[i]; - if (row[5] == 'Closed Won') { - won.push(row); - } - } - won.sort(function(x, y) { - var xp = x[2]; - var yp = y[2]; - return xp == yp ? 0 : xp < yp ? 1 : -1; - }); - var top = [won[0], won[1], won[2]]; - for (var i = 0; i < 3; i++) { - slide.replaceAllText('{{name'+ i +'}}', won[i][0]); - slide.replaceAllText('{{date'+ i +'}}', won[i][4]); - slide.replaceAllText('{{owner'+ i +'}}', won[i][9]); - } -} - -function nearSlide_(slide, sheet) { - var range = sheet.getDataRange().getValues(); - var near = []; - for (var i = 1; i < range.length; i++) { - var row = range[i]; - if ((row[5] == 'Qualification' || row[5] == 'Needs Analysis' - || row[5] == 'Negotiation') && row[6] > .5) { - near.push(row); - } - } - near.sort(function(x, y) { - var xp = x[6]; - var yp = y[6]; - return xp == yp ? 0 : xp < yp ? 1 : -1; - }); - var top = [near[0], near[1], near[2]]; - for (var i = 0; i < 3; i++) { - slide.replaceAllText('{{name'+ i +'}}', near[i][0]); - slide.replaceAllText('{{prob'+ i +'}}', near[i][6]); - slide.replaceAllText('{{owner'+ i +'}}', near[i][9]); - } -} - -function needsSlide_(slide, sheet) { - var range = sheet.getDataRange().getValues(); - var needs = []; - for (var i = 1; i < range.length; i++) { - var row = range[i]; - if ((row[5] == 'Qualification' || row[5] == 'Needs Analysis' - || row[5] == 'Negotiation') && row[6] <.5) { - needs.push(row); - } - } - needs.sort(function(x, y) { - var xp = x[6]; - var yp = y[6]; - return xp == yp ? 0 : xp < yp ? -1 : 1; - }); - var top = [needs[0], needs[1], needs[2]]; - for (var i = 0; i < 3; i++) { - slide.replaceAllText('{{name'+ i +'}}', needs[i][0]); - slide.replaceAllText('{{prob'+ i +'}}', needs[i][6]); - slide.replaceAllText('{{owner'+ i +'}}', needs[i][9]); - } -} - -function leadsSlide_(slide, sheet, presentation, numRows) { - var names = sheet.getRange('J2:J' + numRows).getValues(); - names = removeDupes_(names); - // Format the string of Owners - var leads = names[0]; - for (var i = 1; i < names.length; i++) { - leads = names[i] + ', ' + leads; - } - slide.replaceAllText('{{LEADS}}', leads); - - var imageUrl = DEFAULT_REGIONAL_IMAGE; - if (REGION == 'Midwest') { - imageUrl = MIDWEST_IMAGE; - } else if (REGION == 'Northeast') { - imageUrl = NORTHEAST_IMAGE; - } else if (REGION == 'Southwest') { - imageUrl = SOUTHWEST_IMAGE; - } else if (REGION == 'West') { - imageUrl = WEST_IMAGE; - } else if (REGION == 'Southeast') { - imageUrl = SOUTHEAST_IMAGE; - } - - try { - var image = slide.insertImage(imageUrl, 200, 200, 275, 275); - } catch (e) { - var image = slide.insertImage(DEFAULT_REGIONAL_IMAGE, 200, 200, 275, 275); - } - - var imgWidth = image.getWidth(); - var imgHeight = image.getHeight(); - var pageWidth = presentation.getPageWidth(); - var pageHeight = presentation.getPageHeight(); - var newX = pageWidth/2.; - var newY = pageHeight/2. - imgHeight/2.; - image.setLeft(newX).setTop(newY); -} - -function removeDupes_(names) { - names = names.sort(); - var result = [names[0].toString()]; - names.forEach(function(name) { - if (result[result.length-1].toString() != name.toString()) { - result.push(name); - } - }); - return result; -} diff --git a/generate-sales-report/src/Constants.js b/generate-sales-report/src/Constants.js deleted file mode 100644 index 011b936..0000000 --- a/generate-sales-report/src/Constants.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var REPORT_SLIDES_TEMPLATE_ID = 'YOUR_SLIDES_ID'; - -var DEFAULT_LOGO_IMAGE = 'https://www.maxpixel.net/static/photo/2x/Page-Not-Found-404-Link-Rot-Broken-Link-2367103.png'; -var ACME_IMAGE = 'https://upload.wikimedia.org/wikipedia/commons/6/6e/Acme-corp.png'; -var UNIKET_IMAGE = 'https://www.goodfreephotos.com/albums/vector-images/rocket-unicorn-vector-clipart.png'; -var GLOBAL_MEDIA_IMAGE = 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Globe_icon_squared.svg/500px-Globe_icon_squared.svg.png'; - -var DEFAULT_REGIONAL_IMAGE = 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Usa_edcp_location_map.svg/500px-Usa_edcp_location_map.svg.png'; -var MIDWEST_IMAGE = 'https://wazeopedia.waze.com/wiki/USA/images/thumb/c/c2/USA_Plains.png/600px-USA_Plains.png'; -var WEST_IMAGE = 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/US_West_Coast.svg/500px-US_West_Coast.svg.png'; -var SOUTHWEST_IMAGE = 'https://wazeopedia.waze.com/wiki/USA/images/thumb/7/72/USA_Southwest.png/600px-USA_Southwest.png'; -var SOUTHEAST_IMAGE = 'https://wazeopedia.waze.com/wiki/USA/images/thumb/7/71/USA_Southeast.png/600px-USA_Southeast.png'; -var NORTHEAST_IMAGE = 'https://wazeopedia.waze.com/wiki/USA/images/thumb/4/4f/USA_Northeast.png/600px-USA_Northeast.png'; diff --git a/generate-sales-report/src/appsscript.json b/generate-sales-report/src/appsscript.json deleted file mode 100644 index 604df14..0000000 --- a/generate-sales-report/src/appsscript.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "timeZone": "America/New_York", - "dependencies": { - }, - "exceptionLogging": "STACKDRIVER" -}