var BAR_ID = 'PROGRESS_BAR_ID'; var BAR_HEIGHT = 10; // px var presentation = SlidesApp.getActivePresentation(); function createBars() { var slides = presentation.getSlides(); deleteBars(); for (var i = 0; i < slides.length; ++i) { var ratioComplete = (i / (slides.length - 1)); var x = 0; var y = presentation.getPageHeight() - BAR_HEIGHT; var barWidth = presentation.getPageWidth() * ratioComplete; if (barWidth > 0) { var bar = slides[i].insertShape(SlidesApp.ShapeType.RECTANGLE, x, y, barWidth, BAR_HEIGHT); bar.getBorder().setTransparent(); bar.setLinkUrl(BAR_ID); } } }
function onMessage(e) { var bot = e.message.annotations[0].userMention.user.displayName; var loc = encodeURI(e.message.text.substring(bot.length+2)); var mapClick = { "openLink": { "url": "https://google.com/maps/search/?api=1&query=" + loc } }; return { // see JSON payload in the documentation link above }; }
var slide = presentation.appendSlide(SlidesApp.PredefinedLayout.BLANK); var image = slide.insertImage(link);
function getIndexesOfFilteredRows(ssId, sheetId) { var hiddenRows = []; // limit what's returned from the API var fields = "sheets(data(rowMetadata(hiddenByFilter)),properties/sheetId)"; var sheets = Sheets.Spreadsheets.get(ssId, {fields: fields}).sheets; for (var i = 0; i < sheets.length; i++) { if (sheets[i].properties.sheetId == sheetId) { var data = sheets[i].data; var rows = data[0].rowMetadata; for (var j = 0; j < rows.length; j++) { if (rows[j].hiddenByFilter) hiddenRows.push(j); } } } return hiddenRows; }
function setSheetBasicFilter(ssId, BasicFilterSettings) { //requests is an array of batchrequests, here we only use setBasicFilter var requests = [ { "setBasicFilter": { "filter": BasicFilterSettings } } ]; Sheets.Spreadsheets.batchUpdate({'requests': requests}, ssId); }
Posted by Michael Winser, Product Lead, Google Apps and Wesley Chun, Developer Advocate, Google Apps
Last week, we clarified the expectations and responsibilities when accessing Google user data via OAuth 2.0. Today, we’re announcing that in order to better protect users, we are increasing account security for enterprise Gmail users effective October 5, 2016. At this time, a new policy will take effect whereby users in a Google Apps domain, while changing their passwords on or after this date, will result in the revocation of the OAuth 2.0 tokens of apps that access their mailboxes using Gmail-based authorization scopes. Please note that users will not notice any specific changes on this date and their applications will continue to work. It is only when a user changes their password from that point moving forward that their Gmail-related tokens become invalid.
Developers should modify their applications to handle HTTP 400 or 401 error codes resulting from revoked tokens and prompt their users to go through the OAuth flow again to re-authorize those apps, such that they can access the user’s mailbox again (additional details below). Late last year, we announced a similar, planned change to our security policy that impacted a broader set of authorization scopes. We later decided not to move forward with that change for Apps customers and began working on a less impactful update as described above.
What is a revoked token?
A revoked OAuth 2.0 token no longer provides access to a user’s resources. Any attempt to use a revoked token in API calls will result in an error. Any existing token strings will no longer have any value and should be discarded. Applications accessing Google APIs should be modified to handle failed API calls.
Token revocation itself is not a new feature. Users have always been able to revoke access to applications in Security Checkup, and Google Apps admins have the ability to do the same in the Admin console. In addition, tokens that were not used for extended periods of time have always been subject to expiration or revocation. This change in our security policy will likely increase the rate of revoked tokens that applications see, since in some cases the process will now take place automatically.
What APIs and scopes are impacted?
To achieve the security benefits of this policy change with minimal admin confusion and end-user disruption, we’ve decided to limit its application to mail scopes only and to exclude Apps Script tokens. Apps installed via the Google Apps Marketplace are also not subject to the token revocation. Once this change is in effect, third-party mail apps like Apple Mail and Thunderbird―as well as other applications that use multiple scopes that include at least one mail scope―will stop accessing data upon password reset until a new OAuth 2.0 token has been granted. Your application will need to detect this scenario, notify the user that your application has lost access to their account data, and prompt them to go through the OAuth 2.0 flow again.
Mobile mail applications are also included in this policy change. For example, users who use the native mail application on iOS will have to re-authorize with their Google account credentials when their password has been changed. This new behavior for third-party mail apps on mobile aligns with the current behavior of the Gmail apps on iOS and Android, which also require re-authorization upon password reset.
How can I determine if my token was revoked?
Both short-lived access tokens and long-lived refresh tokens will be revoked when a user changes their password. Using a revoked access token to access an API or to generate a new access token will result in either HTTP 400 or 401 errors. If your application uses a library to access the API or handle the OAuth flow, then these errors will likely be thrown as exceptions. Consult the library’s documentation for information on how to catch these exceptions. NOTE: because HTTP 400 errors may be caused by a variety of reasons, expect the payload from a 400 due to a revoked token to be similar to the following:
{ "error_description": "Token has been revoked.", "error": "invalid_grant" }
How should my application handle revoked tokens?
This change emphasizes that token revocation should be considered a normal condition, not an error scenario. Your application should expect and detect the condition, and your UI should be optimized for restoring tokens.
To ensure that your application works correctly, we recommend doing the following:
If your application uses incremental authorization to accrue multiple scopes in the same token, you should track which features and scopes a given user has enabled. The end result is that if your app requested and obtained authorization for multiple scopes, and at least one of them is a mail scope, that token will be revoked, meaning you will need to prompt your user to re-authorize for all scopes originally granted.
Many applications use tokens to perform background or server-to-server API calls. Users expect this background activity to continue reliably. Since this policy change also affects those apps, this makes prompt notification requesting re-authorization even more important.
What is the timeline for this change?
To summarize, properly configured applications should be expected to handle invalid tokens in general, whether they be from expiration, non-existence, and revocation as normal conditions. We encourage developers to make any necessary changes to give their users the best experience possible. The policy change is planned to take effect on October 5, 2016.
Please see this Help Center article and FAQ for more details and the full list of mail scopes. Moving forward, any additional scopes to be added to the policy will be communicated in advance. We will provide those details as they become available.
Calling all developers: try our developer preview today!
As you can see from above, Android add-ons offer a great opportunity to build innovative integrations and reach Docs and Sheets users around the world. They’re basically Android apps that connect with Google Apps Script projects on the server-side, allowing them to access and manipulate data from Google Docs or Sheets using standard Apps Script techniques. Check out our documentation which includes UI guidelines as well as sample code to get you started. We’ve also made it easy for you to publish your apps with the Apps Script editor.
Android add-ons are available today as a developer preview. We look forward to seeing what you build!
Posted by Sumit Chatterjee, Product Manager, Google Mobile Management and Wesley Chun, Developer Advocate, Google Apps
As security on mobile devices becomes increasingly important, we want to give Google Apps administrators the visibility to monitor key activities occurring on managed devices in their domain. With that in mind, today we are launching Mobile Audit for Google Apps Unlimited users, which audits activities on iOS and Android devices (using the native iOS Sync and Android Sync protocols) managed by Google Mobile Management. Examples of activities that are recorded include: operating system updates, device settings changes, and mobile application installs, updates and removals.
Google Apps Administrators can also go to the Reports page in the Admin console to see all the Mobile Audit events. Below is an example of what this page looks like querying one user’s activity, including newly registered devices:
As part of this launch, we are excited to make all Mobile Audit events available programmatically through the Admin SDK Reports API via Mobile Audit Activity Events. The Admin SDK is accessible through its REST API or Google Apps Script. With this new feature, administrators will be able to query all of the Mobile Audit information by user, by device, or by event, as well as verify that all of your deployed devices are running the latest security updates. You can even send this information to an asset management system to track all of your mobile inventory.
For example, if we wanted to download all of the newly registered devices, you could make a call like this to the API (via Google Apps Script):
AdminReports.Activities.list('all', 'mobile', { eventName: "DEVICE_REGISTER_UNREGISTER_EVENT", filters: "ACCOUNT_STATE==REGISTERED", maxResults: 1000 });
Once you have the response from the API, you could easily parse through that data, including device models, serial numbers, user email addresses, and any other relevant information to help you track your inventory.
A more critical use case that mobile administrators have been asking for is a way to take proactive actions on devices without requiring manual intervention. For example, a mobile administrator may want to automatically block a device when something suspicious is detected. This new feature enables admins to write simple apps that do just that.
Using the Mobile Audit Activity Events in the Reports API and the Mobile Devices component of the Directory API, administrators can create scheduled scripts to check for devices that are reporting suspicious activity, and then take actions on those devices.
The Apps Script snippet below returns results if there are any mobile audit events for suspicious activity:
var response = AdminReports.Activities.list('all', 'mobile', { eventName: "SUSPICIOUS_ACTIVITY_EVENT", maxResults: 1000 });
If there are results, the code below shows you how to retrieve the unique Google identifier for those devices, via the “RESOURCE_ID” parameter. Note that this snippet only assumes a single device reporting suspicious activity—you’ll need to tweak the code to support more than one.
var parameters = response.items[0].events[0].parameters; var resourceId; for (i = 0; i < parameters.length; i++) { if (parameters[i].name == ‘RESOURCE_ID’) { resourceId = parameters[i].value; break; } }
Now the Directory API can be invoked to block the device that corresponds with the above unique identifier using this Apps Script snippet:
AdminDirectory.Mobiledevices.action('my_customer', resourceId, { action: "block" });
We are really excited to see what uses you have for Mobile Audit in the Reports API. For more information and to get started, please see the Developer Guide, reference documentation, and the Help Center. (NOTE: Again, this feature is available only to our customers with users on the Google Apps Unlimited license.)
Posted by Saurabh Gupta, Product Manager, Google Apps Script
There are two ways to send email in Apps Script: MailApp's sendEmail and GmailApp's sendEmail method. One of the differences between these two methods is that the MailApp’s sendEmail method doesn’t require the developer to be a Gmail user. For example, a Google Apps customer who doesn’t use Gmail, but uses Apps Script instead, can send emails through MailApp but not GmailApp.
Starting on September 13, 2016, users with free public Google Accounts (consumers) and Google Apps for Education and Google Apps Free edition users, will be required to have Gmail access to send messages through Apps Script’s Mail Service. Consumers can enable Gmail on their Google account after signing-in—note your Gmail will then become the primary address of your Google account. Administrators of Google Apps domains (Education and Free edition only) can use the Admin console to turn on Gmail for their domain.
This change does not require any updates to your code. You can continue to use MailApp as before; just make sure that you have signed up for Gmail. We realize that sometimes these changes are disruptive to our developers, but we can assure you that we put lot of care and deliberation into this process.
Posted by Ben Greve, Developer Support Specialist, Google Apps Script
So you’ve started to build an add-on. Congrats! You identified a problem, figured out a solution, and wrote some code to accomplish it like a pro. Now it’s time to focus on design, to make sure your audience understands what your add-on does and how to use it.
In this post, I will outline five simple design tips to help make your add-on a pleasure to use. Don’t worry if you’re not an artist – these are basic concepts that anyone can apply.
Crafting a guided workflow takes the guesswork (and stress) out of using an add-on. Your user should never wonder, ‘What am I supposed to do next?’ Actions, forms, text, and buttons should be designed to create a natural flow guiding the user from one step to the next.
This can be accomplished in a number of ways. Try presenting actions in a natural order: from left to right and from top to bottom (assuming LtR language; adjust as needed). You can indicate which button is the primary action by styling it using the blue .action class. You can guide the user’s behavior by limiting the actions available (sometimes referred to as ‘forcing function’). For example, actions/options with dependencies can be disabled or hidden until they should be used. Another option is to spread a workflow across several pages and require the user to complete a given page before they can proceed to the next.
Complex add-ons require effective communication. Simple add-ons do, too. Effective communication is necessary for your audience to understand what your add-on does and how they should use it.
Use accessible language that anyone can understand. Don’t use complex wording if more easily digestible terminology is available. Unless your target audience has been demanding a feature to “asynchronously call an RPC with dependent proto messages,” you should avoid using unnecessarily technical or jargony language.
Present information when and where it’s needed. Instructions should be displayed in the context which they will be used. Actions should be clearly labeled so that users will know exactly what they do. Provide enough information so the user understands what they are doing, why they are doing it, and where they are going.
Have you ever used an app where you click on a button and nothing happens? You sit there wondering: Did it work? Did it not work? Did anything happen at all? When building your add-on, don’t do this to your users. Make sure that all actions have clear and immediate feedback, so no one is never left wondering, “What just happened?”.
With this in mind, there is still room for graceful design. Feedback can be subtle - it doesn’t need to shout, ‘ACTION 1 COMPLETED’! Leverage nuanced changes, such as displaying a quick message in a toast or moving to the next step in the workflow.
What happens if an action takes a long time to complete? Someone clicks a button and waits… and waits… and waits. A good UI will account for this scenario, too. Try using a loading graphic (i.e. a spinner or a progress bar) and for longer loading times consider including a button to cancel.
People make mistakes. It’s sad but true, and unlikely to change any time soon. Keep your users safe from themselves and design actions to have minimal risk.
The ideal solution is to remove the risk entirely. Inserting a bunch of data into a spreadsheet? Consider creating a new sheet and insert the data there. When appropriate, configure actions to add rather than replace, minimizing potential damage to existing content.
There will be situations where avoiding risk entirely won’t be possible. In these cases, do the best you can to explicitly communicate the action’s effect so your user can make a well-informed decision. A preview or a warning of the impending changes will help ensure that the user is aware of what’s coming. And of course, provide a method to ‘undo’ when possible.
Each UI element in your add-on should serve a purpose; consider removing anything that doesn’t. For the best design, keep it focused on functionality and trim any excess.
Using a large range of styling can actually undermine the power of the design. When a website is covered with different colors, styles, and fonts, it makes it difficult for any styling to communicate a specific meaning. Design patterns that are overly complicated or inconsistent make it difficult for users to learn what’s important and what isn’t.
Instead, consider an app with only three text stylings: one large, one bold, and one plain. The large style is always (and only) used for headers/titles, the bold style is used for labels, and the plain style is normal body text. Any time a user sees one of these, they’ll know exactly what they are looking at. Less is more.
Too many brilliant add-ons and apps have failed due to simple design flaws that made them inaccessible to users. These five tips are intended to help you prevent those common mistakes and provide a foundation for a great user experience.
As you work on your next add-on, remember the five lessons here:
Finally: please make sure to include the necessary onOpen() and onInstall() functions (if you want the add-on to work), follow our UI Style Guide, and use the provided CSS Package.
Have any tips or tricks of your own? Leave a comment below and share your design insights with the rest of the Apps Script community!