This repository was archived by the owner on Dec 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 93
Group membership #47
Open
TechandEco
wants to merge
17
commits into
googleworkspace:master
Choose a base branch
from
TechandEco:group-membership
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Group membership #47
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
241072b
Added group-membership solution
TechandEco 4c0d56c
Adding new solution to help add external emails to a Google Group fro…
TechandEco 653fbae
README redone
TechandEco c072cf3
updated group-membership solution
TechandEco 3910182
Resolved reviewer comments
TechandEco dddf96e
edited spacing
TechandEco fb1acdf
added validation code and updated instructions in readme
TechandEco 7776c1a
updated link of public sheet
TechandEco 05d60bc
updated lint to ES6 and made lets into consts
TechandEco 21a8c13
changed variable names
TechandEco f3800a0
downloaded data
TechandEco 7a7dbe7
made url copyable
TechandEco a980b70
Made status into classes
TechandEco c6a4709
Updated xls with added rows of data
TechandEco 78c77c7
changed emailBody from const to let
TechandEco c4fdd89
fixed minor issues
TechandEco e375774
reupdated xls sheet
TechandEco File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"parserOptions": { | ||
"ecmaVersion": 6 | ||
}, | ||
"extends": "../configs/appsscript.eslintrc.json" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
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). | ||
|
||
 | ||
|
||
## 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. | ||
|
||
 | ||
|
||
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. | ||
|
||
 | ||
|
||
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
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'; | ||
|
||
/** | ||
* Email state base class. | ||
*/ | ||
class EmailState { | ||
/** | ||
* Converts state to string. | ||
* @return {string} | ||
*/ | ||
toString() { | ||
return ''; | ||
} | ||
} | ||
|
||
/** | ||
* 'Email sent' state. | ||
*/ | ||
class StateSent extends EmailState { | ||
/** | ||
* Class constructor. | ||
* @param {Date} date - date when the email was sent. | ||
*/ | ||
constructor(date) { | ||
super(); | ||
this.date = date; | ||
} | ||
/** | ||
* Converts state to string. | ||
* @return {string} | ||
*/ | ||
toString() { | ||
return `Sent: ${this.date}`; | ||
} | ||
} | ||
|
||
/** | ||
* 'Already in group' state. | ||
*/ | ||
class StateAlreadyInGroup extends EmailState { | ||
/** | ||
* Converts state to string. | ||
* @return {string} | ||
*/ | ||
toString() { | ||
return 'Already in group'; | ||
} | ||
} | ||
|
||
/** | ||
* 'Not sent' state. | ||
*/ | ||
class StateNotSent extends EmailState { | ||
/** | ||
* Converts state to string. | ||
* @return {string} | ||
*/ | ||
toString() { | ||
return 'Not sent'; | ||
} | ||
} | ||
|
||
/** | ||
* 'Requiered field missing' state. | ||
*/ | ||
class StateRequiredFieldMissing extends EmailState { | ||
/** | ||
* Class constructor. | ||
* @param {string} user - email of user to add to the group. | ||
* @param {string} group - address of Google Group to add user to. | ||
* @param {string} template - Google Doc URL that serves as template | ||
* of the welcome email sent to a user added to the group. | ||
* @param {string} subject - subject of welcome email sent to user added | ||
*/ | ||
constructor(user, group, template, subject) { | ||
super(); | ||
this.user = user; | ||
this.group = group; | ||
this.template = template; | ||
this.subject = subject; | ||
} | ||
/** | ||
* Converts state to string. | ||
* @return {string} | ||
*/ | ||
toString() { | ||
return `Required field missing: ${ | ||
!this.user ? EMAIL : | ||
!this.group ? GOOGLE_GROUP : | ||
!this.template ? EMAIL_TEMPLATE_DOC_URL : | ||
EMAIL_SUBJECT}`; | ||
} | ||
} | ||
|
||
/** | ||
* 'Allowed field not specified' state. | ||
*/ | ||
class StateAllowedFieldNotSpecified extends EmailState {} | ||
|
||
/** | ||
* 'Error' state. | ||
*/ | ||
class StateError extends EmailState { | ||
/** | ||
* Class constructor. | ||
* @param {Exception} error - error message. | ||
*/ | ||
constructor(error) { | ||
super(); | ||
this.error = error; | ||
} | ||
/** | ||
* Converts state to string. | ||
* @return {string} | ||
*/ | ||
toString() { | ||
return `${this.error}`; | ||
} | ||
} | ||
|
||
/** | ||
* 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
|
||
// Don't do anything if the header row (first row) was updated. | ||
if (e.range.getRow() == 1) { | ||
return; | ||
} | ||
|
||
// 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 { | ||
entries[EMAIL_STATE] = addToGroup( | ||
entries[EMAIL], | ||
entries[GOOGLE_GROUP], | ||
entries[ALLOWED], | ||
entries[EMAIL_TEMPLATE_DOC_URL], | ||
entries[EMAIL_SUBJECT] | ||
).toString(); | ||
} 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] = new StateError(e).toString(); | ||
} | ||
|
||
// 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. | ||
console.log(JSON.stringify(rowToWrite)); | ||
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 {EmailState} - state if email was sent to a user added in the sheet. | ||
*/ | ||
function addToGroup(userEmail, groupEmail, allowed, emailTemplateDocUrl, emailSubject) { | ||
if (!allowed) { | ||
return new StateAllowedFieldNotSpecified(); | ||
} | ||
if (!userEmail || !groupEmail || !emailTemplateDocUrl || !emailSubject) { | ||
return new StateRequiredFieldMissing( | ||
userEmail, groupEmail, emailTemplateDocUrl, emailSubject); | ||
} | ||
if (allowed.toLowerCase() != 'yes') { | ||
return new StateNotSent(); | ||
} | ||
|
||
// 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(); | ||
|
||
// Replace the template variables like {{VARIABLE}} with real values. | ||
const emailBody = docToHtml(docId) | ||
.replace('{{EMAIL}}', userEmail) | ||
.replace('{{GOOGLE_GROUP}}', groupEmail); | ||
|
||
MailApp.sendEmail({ | ||
to: userEmail, | ||
subject: emailSubject, | ||
htmlBody: emailBody, | ||
}); | ||
|
||
return new StateSent(new Date()); | ||
} | ||
return new StateAlreadyInGroup(); | ||
} | ||
|
||
/** | ||
* 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(); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.