Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Group membership #47

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions group-membership/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"parserOptions": {
"ecmaVersion": 6
},
"extends": "../configs/appsscript.eslintrc.json"
}
119 changes: 119 additions & 0 deletions group-membership/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: Scale access for external vendors and partners
description: Share documents, events, and communications with users outside
your domain
labels: Sheets, Groups, Apps Script
material_icon: group_work
create_time: 2019-08-30
update_time: 2020-3-27
---
Contributed by Tech and Eco, follow me on
[Twitter](https://twitter.com/TechandEco)!

Use a Google Group to work cross-functionally with vendors, partners,
customers, and volunteers outside of your domain, and scale access
to work assignments and leverage security controls. You can add an external
user's Google email (this can be their own G Suite account, a G Suite user
account you created for them within your domain, or a free Gmail account) to
your G Suite Google group (not available for consumer Google Groups) via a
Google Sheet to manage users in bulk and keep track of the onboarding history.

Top 6 benefits of this solution:

1. Adds a new external user to a G Suite Google Group you create
(example: [email protected]) once, and it allows users to open all
resources that are shared with that group moving forward (calendar, files,
training site, dashboards (built on DataStudio), etc.

1. When adding one or more users to your Google Group, users can receive a
welcome email with the links they can access.
[Creating a Google Site](https://sites.google.com/new) is optional but
recommended to centralize information in a beautiful website interface.

1. The
[template of the welcome email](https://docs.google.com/document/d/1GZDh_u9B2ARpYs3Iks8ZouQ_Xlixq076kAst710Z4cg/edit?usp=sharing)
is composed in a Google Doc, and the script renders it as HTML. You can use
seperate Google Docs for different recipients to customize messaging.

1. Capture the email addresses of users in your Sheet, and add them in bulk to
the desired Google Groups at a later time by changing the column "Allowed"
to "yes" for each row of users.
TechandEco marked this conversation as resolved.
Show resolved Hide resolved

1. You or members of your team, can add users to different Google Groups as
long as the person who made a copy of this sheet has
_manager_ or _owner_ rights in each of those Google Groups.

1. Removing a user from a Group (via the Google Group's interface) will revoke
their access to all resources. You can also limit documents to be available
to that group for a
[specific period of time](https://support.google.com/docs/answer/2494893?co=GENIE.Platform%3DDesktop&hl=en).

![Users are added to a Google Group via a Google Sheet](https://cdn.jsdelivr.net/gh/gsuitedevs/solutions@master/group-membership/demo.gif)

## Technology highlights

- Install a trigger with a click so the script is setup to run everytime the
Sheet is edited. To learn more visit the
[Apps Script trigger guide](https://developers.google.com/apps-script/guides/triggers/installable))
- Add members to a _Google Group for Business_ using the
[Admin Directory API](https://developers.google.com/apps-script/advanced/admin-sdk-directory)

## Try it

1. Copy the [External Sustainability Group List](https://docs.google.com/spreadsheets/d/1toqdDkWSAOL7aIElil59RHJH0b1Efebg7GBlgjn3B2Y/copy) from your _G Suite_ account.
TechandEco marked this conversation as resolved.
Show resolved Hide resolved

1. Enter for testing purposes a _Gmail address you own_ and a Google Group you
have _rights to manage_ its membership. You can learn about
[group permissions here](https://support.google.com/groups/answer/2464975?hl=en)

> _Note_: The membership status will be populated by the words
> **Newly added** if the user was added to the group, or **Already added**
> if it recognizes the user is already a member of that group.

1. Enter “Yes” in the “Allowed” column.

1. Next, click the custom menu called **Install Trigger** > **onEdit**, this
will install a trigger that will run everytime a value is changed in the
sheet.

![Install trigger screenshot](https://cdn.jsdelivr.net/gh/gsuitedevs/solutions@master/group-membership/install_trigger.png)

1. When prompted, click the **Review permissions** and click **Allow** so the
script can email on your behalf.

> _Note_: If you get a warning that **This app isn't verified** continue
> with the verification process by clicking **Advanced** and then scroll
> down and click the grey text at the bottom that begins with **Go to...**

1. Your status field will change.

![Status column is changed](https://cdn.jsdelivr.net/gh/gsuitedevs/solutions@master/group-membership/final_group_add.gif)

1. Check your inbox to see the email, and then your Google Group’s interface
to see the new member added.

## _[optional]_ Customize your messaging

- If you wish to change the subject lines of your emails, replace
the text in the “Email subject” column.

- If you wish to change the
[email template](https://docs.google.com/document/d/1GZDh_u9B2ARpYs3Iks8ZouQ_Xlixq076kAst710Z4cg/edit#heading=h.uwtpzkmp9874)
that is sent out, replace the URL in the “Email template” column with your
preferred Google Doc. If you wish to include any of the column values in the
template, enter them as such in the template {{Column_name}} like
this: _Welcome, we have added your {{Email}} to this {{Google_Group}} in
order give you access to the following resources..._
> _Note_: If you encounter any issues with the welcome email, change the
> permission levels of the Google Doc templates to more open settings.

## Next steps

To get started with Google Apps Script, try out [the codelab][codelab]
which guides you through the creation of your first script.

You can also view the [full source code][github] of this solution on GitHub to
learn more about how it was built.

[codelab]: https://codelabs.developers.google.com/codelabs/apps-script-intro
[github]: https://github.com/gsuitedevs/solutions/blob/master/group-membership
Binary file not shown.
Binary file added group-membership/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added group-membership/final_group_add.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added group-membership/install_trigger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 150 additions & 0 deletions group-membership/src/Code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const EMAIL = 'Email';
const GOOGLE_GROUP = 'Google Group';
const ALLOWED = 'Allowed';
const EMAIL_TEMPLATE_DOC_URL = 'Email template doc URL';
const EMAIL_SUBJECT = 'Email subject';
const EMAIL_STATE = 'Email state';

const EMAIL_STATE_VALUE = {
sent: 'Sent',
alreadyInGroup: 'Already in group',
notSent: 'Not sent',
requiredFieldMissing: 'Required field(s) missing: fill out all fields for this row',
allowedFieldNotSpecified: '',
};

/**
* Installs a trigger in the Spreadsheet to run upon the Sheet being opened.
* To learn more about triggers read:
* https://developers.google.com/apps-script/guides/triggers
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Install trigger')
.addItem('onEdit', 'installOnEditTrigger')
.addToUi();
}

/**
* Installs a trigger in the Spreadsheet that is scheduled
* to run upon when values in the Sheet are edited.
* To learn more about triggers read:
* https://developers.google.com/apps-script/guides/triggers/installable
*/
function installOnEditTrigger() {
ScriptApp.newTrigger('onEditInstallableTrigger')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create();
}

/**
* Trigger that runs on edit after being installed via the interface.
*
* @param {Object} e - onEdit trigger event.
*/
function onEditInstallableTrigger(e) {
TechandEco marked this conversation as resolved.
Show resolved Hide resolved
// Get the headers, row range and values from the active sheet.
const sheet = SpreadsheetApp.getActiveSheet();
const headers = sheet.getDataRange().offset(0, 0, 1).getValues()[0];
const range = sheet.getRange(e.range.getRow(), 1, 1, headers.length);
const row = range.getValues()[0];

// Convert the row Array into an entries Object using the headers for the
// field names.
const entries = headers.reduce((result, columnName, i) => {
result[columnName] = row[i];
return result;
}, {});

// Update the entries Object with the email state returned by addToGroup().
try {
const emailState = addToGroup(
entries[EMAIL],
entries[GOOGLE_GROUP],
entries[ALLOWED],
entries[EMAIL_TEMPLATE_DOC_URL],
entries[EMAIL_SUBJECT]
);
entries[EMAIL_STATE] = emailState == EMAIL_STATE_VALUE.sent ?
`${emailState}: ${new Date()}` : emailState;
} catch (e) {
TechandEco marked this conversation as resolved.
Show resolved Hide resolved
// If there's an error, report that as the email state.
entries[EMAIL_STATE] = e;
}

// Convert the updated entries Object into a row Array.
TechandEco marked this conversation as resolved.
Show resolved Hide resolved
const rowToWrite = headers.map((columnName) => entries[columnName]);

// setValues() receives a 2D array, so we create an array with the row
// contents.
range.setValues([rowToWrite]);
}


/**
* Trigger that runs on edit after being installed via the interface.
*
* @param {string} userEmail - email of user to add to the group.
* @param {string} groupEmail - address of Google Group to add user to.
* @param {string} allowed - 'yes' flag to add user to group.
* @param {string} emailTemplateDocUrl - Google Doc URL that serves as template
* of the welcome email sent to a user added to the group.
* @param {string} emailSubject - subject of welcome email sent to user added
* to group.
* @return {string} - state if email was sent to a user added in the sheet.
TechandEco marked this conversation as resolved.
Show resolved Hide resolved
*/
function addToGroup(userEmail, groupEmail, allowed, emailTemplateDocUrl, emailSubject) {
if (!allowed) {
return EMAIL_STATE_VALUE.allowedFieldNotSpecified;
}
if (!userEmail || !groupEmail || !emailTemplateDocUrl || !emailSubject) {
return EMAIL_STATE_VALUE.requiredFieldMissing;
}
if (allowed.toLowerCase() != 'yes') {
return EMAIL_STATE_VALUE.notSent;
}

// If the group does not contain the user's email, add it and send an email.
const group = GroupsApp.getGroupByEmail(groupEmail);
if (!group.hasUser(userEmail)) {
// User is not part of the group, add user to the group.
const member = {email: userEmail, role: 'MEMBER'};
AdminDirectory.Members.insert(member, groupEmail);

// Send a confirmation email that the member was now added.
const docId = DocumentApp.openByUrl(emailTemplateDocUrl).getId();
const emailBody = docToHtml(docId);

// Replace the template variables like {{VARIABLE}} with real values.
emailBody = emailBody
.replace('{{EMAIL}}', userEmail)
.replace('{{GOOGLE_GROUP}}', groupEmail);

MailApp.sendEmail({
to: userEmail,
subject: emailSubject,
htmlBody: emailBody,
});

return EMAIL_STATE_VALUE.sent;
}
return EMAIL_STATE_VALUE.alreadyInGroup;
}

/**
* Fetches a Google Doc as an HTML string.
*
* @param {string} docId - The ID of a Google Doc to fetch content from.
* @return {string} The Google Doc rendered as an HTML string.
*/
function docToHtml(docId) {
const url = 'https://docs.google.com/feeds/download/documents/export/Export?id=' +
docId + '&exportFormat=html';
const param = {
method: 'get',
headers: {'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()},
muteHttpExceptions: true,
};
return UrlFetchApp.fetch(url, param).getContentText();
}
22 changes: 22 additions & 0 deletions group-membership/src/appsscript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"timeZone": "America/Los_Angeles",
"dependencies": {
"enabledAdvancedServices": [{
"userSymbol": "AdminDirectory",
"serviceId": "admin",
"version": "directory_v1"
}]
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/admin.directory.group.member",
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/groups",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.scriptapp",
"https://www.googleapis.com/auth/script.send_mail",
"https://www.googleapis.com/auth/spreadsheets.currentonly"
]
}