diff --git a/templates/custom-functions/Code.gs b/templates/custom-functions/Code.gs index 97b1325ff..48c1b4d4b 100644 --- a/templates/custom-functions/Code.gs +++ b/templates/custom-functions/Code.gs @@ -22,8 +22,8 @@ * A function that takes a single input value and returns a single value. * Returns a simple concatenation of Strings. * - * @param {String} name A name to greet. - * @return {String} A greeting. + * @param {string} name A name to greet. + * @return {string} A greeting. * @customfunction */ function SAY_HELLO(name) { @@ -35,8 +35,8 @@ function SAY_HELLO(name) { * range of cells. * Returns a range with all the input values incremented by one. * - * @param {Array} input The range of numbers to increment. - * @return {Array} The incremented values. + * @param {any} input The range of numbers to increment. + * @return {any} The incremented values. * @customfunction */ function INCREMENT(input) { @@ -55,8 +55,8 @@ function INCREMENT(input) { * Returns the sum the corner values in the range; for a single cell, * this is equal to (4 * the cell value). * - * @param {Array} input The Range of numbers to sum the corners of. - * @return {Number} The calculated sum. + * @param {number | number[][]} input The Range of numbers to sum the corners of. + * @return {number} The calculated sum. * @customfunction */ function CORNER_SUM(input) { @@ -65,8 +65,8 @@ function CORNER_SUM(input) { return CORNER_SUM([[input]]); // eslint-disable-line new-cap } // Range processing here. - var maxRowIndex = input.length - 1; - var maxColIndex = input[0].length - 1; + const maxRowIndex = input.length - 1; + const maxColIndex = input[0].length - 1; return input[0][0] + input[0][maxColIndex] + input[maxRowIndex][0] + input[maxRowIndex][maxColIndex]; } @@ -76,20 +76,21 @@ function CORNER_SUM(input) { * Returns a range consisting of the first 10 powers and roots of that * number (with column headers). * - * @param {Number} input The number to calculate from. - * @return {Array} The first ten powers and roots of that number, - * with associated labels. + * @param {number} input The number to calculate from. + * @return {Array>} The first ten powers and roots of that + * number, with associated labels. * @customfunction */ function POWERS_AND_ROOTS(input) { - if (input instanceof Array) { - throw new Error('Invalid: Range input not permitted'); + if (typeof input !== 'number') { + throw new Error('Invalid: A single number is required.'); } // Value processing and range generation here. - var headers = ['x', input + '^x', input + '^(1/x)']; - var result = [headers]; - for (var i = 1; i <= 10; i++) { - result.push([i, Math.pow(input, i), Math.pow(input, 1/i)]); + const headers = ['x', input.toString() + '^x', input.toString() + '^(1/x)']; + /** @type {Array>} */ + const result = [headers]; + for (let i = 1; i <= 10; i++) { + result.push([i, Math.pow(input, i), Math.pow(input, 1 / i)]); } return result; } @@ -99,7 +100,7 @@ function POWERS_AND_ROOTS(input) { * Returns the day of the year represented by the provided date. * * @param {Date} date A Date to examine. - * @return {Number} The day of year for that date. + * @return {number} The day of year for that date. * @customfunction */ function GET_DAY_OF_YEAR(date) { @@ -107,9 +108,9 @@ function GET_DAY_OF_YEAR(date) { throw new Error('Invalid: Date input required'); } // Date processing here. - var firstOfYear = new Date(date.getFullYear(), 0, 0); - var diff = date - firstOfYear; - var oneDay = 1000 * 60 * 60 * 24; + const firstOfYear = new Date(date.getFullYear(), 0, 0); + const diff = date.getTime() - firstOfYear.getTime(); + const oneDay = 1000 * 60 * 60 * 24; return Math.floor(diff / oneDay); } @@ -118,7 +119,7 @@ function GET_DAY_OF_YEAR(date) { * Returns the number of seconds measured by that duration. * * @param {Date} duration A duration to convert. - * @return {Number} Number of seconds in that duration. + * @return {number} Number of seconds in that duration. * @customfunction */ function CONVERT_DURATION_TO_SECONDS(duration) { diff --git a/templates/docs-addon/Code.gs b/templates/docs-addon/Code.gs index 1c4ad0c29..48a14e3b7 100644 --- a/templates/docs-addon/Code.gs +++ b/templates/docs-addon/Code.gs @@ -24,7 +24,7 @@ var SIDEBAR_TITLE = 'Example Sidebar'; /** * Adds a custom menu with items to show the sidebar and dialog. * - * @param {Object} e The event parameter for a simple onOpen trigger. + * @param {any} e The event parameter for a simple onOpen trigger. */ function onOpen(e) { DocumentApp.getUi() @@ -38,7 +38,7 @@ function onOpen(e) { * Runs when the add-on is installed; calls onOpen() to ensure menu creation and * any other initializion work is done immediately. * - * @param {Object} e The event parameter for a simple onInstall trigger. + * @param {any} e The event parameter for a simple onInstall trigger. */ function onInstall(e) { onOpen(e); @@ -70,7 +70,7 @@ function showDialog() { /** * Returns the existing footer text (if any). * - * @return {String} existing document footer text (as a plain string). + * @return {string} existing document footer text (as a plain string). */ function getFooterText() { // Retrieve and return the information requested by the sidebar. @@ -80,7 +80,7 @@ function getFooterText() { /** * Replaces the current document footer with the given text. * - * @param {String} footerText text collected from the client-side + * @param {string} footerText text collected from the client-side * sidebar. */ function setFooterText(footerText) { @@ -91,7 +91,7 @@ function setFooterText(footerText) { /** * Returns the document title. * - * @return {String} the current document title. + * @return {string} the current document title. */ function getDocTitle() { // Retrieve and return the information requested by the dialog. @@ -101,7 +101,7 @@ function getDocTitle() { /** * Changes the document title. * - * @param {String} title the new title to use for the document. + * @param {string} title the new title to use for the document. */ function setDocTitle(title) { // Use data collected from dialog to manipulate the document. diff --git a/templates/forms-addon/Code.gs b/templates/forms-addon/Code.gs index 295256f4b..08b5ec0bd 100644 --- a/templates/forms-addon/Code.gs +++ b/templates/forms-addon/Code.gs @@ -24,7 +24,7 @@ var SIDEBAR_TITLE = 'Example Sidebar'; /** * Adds a custom menu with items to show the sidebar and dialog. * - * @param {Object} e The event parameter for a simple onOpen trigger. + * @param {any} e The event parameter for a simple onOpen trigger. */ function onOpen(e) { FormApp.getUi() @@ -38,7 +38,7 @@ function onOpen(e) { * Runs when the add-on is installed; calls onOpen() to ensure menu creation and * any other initializion work is done immediately. * - * @param {Object} e The event parameter for a simple onInstall trigger. + * @param {any} e The event parameter for a simple onInstall trigger. */ function onInstall(e) { onOpen(e); @@ -67,10 +67,17 @@ function showDialog() { FormApp.getUi().showModalDialog(ui, DIALOG_TITLE); } +/** + * @typedef {{ + * type: string, + * name: string, + * }} ItemData + */ + /** * Appends a new form item to the current form. * - * @param {Object} itemData a collection of String data used to + * @param {ItemData} itemData a collection of String data used to * determine the exact form item created. */ function addFormItem(itemData) { @@ -93,7 +100,7 @@ function addFormItem(itemData) { * Queries the form DocumentProperties to determine whether the formResponse * trigger is enabled or not. * - * @return {Boolean} True if the form submit trigger is enabled; false + * @return {boolean} True if the form submit trigger is enabled; false * otherwise. */ function getTriggerState() { @@ -105,7 +112,7 @@ function getTriggerState() { /** * Turns the form submit trigger on or off based on the given argument. * - * @param {Boolean} enableTrigger whether to turn on the form submit + * @param {boolean} enableTrigger whether to turn on the form submit * trigger or not */ function adjustFormSubmitTrigger(enableTrigger) { @@ -136,12 +143,18 @@ function adjustFormSubmitTrigger(enableTrigger) { } } +/** + * @typedef {{ + * response: GoogleAppsScript.Forms.FormResponse + * }} FormSubmitEvent + */ + /** * Responds to form submit events if a form summit trigger is enabled. * Collects some form information and sends it as an email to the form creator. * - * @param {Object} e The event parameter created by a form - * submission; see + * @param {FormSubmitEvent} e The event parameter + * created by a form submission. * https://developers.google.com/apps-script/understanding_events */ function respondToFormSubmit(e) { diff --git a/templates/sheets-addon/Code.gs b/templates/sheets-addon/Code.gs index 2c862bf51..9a956b453 100644 --- a/templates/sheets-addon/Code.gs +++ b/templates/sheets-addon/Code.gs @@ -24,7 +24,7 @@ var SIDEBAR_TITLE = 'Example Sidebar'; /** * Adds a custom menu with items to show the sidebar and dialog. * - * @param {Object} e The event parameter for a simple onOpen trigger. + * @param {any} e The event parameter for a simple onOpen trigger. */ function onOpen(e) { SpreadsheetApp.getUi() @@ -38,7 +38,7 @@ function onOpen(e) { * Runs when the add-on is installed; calls onOpen() to ensure menu creation and * any other initializion work is done immediately. * - * @param {Object} e The event parameter for a simple onInstall trigger. + * @param {any} e The event parameter for a simple onInstall trigger. */ function onInstall(e) { onOpen(e); @@ -70,7 +70,7 @@ function showDialog() { /** * Returns the value in the active cell. * - * @return {String} The value of the active cell. + * @return {string} The value of the active cell. */ function getActiveValue() { // Retrieve and return the information requested by the sidebar. @@ -81,7 +81,7 @@ function getActiveValue() { /** * Replaces the active cell value with the given value. * - * @param {Number} value A reference number to replace with. + * @param {number} value A reference number to replace with. */ function setActiveValue(value) { // Use data collected from sidebar to manipulate the sheet. @@ -93,7 +93,7 @@ function setActiveValue(value) { * Executes the specified action (create a new sheet, copy the active sheet, or * clear the current sheet). * - * @param {String} action An identifier for the action to take. + * @param {string} action An identifier for the action to take. */ function modifySheets(action) { // Use data collected from dialog to manipulate the spreadsheet. diff --git a/templates/sheets-import/APICode.gs b/templates/sheets-import/APICode.gs index 5bbdb7d9a..0f15bff7b 100644 --- a/templates/sheets-import/APICode.gs +++ b/templates/sheets-import/APICode.gs @@ -1,3 +1,4 @@ +// @ts-nocheck /** * Copyright Google LLC * @@ -14,10 +15,17 @@ * limitations under the License. */ +/** + * @typedef {{ + * id: string, + * label: string, + * }} ColumnOption + */ + /** * Return an array of potential columns (identifiers to locate them in * the data response object and the labels to use as column headers). - * @return {Array} list of potential columns. + * @return {ColumnOption[]} list of potential columns. */ function getColumnOptions() { var columns = []; @@ -37,15 +45,15 @@ function getColumnOptions() { * Return a page of results from the data source as a 2D array of * values (with columns corresponding to the columns specified). Return * null if no data exists for the specified pageNumber. - * @param {Array} columns an array of Strings specifying the column ids + * @param {string[]} columns an array of Strings specifying the column ids * to include in the output. - * @param {Number} pageNumber a number indicating what page of data to + * @param {number} pageNumber a number indicating what page of data to * retrieve from the data source. - * @param {Number} pageSize a number indicating the maximum number of + * @param {number} pageSize a number indicating the maximum number of * rows to return per call. - * @param {Object} opt_settings optional object containing any additional - * information needed to retrieve data from the data source. - * @return {object[]|null} Pages of data. + * @param {Object} opt_settings optional object containing any + * additional information needed to retrieve data from the data source. + * @return {Object|null} Pages of data. */ function getDataPage(columns, pageNumber, pageSize, opt_settings) { var data = null; diff --git a/templates/sheets-import/Auth.gs b/templates/sheets-import/Auth.gs index f303e4bb5..ca3c1c3af 100644 --- a/templates/sheets-import/Auth.gs +++ b/templates/sheets-import/Auth.gs @@ -1,3 +1,4 @@ +// @ts-nocheck /** * Copyright 2015 Google Inc. All Rights Reserved. * @@ -14,15 +15,37 @@ * limitations under the License. */ +// @ts-nocheck +var OAuth2 = undefined; + +/** + * @typedef {{ + * setAuthorizationBaseUrl: (url: string) => Service, + * setTokenUrl: (url: string) => Service, + * setClientId: (id: string) => Service, + * setClientSecret: (secret: string) => Service, + * setCallbackFunction: (handler: string) => Service, + * setPropertyStore: (store: GoogleAppsScript.Properties.Properties) => Service, + * getAuthorizationUrl: () => string, + * reset: () => void, + * handleCallback: (request: Object) => boolean, + * hasAccess: () => boolean, + * }} Service + */ + /** * Return an OAuth service object to handle authorization for a specific * data source (such as an API resource). Makes use of the OAuth2 Apps * Script library: * https://github.com/googlesamples/apps-script-oauth2 - * @return {Object} a service object associated with the specified - * resource. + * @return {Service} a service object associated with + * the specified resource. */ function getService() { + if (typeof OAuth2 === 'undefined') { + throw new Error('OAuth2 library not found. Have you added it?'); + } + /* TODO: Fill in the following required parameters for your data source. */ var service = OAuth2.createService('ENTER_SERVICE_NAME_HERE') .setAuthorizationBaseUrl('ENTER_BASE_URL_HERE') @@ -46,32 +69,23 @@ function getService() { * completion of the API auth sequence. For additional details, see the * OAuth2 Apps Script library: * https://github.com/googlesamples/apps-script-oauth2 - * @param {Object} request results of API auth request. - * @return {HTML} A auth callback HTML page. + * @param {Object} request results of API auth request. + * @return {GoogleAppsScript.HTML.HtmlOutput} A auth callback HTML page. */ function authCallback(request) { var template = HtmlService.createTemplateFromFile('AuthCallbackView'); template.user = Session.getEffectiveUser().getEmail(); - template.isAuthorized = false; - template.error = null; - var title; - try { - var service = getService(); - var authorized = service.handleCallback(request); - template.isAuthorized = authorized; - title = authorized ? 'Access Granted' : 'Access Denied'; - } catch (e) { - template.error = e; - title = 'Access Error'; - } + var service = getService(); + var authorized = service.handleCallback(request); + template.isAuthorized = authorized; + var title = authorized ? 'Access Granted' : 'Access Denied'; template.title = title; - return template.evaluate() - .setTitle(title); + return template.evaluate().setTitle(title); } /** * Builds and returns the API authorization URL from the service object. - * @return {String} the API authorization URL. + * @return {string} the API authorization URL. */ function getAuthorizationUrl() { return getService().getAuthorizationUrl(); diff --git a/templates/sheets-import/Configurations.gs b/templates/sheets-import/Configurations.gs index 3f40fb100..67e2b75e5 100644 --- a/templates/sheets-import/Configurations.gs +++ b/templates/sheets-import/Configurations.gs @@ -1,3 +1,4 @@ +// @ts-nocheck /** * Copyright 2015 Google Inc. All Rights Reserved. * @@ -14,6 +15,7 @@ * limitations under the License. */ +// @ts-nocheck var REPORT_SET_KEY = 'Import.ReportSet'; var SCHEDULE_TRIGGER_ID = 'Import.scheduled.triggerId'; @@ -25,11 +27,30 @@ var UPDATE_TYPE = { REMOVE: 2 }; +/** + * @typedef {{ + * reportId: string, + * sheetId: string, + * sheetName: string, + * name: string, + * lastRun: string, + * owner: string, + * scheduled: boolean, + * columns: any[], + * }} ReportConfig + */ + +/** + * @typedef {{ + * [reportId: string]: string + * }} Report + */ + /** * Return the report configuration for the report with the given * ID; returns an empty Object if no such report name exists. - * @param {String} reportId a report ID. - * @return {Object} a report configuration corresponding to that ID, + * @param {string} reportId a report ID. + * @return {ReportConfig|null} a report configuration corresponding to that ID, * or null if no such report exists. */ function getReportConfig(reportId) { @@ -41,22 +62,24 @@ function getReportConfig(reportId) { // get the current one. var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = getSheetById(ss, parseInt(config.sheetId)); - config.sheetName = !sheet ? null : sheet.getName(); - return config; + config.sheetName = !sheet ? '' : sheet.getName(); + return /** @type {ReportConfig} */ (config); } /** * Given a report configuration, save it. - * @param {object} config the report configuration. - * @param {object} the updated report configuration. - * @return {object} The saved configuration. + * @param {ReportConfig} config the report configuration. + * @return {ReportConfig} The saved configuration. */ function saveReportConfig(config) { + if (typeof _ === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); + } var previous = getReportConfig(config.reportId); if (config.reportId === 'new-report') { config.reportId = newReportId(); - config.lastRun = null; - config.owner = Session.getEffectiveUser().getEmail(); + config.lastRun = ''; + config.owner = Session.getEffectiveUser().getEmail() || ''; } saveObjectToProperties(config.reportId, config); updateReportSet(UPDATE_TYPE.ADD, config.reportId, config.name); @@ -68,7 +91,7 @@ function saveReportConfig(config) { /** * Delete the report specified by the given ID. - * @param {String} reportId indicates the report to delete. + * @param {string} reportId indicates the report to delete. */ function deleteReportConfig(reportId) { deleteObjectFromProperties(reportId); @@ -78,7 +101,7 @@ function deleteReportConfig(reportId) { /** * Returns true if the current user is allowed to edit the * report associated with the given config. - * @param {Object} config a report configuration. + * @param {ReportConfig} config a report configuration. * @return {boolean} True if the user can edit the report. */ function canEditReport(config) { @@ -92,7 +115,7 @@ function canEditReport(config) { /** * Given a new report configuration, return true if it saving this report would mean the limit on * scheduled reports would be exceeded. - * @param {Object} config a report configuration to be saved. + * @param {ReportConfig} config a report configuration to be saved. * @return {boolean} If it saving this report would mean the limit on scheduled reports * would be exceeded. */ @@ -108,46 +131,59 @@ function isOverScheduleLimit(config) { /** * Return a set of all saved reports (reportIds as keys, report * names as values). - * @return {Object} + * @return {Report} */ function getAllReports() { var properties = PropertiesService.getDocumentProperties(); - return JSON.parse(properties.getProperty(REPORT_SET_KEY)); + var reports = properties.getProperty(REPORT_SET_KEY); + if (reports) { + return JSON.parse(reports); + } + return {}; } /** * Get a set of report configurations that all have been marked * for scheduled imports. - * @param {String} opt_user optional user email; if provided, returned + * @param {string} opt_user optional user email; if provided, returned * results will only include reports that user is the owner of. - * @return {Object} collection of configuration object for scheduled + * @return {ReportConfig[]} collection of configuration object for scheduled * reports. */ function getScheduledReports(opt_user) { - var scheduledReports = []; - _.keys(getAllReports()).forEach(function(reportId) { - var config = getReportConfig(reportId); - if (config && config.scheduled && - (!opt_user || opt_user == config.owner)) { - scheduledReports.push(config); - } - }); + const scheduledReports = []; + if (typeof _ === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); + } + var allReports = getAllReports(); + if (allReports) { + Object.keys(allReports).forEach(function(reportId) { + var config = getReportConfig(reportId); + if ( + config && + config.scheduled && + (!opt_user || opt_user == config.owner) + ) { + scheduledReports.push(config); + } + }); + } return scheduledReports; } /** * Updates the current report list (adding or removing a given * report name and id). - * @param {Number} updateType Enum: either UPDATE_TYPE.ADD or + * @param {number} updateType Enum: either UPDATE_TYPE.ADD or * UPDATE_TYPE.REMOVE. - * @param {String} reportId report to add or remove. - * @param {String} reportName report name (only needed for ADD). + * @param {string} reportId report to add or remove. + * @param {string=} reportName report name (only needed for ADD). */ function updateReportSet(updateType, reportId, reportName) { var properties = PropertiesService.getDocumentProperties(); var lock = LockService.getDocumentLock(); lock.waitLock(2000); - var reportSet = JSON.parse(properties.getProperty(REPORT_SET_KEY)); + var reportSet = getAllReports(); if (reportSet == null) { reportSet = {}; } @@ -164,29 +200,37 @@ function updateReportSet(updateType, reportId, reportName) { * Update a report configuration with a sheetId and last runtime * information, save and return it. Include but do not save the * sheet name. - * @param {Object} config the report configuration. - * @param {Sheet} sheet the report's sheet. - * @param {String} lastRun the datetime string indicating the last + * @param {ReportConfig} config the report configuration. + * @param {GoogleAppsScript.Spreadsheet.Sheet} sheet the report's sheet. + * @param {string} lastRun the datetime string indicating the last * time the report was run. - * @return {Object} the updated report configuration. + * @return {ReportConfig} the updated report configuration. */ function updateOnImport(config, sheet, lastRun) { + if (typeof _ === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); + } var update = { sheetId: sheet.getSheetId().toString(), - lastRun: lastRun + lastRun: lastRun, }; saveObjectToProperties(config.reportId, update); - update.sheetName = sheet.getName(); - return _.extend(config, update); + config.sheetId = update.sheetId; + config.lastRun = update.lastRun; + config.sheetName = sheet.getName(); + return config; } /** * Return the array of column IDs used by the given report * configuration. - * @param {Object} config the report configuration. - * @return {Array} column ID strings. + * @param {ReportConfig} config the report configuration. + * @return {string[]} column ID strings. */ function getColumnIds(config) { + if (typeof _ === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); + } return _.map(config.columns, function(col) { return col.column; }); @@ -203,7 +247,7 @@ function getTriggerId() { /** * Save the trigger ID of the scheduling trigger for this user. - * @param {Trigger} trigger the trigger whose ID should be saved. + * @param {object} trigger the trigger whose ID should be saved. */ function saveTriggerId(trigger) { var properties = PropertiesService.getUserProperties(); diff --git a/templates/sheets-import/Server.gs b/templates/sheets-import/Server.gs index aeca91075..d160503e7 100644 --- a/templates/sheets-import/Server.gs +++ b/templates/sheets-import/Server.gs @@ -1,3 +1,4 @@ +// @ts-nocheck /** * Copyright 2015 Google Inc. All Rights Reserved. * @@ -18,7 +19,12 @@ * @OnlyCurrentDoc Limits the script to only accessing the current spreadsheet. */ -var _ = Underscore.load(); +if (typeof Underscore === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); +} +// @ts-nocheck +var Underscore = undefined; +var _ = undefined; /** * TODO: Replace the following with the name of the service you are importing @@ -37,12 +43,12 @@ var ERROR_CODES = { AUTO_UPDATE_LIMIT: 1, ILLEGAL_EDIT: 2, ILLEGAL_DELETE: 3, - IMPORT_FAILED: 4 + IMPORT_FAILED: 4, }; /** * Adds a custom menu with items to show the sidebar. - * @param {Object} e The event parameter for a simple onOpen trigger. + * @param {any} e The event parameter for a simple onOpen trigger. */ function onOpen(e) { SpreadsheetApp.getUi() @@ -54,7 +60,7 @@ function onOpen(e) { /** * Runs when the add-on is installed; calls onOpen() to ensure menu creation and * any other initializion work is done immediately. - * @param {Object} e The event parameter for a simple onInstall trigger. + * @param {any} e The event parameter for a simple onInstall trigger. */ function onInstall(e) { onOpen(e); @@ -74,30 +80,38 @@ function showSidebar() { if (!template.isAuthorized) { template.authorizationUrl = service.getAuthorizationUrl(); } - var page = template.evaluate() - .setTitle(SIDEBAR_TITLE); + var page = template.evaluate().setTitle(SIDEBAR_TITLE); SpreadsheetApp.getUi().showSidebar(page); } +/** + * @typedef {{ + * name: string, + * reportId: string + * }} ReportListItem + */ + /** * Return data needed to build the sidebar UI: a list of the names of the * currently saved report configurations and the list of potential * column choices. - * @return {Object} a collection of saved report data and column options. + * @return {{reports: ReportListItem[], columns: ColumnOption[]}} a collection + * of saved report data and column options. */ function getInitialDataForSidebar() { var reportSet = getAllReports(); + /** @type {ReportListItem[]} */ var reportList = []; - _.each(reportSet, function(val, key) { - reportList.push({'name': val, 'reportId': key}); + Object.keys(reportSet).forEach(function(key) { + reportList.push({name: reportSet[key], reportId: key}); }); reportList.sort(function(a, b) { if (a.name > b.name) { - return 1; -} + return 1; + } if (a.name < b.name) { - return -1; -} + return -1; + } return 0; }); return {reports: reportList, columns: getColumnOptions()}; @@ -106,12 +120,14 @@ function getInitialDataForSidebar() { /** * Get the report configuration for the given report and, if a sheet * exists for it, activate that sheet. - * @param {String} reportId a report ID. - * @return {object} The report config. + * @param {string} reportId a report ID. + * @return {ReportConfig|null} The report config. */ function switchToReport(reportId) { var config = getReportConfig(reportId); - activateById(config.sheetId); + if (config) { + activateById(parseInt(config.sheetId)); + } return config; } @@ -119,12 +135,14 @@ function switchToReport(reportId) { * Import data to the spreadsheet according to the given report * configuration. * @param {string} reportId the report identifier. - * @return {object} the (possibly updated) report configuration. + * @return {ReportConfig} the (possibly updated) report configuration. */ function runImport(reportId) { var ss = SpreadsheetApp.getActiveSpreadsheet(); var config = getReportConfig(reportId); - + if (!config) { + return; + } // Acquire the sheet to place the import results in, // then clear and format it. // Update the saved config with sheet/time information. @@ -137,21 +155,19 @@ function runImport(reportId) { // page at a time. var pageNumber = 0; var firstRow = 2; - try { - var page; - do { - page = getDataPage(columnIds, pageNumber, IMPORT_PAGE_SIZE, config); - if (page) { - sheet.getRange(firstRow, 1, page.length, page[0].length).setValues(page); - firstRow += page.length; - pageNumber++; - SpreadsheetApp.flush(); - } - } while (page != null); - } catch (e) { - // Ensure a new sheet Id, if created, is preserved. - throw ERROR_CODES.IMPORT_FAILED; - } + + var page; + do { + page = getDataPage(columnIds, pageNumber, IMPORT_PAGE_SIZE, config); + if (page && page.length > 0) { + sheet + .getRange(firstRow, 1, page.length, page[0].length) + .setValues(page); + firstRow += page.length; + pageNumber++; + SpreadsheetApp.flush(); + } + } while (page != null); for (var i = 1; i <= sheet.getLastColumn(); i++) { sheet.autoResizeColumn(i); @@ -162,13 +178,13 @@ function runImport(reportId) { /** * Save the given report configuration. - * @param {Object} config a report configuration to save. - * @return {Object} the updated report configuration. + * @param {ReportConfig} config a report configuration to save. + * @return {ReportConfig} the updated report configuration. */ function saveReport(config) { var existingConfig = getReportConfig(config.reportId); if (existingConfig != null) { - activateById(existingConfig.sheetId); + activateById(parseInt(existingConfig.sheetId)); // Check: users are not allowed to save edits to reports // created by other users if those reports have been marked // for auto-update. @@ -188,13 +204,17 @@ function saveReport(config) { /** * Delete the given report configuration. - * @param {String} reportId indicates the report to delete. - * @return {String} the report ID deleted. + * @param {string} reportId indicates the report to delete. + * @return {string} the report ID deleted. */ function removeReport(reportId) { + var config = getReportConfig(reportId); + if (!config) { + return; + } // Check: users are not allowed to delete reports created by // other users if those reports have been marked for auto-update. - if (!canEditReport(getReportConfig(reportId))) { + if (!canEditReport(config)) { throw ERROR_CODES.ILLEGAL_DELETE; } deleteReportConfig(reportId); @@ -206,8 +226,8 @@ function removeReport(reportId) { * Activate, clear, format and return the sheet associated with the * specified report configuration. If the sheet does not exist, create, * format and activate it. - * @param {Object} config the report configuration. - * @return {Sheet} + * @param {ReportConfig} config the report configuration. + * @return {GoogleAppsScript.Spreadsheet.Sheet} */ function activateReportSheet(config) { var ss = SpreadsheetApp.getActiveSpreadsheet(); @@ -224,10 +244,11 @@ function activateReportSheet(config) { sheet.clear(); sheet.clearNotes(); sheet.setFrozenRows(1); - sheet.getRange('1:1') - .setFontWeight('bold') - .setBackground('#000000') - .setFontColor('#ffffff'); + sheet + .getRange('1:1') + .setFontWeight('bold') + .setBackground('#000000') + .setFontColor('#ffffff'); sheet.getRange(1, 1, 1, headers.length).setValues([headers]); return sheet; } diff --git a/templates/sheets-import/Utilities.gs b/templates/sheets-import/Utilities.gs index 080beb63f..caefefe82 100644 --- a/templates/sheets-import/Utilities.gs +++ b/templates/sheets-import/Utilities.gs @@ -1,3 +1,4 @@ +// @ts-nocheck /** * Copyright 2015 Google Inc. All Rights Reserved. * @@ -20,6 +21,7 @@ * @param {string} filename Project file name. * @return {string} The content of the rendered HTML. */ +// @ts-nocheck function include(filename) { return HtmlService.createHtmlOutputFromFile(filename).getContent(); } @@ -27,28 +29,31 @@ function include(filename) { /** * Returns true if the given date string represents a date that is * more than 24 hours in the past; returns false otherwise. - * @param {String} dateStr a date string. - * @return {Boolean} + * @param {string} dateStr a date string. + * @return {boolean} */ function isOlderThanADay(dateStr) { - var now = (new Date()).getTime(); + var now = new Date().getTime(); var then = Date.parse(dateStr); - return (then + 24 * 60 * 60 * 1000) < now; + return then + 24 * 60 * 60 * 1000 < now; } /** * Given an object and a string prefix, save every value in that object * to the Document properties service as a JSONified string. The property * key for each object key will be: prefix. - * @param {String} prefix a common string to label each added property. - * @param {Object} obj a collection of key-values to save as + * @param {string} prefix a common string to label each added property. + * @param {Object} obj a collection of key-values to save as * user properties. */ function saveObjectToProperties(prefix, obj) { + if (typeof _ === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); + } var properties = PropertiesService.getDocumentProperties(); - _.each(obj, function(val, key) { - var propKey = prefix + '.' + key; - properties.setProperty(propKey, JSON.stringify(val)); + Object.keys(obj).forEach(function(key) { + var propKey = prefix + '.' + key; + properties.setProperty(propKey, JSON.stringify(obj[key])); }); } @@ -57,19 +62,23 @@ function saveObjectToProperties(prefix, obj) { * properties whose keys start with that prefix, and return the (JSON-parsed) * values in an object. The keys of the returned object will be the * same as the property keys with the leading "prefix." removed. - * @param {String} prefix label of requested properties. - * @return {Object} collection of key-value pairs taken from the + * @param {string} prefix label of requested properties. + * @return {Object|null} collection of key-value pairs taken from the * properties service. Will return null if the prefix is unrecognized. */ function getObjectFromProperties(prefix) { + if (typeof _ === 'undefined') { + throw new Error('Underscore library not found. Have you added it?'); + } var properties = PropertiesService.getDocumentProperties(); var obj = {}; - _.each(properties.getProperties(), function(val, key) { + var allProps = properties.getProperties(); + Object.keys(allProps).forEach(function(key) { if (key.indexOf(prefix) > -1) { - obj[key.substr(prefix.length + 1)] = JSON.parse(val); + obj[key.substr(prefix.length + 1)] = JSON.parse(allProps[key] || '{}'); } }); - if (_.keys(obj).length == 0) { + if (Object.keys(obj).length == 0) { return null; } return obj; @@ -78,11 +87,11 @@ function getObjectFromProperties(prefix) { /** * Given a string prefix, remove from the Document properties service all * properties whose keys start with that prefix. - * @param {String} prefix label of properties to remove. + * @param {string} prefix label of properties to remove. */ function deleteObjectFromProperties(prefix) { var properties = PropertiesService.getDocumentProperties(); - _.each(properties.getProperties(), function(val, key) { + Object.keys(properties.getProperties()).forEach(function(key) { if (key.indexOf(prefix) > -1) { properties.deleteProperty(key); } @@ -91,7 +100,7 @@ function deleteObjectFromProperties(prefix) { /** * Generate a random alphanumeric string. - * @return {String} report ID string. + * @return {string} report ID string. */ function newReportId() { return Math.random().toString(36).substring(2); @@ -100,9 +109,9 @@ function newReportId() { /** * Sheets-specific utility. Find a sheet within a spreadsheet with * the given id. If not present, return null. - * @param {Object} ss a Spreadsheet object. - * @param {Number} sheetId a Sheet id. - * @return {Object} a Sheet object, or null if not found. + * @param {object} ss a Spreadsheet object. + * @param {number} sheetId a Sheet id. + * @return {object|null} a Sheet object, or null if not found. */ function getSheetById(ss, sheetId) { if (sheetId === null) { @@ -123,16 +132,16 @@ function getSheetById(ss, sheetId) { * suffix to append to it to make it unique and return. This function * is used to avoid name collisions while adding or renaming sheets * automatically. - * @param {Object} spreadsheet a Spreadsheet. - * @param {String} baseName the initial suggested title for a sheet. - * @return {String} a unique title for the sheet, based on the + * @param {object} spreadsheet a Spreadsheet. + * @param {string} baseName the initial suggested title for a sheet. + * @return {string} a unique title for the sheet, based on the * given base title. */ function getUniqueSheetName(spreadsheet, baseName) { var sheetName = baseName; var i = 2; while (spreadsheet.getSheetByName(sheetName) != null) { - sheetName = baseName + ' ' + i++; + sheetName = baseName + ' ' + i++; } return sheetName; } @@ -141,9 +150,9 @@ function getUniqueSheetName(spreadsheet, baseName) { * Sheets-specific utility. Given a spreadsheet and a triggerId string, * return the user trigger that corresponds to that ID. Returns null * if no such trigger exists. - * @param {Spreadsheet} spreadsheet container of the user triggers. - * @param {String} triggerId trigger ID string. - * @return {Trigger} corresponding user trigger, or null if not found. + * @param {object} spreadsheet container of the user triggers. + * @param {string} triggerId trigger ID string. + * @return {object|null} corresponding user trigger, or null if not found. */ function getUserTriggerById(spreadsheet, triggerId) { var triggers = ScriptApp.getUserTriggers(spreadsheet); @@ -158,11 +167,11 @@ function getUserTriggerById(spreadsheet, triggerId) { /** * Sheets-specific utility. Given a String sheet id, activate that * sheet if it exists. - * @param {String} sheetId the sheet ID. + * @param {number} sheetId the sheet ID. */ function activateById(sheetId) { var ss = SpreadsheetApp.getActiveSpreadsheet(); - var sheet = getSheetById(ss, parseInt(sheetId)); + var sheet = getSheetById(ss, sheetId); if (sheet != null) { sheet.activate(); } diff --git a/templates/standalone/helloWorld.gs b/templates/standalone/helloWorld.gs index a9794babf..215d4c8ba 100644 --- a/templates/standalone/helloWorld.gs +++ b/templates/standalone/helloWorld.gs @@ -16,32 +16,30 @@ // [START apps_script_hello_world] /** * Creates a Google Doc and sends an email to the current user with a link to the doc. + * @return {void} */ function createAndSendDocument() { - try { - // Create a new Google Doc named 'Hello, world!' - const doc = DocumentApp.create('Hello, world!'); + // Create a new Google Doc named 'Hello, world!' + const doc = DocumentApp.create('Hello, world!'); - // Access the body of the document, then add a paragraph. - doc.getBody().appendParagraph('This document was created by Google Apps Script.'); + // Access the body of the document, then add a paragraph. + doc + .getBody() + .appendParagraph('This document was created by Google Apps Script.'); - // Get the URL of the document. - const url = doc.getUrl(); + // Get the URL of the document. + const url = doc.getUrl(); - // Get the email address of the active user - that's you. - const email = Session.getActiveUser().getEmail(); + // Get the email address of the active user - that's you. + const email = Session.getActiveUser().getEmail(); - // Get the name of the document to use as an email subject line. - const subject = doc.getName(); + // Get the name of the document to use as an email subject line. + const subject = doc.getName(); - // Append a new string to the "url" variable to use as an email body. - const body = 'Link to your doc: ' + url; + // Append a new string to the "url" variable to use as an email body. + const body = 'Link to your doc: ' + url; - // Send yourself an email with a link to the document. - GmailApp.sendEmail(email, subject, body); - } catch (err) { - // TODO (developer) - Handle exception - console.log('Failed with error %s', err.message); - } + // Send yourself an email with a link to the document. + GmailApp.sendEmail(email, subject, body); } // [END apps_script_hello_world] diff --git a/templates/web-app/Code.gs b/templates/web-app/Code.gs index 57bf21b16..866ba20ac 100644 --- a/templates/web-app/Code.gs +++ b/templates/web-app/Code.gs @@ -14,15 +14,23 @@ * limitations under the License. */ +/** + * @typedef {{ + * parameter: { + * folderId: string, + * } + * }} AppsScriptEvent + */ + /** * Serves HTML of the application for HTTP GET requests. * If folderId is provided as a URL parameter, the web app will list * the contents of that folder (if permissions allow). Otherwise * the web app will list the contents of the root folder. * - * @param {Object} e event parameter that can contain information + * @param {AppsScriptEvent} e event parameter that can contain information * about any URL parameters provided. - * @return {HTML} The web app's HTML. + * @return {object} The web app's HTML. */ function doGet(e) { var template = HtmlService.createTemplateFromFile('Index'); @@ -35,24 +43,32 @@ function doGet(e) { } // Build and return HTML in IFRAME sandbox mode. - return template.evaluate() - .setTitle('Web App Window Title'); + return template.evaluate().setTitle('Web App Window Title'); } +/** + * @typedef {{ + * children: string[], + * rootName: string, + * }} FolderContents + */ + /** * Return an array of up to 20 filenames contained in the * folder previously specified (or the root folder by default). * - * @param {String} folderId String ID of folder whose contents + * @param {string} folderId String ID of folder whose contents * are to be retrieved; if this is 'root', the * root folder is used. - * @return {Object} list of content filenames, along with + * @return {FolderContents} list of content filenames, along with * the root folder name. */ function getFolderContents(folderId) { var topFolder; + /** @type {FolderContents} */ var contents = { - children: [] + children: [], + rootName: '', }; if (folderId == 'root') {