Skip to content

Commit 76ab5ea

Browse files
committed
Add automation samples from docs
1 parent d9c5d78 commit 76ab5ea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+4118
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"scriptId": "147xVWUWmw8b010zbiDMIa3eeKATo3P2q5rJCZmY3meirC-yA_XucdZlp"}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// To learn how to use this script, refer to the documentation:
2+
// https://developers.google.com/apps-script/samples/automations/agenda-maker
3+
4+
/*
5+
Copyright 2022 Google LLC
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
https://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
/**
21+
* Checks if the folder for Agenda docs exists, and creates it if it doesn't.
22+
*
23+
* @return {*} Drive folder ID for the app.
24+
*/
25+
function checkFolder() {
26+
const folders = DriveApp.getFoldersByName('Agenda Maker - App');
27+
// Finds the folder if it exists
28+
while (folders.hasNext()) {
29+
let folder = folders.next();
30+
if (
31+
folder.getDescription() ==
32+
'Apps Script App - Do not change this description' &&
33+
folder.getOwner().getEmail() == Session.getActiveUser().getEmail()
34+
) {
35+
return folder.getId();
36+
}
37+
}
38+
// If the folder doesn't exist, creates one
39+
let folder = DriveApp.createFolder('Agenda Maker - App');
40+
folder.setDescription('Apps Script App - Do not change this description');
41+
return folder.getId();
42+
}
43+
44+
/**
45+
* Finds the template agenda doc, or creates one if it doesn't exist.
46+
*/
47+
function getTemplateId(folderId) {
48+
const folder = DriveApp.getFolderById(folderId);
49+
const files = folder.getFilesByName('Agenda TEMPLATE##');
50+
51+
// If there is a file, returns the ID.
52+
while (files.hasNext()) {
53+
const file = files.next();
54+
return file.getId();
55+
}
56+
57+
// Otherwise, creates the agenda template.
58+
// You can adjust the default template here
59+
const doc = DocumentApp.create('Agenda TEMPLATE##');
60+
const body = doc.getBody();
61+
62+
body
63+
.appendParagraph('##Attendees##')
64+
.setHeading(DocumentApp.ParagraphHeading.HEADING1)
65+
.editAsText()
66+
.setBold(true);
67+
body.appendParagraph(' ').editAsText().setBold(false);
68+
69+
body
70+
.appendParagraph('Overview')
71+
.setHeading(DocumentApp.ParagraphHeading.HEADING1)
72+
.editAsText()
73+
.setBold(true);
74+
body.appendParagraph(' ');
75+
body.appendParagraph('- Topic 1: ').editAsText().setBold(true);
76+
body.appendParagraph(' ').editAsText().setBold(false);
77+
body.appendParagraph('- Topic 2: ').editAsText().setBold(true);
78+
body.appendParagraph(' ').editAsText().setBold(false);
79+
body.appendParagraph('- Topic 3: ').editAsText().setBold(true);
80+
body.appendParagraph(' ').editAsText().setBold(false);
81+
82+
body
83+
.appendParagraph('Next Steps')
84+
.setHeading(DocumentApp.ParagraphHeading.HEADING1)
85+
.editAsText()
86+
.setBold(true);
87+
body.appendParagraph('- Takeaway 1: ').editAsText().setBold(true);
88+
body.appendParagraph('- Responsible: ').editAsText().setBold(false);
89+
body.appendParagraph('- Accountable: ');
90+
body.appendParagraph('- Consult: ');
91+
body.appendParagraph('- Inform: ');
92+
body.appendParagraph(' ');
93+
body.appendParagraph('- Takeaway 2: ').editAsText().setBold(true);
94+
body.appendParagraph('- Responsible: ').editAsText().setBold(false);
95+
body.appendParagraph('- Accountable: ');
96+
body.appendParagraph('- Consult: ');
97+
body.appendParagraph('- Inform: ');
98+
body.appendParagraph(' ');
99+
body.appendParagraph('- Takeaway 3: ').editAsText().setBold(true);
100+
body.appendParagraph('- Responsible: ').editAsText().setBold(false);
101+
body.appendParagraph('- Accountable: ');
102+
body.appendParagraph('- Consult: ');
103+
body.appendParagraph('- Inform: ');
104+
105+
doc.saveAndClose();
106+
107+
folder.addFile(DriveApp.getFileById(doc.getId()));
108+
109+
return doc.getId();
110+
}
111+
112+
/**
113+
* When there is a change to the calendar, searches for events that include "#agenda"
114+
* in the decrisption.
115+
*
116+
*/
117+
function onCalendarChange() {
118+
// Gets recent events with the #agenda tag
119+
const now = new Date();
120+
const events = CalendarApp.getEvents(
121+
now,
122+
new Date(now.getTime() + 2 * 60 * 60 * 1000000),
123+
{search: '#agenda'},
124+
);
125+
126+
const folderId = checkFolder();
127+
const templateId = getTemplateId(folderId);
128+
129+
const folder = DriveApp.getFolderById(folderId);
130+
131+
// Loops through any events found
132+
for (i = 0; i < events.length; i++) {
133+
const event = events[i];
134+
135+
// Confirms whether the event has the #agenda tag
136+
let description = event.getDescription();
137+
if (description.search('#agenda') == -1) continue;
138+
139+
// Only works with events created by the owner of this calendar
140+
if (event.isOwnedByMe()) {
141+
// Creates a new document from the template for an agenda for this event
142+
const newDoc = DriveApp.getFileById(templateId).makeCopy();
143+
newDoc.setName('Agenda for ' + event.getTitle());
144+
145+
const file = DriveApp.getFileById(newDoc.getId());
146+
folder.addFile(file);
147+
148+
const doc = DocumentApp.openById(newDoc.getId());
149+
const body = doc.getBody();
150+
151+
// Fills in the template with information about the attendees from the
152+
// calendar event
153+
const conf = body.findText('##Attendees##');
154+
if (conf) {
155+
const ref = conf.getStartOffset();
156+
157+
for (let i in event.getGuestList()) {
158+
let guest = event.getGuestList()[i];
159+
160+
body.insertParagraph(ref + 2, guest.getEmail());
161+
}
162+
body.replaceText('##Attendees##', 'Attendees');
163+
}
164+
165+
// Replaces the tag with a link to the agenda document
166+
const agendaUrl = 'https://docs.google.com/document/d/' + newDoc.getId();
167+
description = description.replace(
168+
'#agenda',
169+
'<a href=' + agendaUrl + '>Agenda Doc</a>',
170+
);
171+
event.setDescription(description);
172+
173+
// Invites attendees to the Google doc so they automatically receive access to the agenda
174+
newDoc.addEditor(newDoc.getOwner());
175+
176+
for (let i in event.getGuestList()) {
177+
let guest = event.getGuestList()[i];
178+
179+
newDoc.addEditor(guest.getEmail());
180+
}
181+
}
182+
}
183+
return;
184+
}
185+
186+
/**
187+
* Creates an event-driven trigger that fires whenever there's a change to the calendar.
188+
*/
189+
function setUp() {
190+
let email = Session.getActiveUser().getEmail();
191+
ScriptApp.newTrigger("onCalendarChange").forUserCalendar(email).onEventUpdated().create();
192+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Make an agenda for meetings
2+
3+
See [developers.google.com](https://developers.google.com/apps-script/samples/automations/agenda-maker) for additional details.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"timeZone": "America/New_York",
3+
"dependencies": {
4+
},
5+
"exceptionLogging": "STACKDRIVER",
6+
"runtimeVersion": "V8"
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"scriptId": "1YGstQLxmTcAQlSHfm0yke12Y2UgT8eVfCxrG_jGpG1dHDmFdOaHQfQZJ"}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// To learn how to use this script, refer to the documentation:
2+
// https://developers.google.com/apps-script/samples/automations/aggregate-document-content
3+
4+
/*
5+
Copyright 2022 Google LLC
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
https://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
/**
21+
* This file containts the main application functions that import data from
22+
* summary documents into the body of the main document.
23+
*/
24+
25+
// Application constants
26+
const APP_TITLE = 'Document summary importer'; // Application name
27+
const PROJECT_FOLDER_NAME = 'Project statuses'; // Drive folder for the source files.
28+
29+
// Below are the parameters used to identify which content to import from the source documents
30+
// and which content has already been imported.
31+
const FIND_TEXT_KEYWORDS = 'Summary'; // String that must be found in the heading above the table (case insensitive).
32+
const APP_STYLE = DocumentApp.ParagraphHeading.HEADING3; // Style that must be applied to heading above the table.
33+
const TEXT_COLOR = '#2e7d32'; // Color applied to heading after import to avoid duplication.
34+
35+
/**
36+
* Updates the main document, importing content from the source files.
37+
* Uses the above parameters to locate content to be imported.
38+
*
39+
* Called from menu option.
40+
*/
41+
function performImport() {
42+
// Gets the folder in Drive associated with this application.
43+
const folder = getFolderByName_(PROJECT_FOLDER_NAME);
44+
// Gets the Google Docs files found in the folder.
45+
const files = getFiles(folder);
46+
47+
// Warns the user if the folder is empty.
48+
const ui = DocumentApp.getUi();
49+
if (files.length === 0) {
50+
const msg =
51+
`No files found in the folder '${PROJECT_FOLDER_NAME}'.
52+
Run '${MENU.SETUP}' | '${MENU.SAMPLES}' from the menu
53+
if you'd like to create samples files.`
54+
ui.alert(APP_TITLE, msg, ui.ButtonSet.OK);
55+
return;
56+
}
57+
58+
/** Processes main document */
59+
// Gets the active document and body section.
60+
const docTarget = DocumentApp.getActiveDocument();
61+
const docTargetBody = docTarget.getBody();
62+
63+
// Appends import summary section to the end of the target document.
64+
// Adds a horizontal line and a header with today's date and a title string.
65+
docTargetBody.appendHorizontalRule();
66+
const dateString = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'MMMM dd, yyyy');
67+
const headingText = `Imported: ${dateString}`;
68+
docTargetBody.appendParagraph(headingText).setHeading(APP_STYLE);
69+
// Appends a blank paragraph for spacing.
70+
docTargetBody.appendParagraph(" ");
71+
72+
/** Process source documents */
73+
// Iterates through each source document in the folder.
74+
// Copies and pastes new updates to the main document.
75+
let noContentList = [];
76+
let numUpdates = 0;
77+
for (let id of files) {
78+
79+
// Opens source document; get info and body.
80+
const docOpen = DocumentApp.openById(id);
81+
const docName = docOpen.getName();
82+
const docHtml = docOpen.getUrl();
83+
const docBody = docOpen.getBody();
84+
85+
// Gets summary content from document and returns as object {content:content}
86+
const content = getContent(docBody);
87+
88+
// Logs if document doesn't contain content to be imported.
89+
if (!content) {
90+
noContentList.push(docName);
91+
continue;
92+
}
93+
else {
94+
numUpdates++
95+
// Inserts content into the main document.
96+
// Appends a title/url reference link back to source document.
97+
docTargetBody.appendParagraph('').appendText(`${docName}`).setLinkUrl(docHtml);
98+
// Appends a single-cell table and pastes the content.
99+
docTargetBody.appendTable(content);
100+
}
101+
docOpen.saveAndClose()
102+
}
103+
/** Provides an import summary */
104+
docTarget.saveAndClose();
105+
let msg = `Number of documents updated: ${numUpdates}`
106+
if (noContentList.length != 0) {
107+
msg += `\n\nThe following documents had no updates:`
108+
for (let file of noContentList) {
109+
msg += `\n ${file}`;
110+
}
111+
}
112+
ui.alert(APP_TITLE, msg, ui.ButtonSet.OK);
113+
}
114+
115+
/**
116+
* Updates the main document drawing content from source files.
117+
* Uses the parameters at the top of this file to locate content to import.
118+
*
119+
* Called from performImport().
120+
*/
121+
function getContent(body) {
122+
123+
// Finds the heading paragraph with matching style, keywords and !color.
124+
var parValidHeading;
125+
const searchType = DocumentApp.ElementType.PARAGRAPH;
126+
const searchHeading = APP_STYLE;
127+
let searchResult = null;
128+
129+
// Gets and loops through all paragraphs that match the style of APP_STYLE.
130+
while (searchResult = body.findElement(searchType, searchResult)) {
131+
let par = searchResult.getElement().asParagraph();
132+
if (par.getHeading() == searchHeading) {
133+
// If heading style matches, searches for text string (case insensitive).
134+
let findPos = par.findText('(?i)' + FIND_TEXT_KEYWORDS);
135+
if (findPos !== null) {
136+
137+
// If text color is green, then the paragraph isn't a new summary to copy.
138+
if (par.editAsText().getForegroundColor() != TEXT_COLOR) {
139+
parValidHeading = par;
140+
}
141+
}
142+
}
143+
}
144+
145+
if (!parValidHeading) {
146+
return;
147+
} else {
148+
// Updates the heading color to indicate that the summary has been imported.
149+
let style = {};
150+
style[DocumentApp.Attribute.FOREGROUND_COLOR] = TEXT_COLOR;
151+
parValidHeading.setAttributes(style);
152+
parValidHeading.appendText(" [Exported]");
153+
154+
// Gets the content from the table following the valid heading.
155+
let elemObj = parValidHeading.getNextSibling().asTable();
156+
let content = elemObj.copy();
157+
158+
return content;
159+
}
160+
}
161+
162+
/**
163+
* Gets the IDs of the Docs files within the folder that contains source files.
164+
*
165+
* Called from function performImport().
166+
*/
167+
function getFiles(folder) {
168+
// Only gets Docs files.
169+
const files = folder.getFilesByType(MimeType.GOOGLE_DOCS);
170+
let docIDs = [];
171+
while (files.hasNext()) {
172+
let file = files.next();
173+
docIDs.push(file.getId());
174+
}
175+
return docIDs;
176+
}

0 commit comments

Comments
 (0)