Zhang: So
before we get started,
can I just get a show of hands
for how many people here
work with workflows
on a daily basis?
So most of you, and that's
probably why you're here.
So you know,
by our definition,
the workflow
is just the management
of a sequence of steps
in a business process,
and that can be anything
from a vacation request approval
to a document approval,
to an expenses report.
And so workflows really are,
you know, relevant to everybody.
And again, by a show of hands,
how many people here
are familiar
with Google Sites?
So most of you, as well.
And how many of you were at
the Apps Script talk yesterday?
Okay, so about half of you.
So great.
Just for those
that aren't familiar,
Google Sites
is just an easy way
to create and share
web pages and websites.
You can embed documents.
You can share them
with your co-workers,
and you can even publish them
on the web.
Apps Script is our cloud
scripting service.
It's a service side
JavaScript language,
and you can use it to extend
and automate Google Apps.
So here's the agenda
for today.
I'm going to go over the basics
of Scripts and Sites,
then I'll talk about
a simple workflow,
which is posting comments
to a site owner.
Then I'm going to go over
a slightly more complex
document approval workflow,
and then Evin's going to
talk to you about
advanced document approval.
So let's go ahead and start
with Script insights.
So if you're familiar
with Apps Scripts,
you know that they are available
in spreadsheets,
and you can use them to interact
with all sorts of Google Apps.
But ultimately, I think
Sites really serve
as the hub for workflows.
If you think of your HR site,
or your team site,
or your expenses application,
these are all sites
in which these workflows live,
and so we're happy to say
that since the beginning
of this year,
Google Apps Script
is available in Google Sites.
With it, you can
now write scripts
that interact with a site
directly,
and you can create
these Script Gadgets
that let you render dynamic
content on a site's page.
If you use Sites,
it's actually really easy.
You can use Scripts
to create pages,
to share the site
to other people,
to apply themes and templates,
to search.
Pretty much anything you can do
through the site's UI,
you can do
with Apps Scripts.
So now, there's this top-level
Sites App class with it.
You can grab a site object
by domain name and site name.
Once you have a site object,
you can interact
with the four basic
page types,
which are WebPage,
ListPage,
AnnouncementPage,
and FileCabinetPage.
Themes can be applied
to pages or templates,
and pages are mostly
standard HTML,
so they're really easy to create
and edit with Apps Scripts.
You can also share
at the site level very easily.
You can add and get each
of the roles, the user roles.
So there's Viewer,
Collaborator, and Owner.
When you add somebody,
you add them by email address.
And when you get
the users for a role,
you get an array of email
addresses, so really simple.
How do you use it?
In the Insert menu,
you can see here
there's a new Apps Script Gadget
menu item,
and that's available
in the page Edit mode.
That lets you insert
the Script Gadgets
that dynamically
render content.
They work just like
any other Sites Gadget.
A lot of the properties
are the same,
and there's
some additional things
that let you change
the behavior of Script Gadgets.
In addition,
in the Manage Site pages,
there's a new Apps Script tab
that lets you see the scripts
that you have in the site,
and you can launch the editor,
which you're probably
already familiar with,
if you want to write
new scripts.
So let's go ahead and talk
about a simple workflow.
So one thing that's been
requested in Sites
that still isn't available
is the ability
to email a site owner without
revealing their email address,
and it turns out this is
actually really easy to do
with App Scripts.
There's two parts
to this problem.
The first part is
the rendering of this form.
You can see what that might
look like on this screenshot.
And the second part is
to actually grab the owners
and send them an email.
This is the code to do
that second part,
which I would say
is the most important part,
and it's really
only two lines long.
This is the
"sendEmail" function.
That underscore means
it's a hidden function.
We're not going to go
into that here.
It's not really relevant.
But in the first line
of the function,
we use SitesApp.getSite.
We grab our site
by domain and name.
We call getOwners
on that site object,
which gives us an array,
and then in the second line,
we join that array
into a comma-separated string.
We pass it to
MailApp.sendEmail.
We'll set the subject line
to "hello"
and the body of the email
to e.parameter.email,
and I'll show you where
that comes from later on.
But that's pretty much it.
That will send an email
to the site owners.
So let's talk about where
we actually get that email from,
and this is the code
to render that form.
We use the built-in doGet
function in Apps Script,
and that means that we're
going to be publishing
this script as a service,
which lets us render content
in the Script Gadget.
In the first few lines,
we will use UiApp,
and I hope, you know,
at least some of you guys
are familiar with this.
UiApp.createApplication
will create a UI application,
obviously, that will let you
render content.
We'll set its title
to "Contact owners,"
we'll create a vertical panel
that lets you arrange things
vertically on the screen,
set its ID to mainPanel,
which lets us refer to it
later on,
set its spacing.
And obviously, you can make it
arbitrarily beautiful,
but we're going to keep things
simple here.
Then we're going to add it
to the app, so pretty basic.
Then we're going to call
Session.getActiveUser.
That gets the user that's
currently viewing the form.
We get their email address,
and we will add a label
to show who the email
is coming from,
as the first element
in the mainPanel.
Then we will create a text area,
set its height and width,
and set its name to Email,
and this is where that
e.parameter.email came from.
Finally, we're going to
create a submitButton.
We'll create
a server clickHandler
that points to that sendEmail
function I just showed you.
We will add the mainPanel
as a CallbackElement,
and that lets us get
the content of the mainPanel
in our handler.
We'll add the handler
to the submitButton,
add the submitButton
to the mainPanel,
and we'll return the app
to render it,
and that's pretty much it.
And for those of you that were
at yesterday's talk,
you'll know that we know have
this Apps Script GUI builder,
and that's also usable here,
but we wanted to
keep the code simple
and make sure people knew
where things were coming from,
so we went with code here.
So let's see that in action.
This is our Acme Corp. website.
We're going to create
a new Contact Us page,
so that people can go there
to send us email,
and this is just
standard Sites UI
that you're probably
familiar with.
So this is our
Contact Us page.
It'll drop us
in the Edit view.
We don't have our Gadget yet,
so we'll just save it.
Now, if we go to More actions,
Manage site,
which is at the bottom,
we can see
that in our Admin pages,
there's now this
Apps Script item.
Going there shows us the scripts
that are already there.
We'll launch the editor
and paste in the code
I just showed you.
And I think
that all looks right,
so we're going to save it,
and we will call it
Contact Owners.
So okay, so that's it.
So let's go back
to the site.
And on this Admin page,
if we refresh it,
you can see that the Contact
Owners is now available.
So going back to our
Contact Us page,
we can now edit that page
and add that Gadget
we just created.
From the Insert menu,
we'll go to Apps Script Gadget,
and you see the Contact Owners
is now available.
We'll select that, and we get
a properties dialog.
Now, this is actually
important.
Because we want to allow
anybody who views the site
to be able to send email,
we're going to allow anyone
to invoke this service.
And you could have just
the members of your domain,
or you even just yourself.
At the bottom,
those are the standard
Sites Gadget properties.
We'll set it to Contact Us,
and save it.
Once we save it, you'll see
that our form now renders.
So it looks pretty good.
The email is coming
from Evin@breakapps.
Let's go ahead
and type a short message
to see if this works.
So we're typing.
So it looks like--
I don't know if you guys
are paying attention,
but the front page
was actually all in Latin.
So we don't know
how to read Latin.
We're going to
ask for some help.
So let's go ahead
and click Submit.
And we click it
a few times.
Nothing seems to really happen;
however, if we go to the Mail,
we see that we actually
did get email,
and we got it
multiple times.
So it's sort of working.
So what happened there?
The problem is that we didn't
give the user any feedback.
And obviously
we need to tell them
that we actually sent the email,
so that they know
not to constantly
click that button.
Here's that sendEmail function
I just showed you.
The first two lines
are the same,
but now we have this extra code
that will update the UI.
We call
UiApp.getActiveApplication.
We get the mainPanel by ID.
That's why we set it earlier.
We'll set it to notVisible,
so that the user can no longer
click on that button,
and then we'll add
another label that says
"Thanks for your feedback."
Once we add that to the app,
we can return it to render it,
and let's give it a shot.
So we'll go back
to our Contact Us page.
We type the same message,
because we still
can't read Latin,
and the site
is still in Latin.
So this time
when we click Submit,
we say, "Thank you
for your feedback."
All right,
so that was pretty easy,
things fairly basic
to the people
who are familiar
with Apps Script.
It should be
pretty familiar.
So let's talk about
complex document approvals.
Now, document approvals can get
really, really complicated,
and we're going to start
with a simple example
and move on from there.
So in our simple document
approval workflow,
there is essentially
three steps.
In the first step,
the requestor will submit
a doc for publication.
In the second step,
the approver will review
and hopefully
approve the document.
And once the document
is approved,
we can then publish that.
And this is actually
really easy to build
on top of Contact Owners.
This is the form
that we just created.
We're going to change the title
to write an article,
change the button to
Submit for Publication,
and the text area will just be
for the document itself.
So here's what
that's going to look like.
We've made the text area
a little bit bigger,
so you can actually see
what you're typing;
and when you click
Submit for Publication,
that's just going to send
an email to the approver.
But what does this mean?
Well, it means that documents
now live in emails,
and as we all know,
emails get deleted.
They get overlooked.
They get forgotten.
It's just not a very good
situation to be in.
So what we really need is to
push this data to a Datastore.
For simplicity, we're
going to use a spreadsheet
in this example.
Apps Script also supports
a JDBC connection,
so you can push this data to
a real database, if you like.
We're not going
to get into this here.
The documentation
for that is all online,
so you can review it,
if you're interested in that.
So here's the approval flow
that I've shown you so far.
In the first step,
the user goes to the site,
either types or pastes
their document
into the form
that we created.
When they submit
that document,
it will send an email
to the approver,
and it will also send that
document to our spreadsheet.
From the spreadsheet, we can
then publish that document
either back to the same site
or to a new site.
So here's a spreadsheet
that we think has the columns
that are going to be relevant.
You can see
that we have a column
for who the document's from,
when it was submitted,
when it should be published,
the title, the approver,
the status, and the content
of the document itself.
So how do we update that?
So again, this is
the sendEmail function.
The first two lines
are still the same,
but this time we're also
going to post that data
to a spreadsheet.
In the first line, we will say
SpreadsheetApp.openById,
and that ID is that funky
string of characters
that you see
in the spreadsheet URL.
We'll get the active sheet,
because there's only one sheet
in the workbook.
We will call
sheet.getRange,
and we want to get the first
empty row in the sheet.
So to do that,
we call sheet.getLastRow,
which gets the last filled row,
and we add one to it.
Once we have the range,
we can set the values
that we're interested in,
which is the user's email,
the date, the fact
that it's not been approved yet,
and the body of the email,
which is the content
of the document.
So let's see that
in action.
We've added this
Get Published page,
where we've already added
our Gadget.
We're going to start
typing our document.
And in the interest of
keeping people awake,
we're going to actually paste
that document in.
From there, we can actually
just go down to the bottom
and submit it
for publication.
So this time,
we actually get feedback,
so that's good.
And if we go to
our approvals spreadsheet,
we see that our document
is now here.
Its status is Unapproved,
and the content
is in that column.
So great.
That looks good.
Now, from that spreadsheet,
how do we publish that
back out to a site?
This is our publishRow function.
So it takes one parameter.
The (r) variable is going
to be the index of the row
that you want published.
So in the first line, again,
we get the sheet by ID.
We will grab the range,
and that's the range
that we're interested in.
We call getValues to get
the values of that range
as a two-dimensional array,
and then we get the site
that we want to publish to:
SitesApp.getSite domain
and name.
Once we have the site object,
we can call createWebPage
with the title and the body
values from the array,
and that will actually go ahead
and create a web page directly.
If we wanted, we could use
an announcements page
or an announcements post here.
We're going to keep things
simple
and create just a web page.
From there,
we're actually done.
We've already
published the document,
but we want to do
a few cleanup tasks,
so we will send an email
to the author saying,
"The post is live.
Here's the URL."
And we can update the Sheets
Status column to be published.
Let's try that out.
We're going to
set a title here,
because we didn't
do that earlier,
and this time
we'll go the site.
And we don't have
a Gadget this time,
so we're just going to
test that function
from the editor directly.
We're going to go
to Apps Script,
and you see that we have
our Publishing workflow script.
This is the publishRow function
I just showed you,
and there's this test function
here that calls publishRow
just on row 2
of this spreadsheet.
When we run that, it looks like
there were no errors.
Something red would've popped up
if there were.
And if we go back to our site,
you see that there's now
a Lorem Ipsum page
in the site.
So that has all the content
that we're interested in.
If we go to our email,
we see that we got
the Article published email.
Clicking on the link
will obviously bring us
back to that page.
Okay, so that was
pretty cool.
So the user
went to the site,
submitted their document
for publication.
We sent an email
to the approver.
We saved it in the spreadsheet,
and from the spreadsheet,
we've published it
to the site.
Now, for those of you
that are paying attention,
you might've noticed
there we're missing
a kind of critical part
of this workflow, right?
In all the approval flows
that I've worked with,
there's usually
an actual approval Step.
The problem is
that there's a whole lot
of ways to do approvals,
and so I'm going to
hand it over now to Evin
to talk to you about
all the different ways
you can complete this workflow.
Levey:
Thanks, Eric.
So as Eric said, we're
going to focus a little bit
on the approval Step,
and there's quite a few
different ways
that we can do this.
The document is now safely
into our spreadsheet,
so we're not too worried
about the email.
The approver knows
about the email
and can head back
to the control panel.
So let's look
at using that spreadsheet
as a Control Panel for
this whole publishing process.
We can write a simple
publish function
that will simply get the active
row of the spreadsheet,
the row that the publisher
is looking at,
and then invoke Eric's
publishRow function.
What we really want, though,
is not to have to run that
from the script editor,
but perhaps to be able
to run it
from a spreadsheet menu
or something convenient.
So we can add
an unopened function;
and this, like doGet,
is another one of those
special functions
inside App Script
that has a special meaning.
And any time a spreadsheet
document is opened,
we automatically run
this function.
So in here, we're just going
to add a little bit of code
that adds an approval menu
onto our control sheet.
So let's see that
in action.
We've got a second article
in here about gravity probe B.
We can go to our script editor
inside the spreadsheet this time
and paste in all the code
we've shown you,
including Eric's
publishRow function.
We'll save that, and now
what we're going to do
is reload the spreadsheet
to simulate an unopened event
happening, and you'll see
that a new menu pops up,
a Publish menu.
So we'll select a row,
and we'll click Approve.
You see the script is running
in the background,
and the status updated
to Published.
And now when we go back to
our Sites page and refresh that,
you see now
we have a new article
listed in the left hand panel.
Okay.
And, obviously, it's
the same publishRow function
that Eric showed us,
so that the author will get
an email telling them
that their article went out.
So it's all well and good.
But going to a spreadsheet
every time you need
to approve something
is a little bit cumbersome,
so let's talk about
approving from email,
which is maybe
a little bit nicer,
a little bit slicker.
It's far more efficient
for the approver.
And what we're going to do
is send a message
with an embedded HTML form.
The process just becomes
a one-click.
So we're going to have
a little digression here
and talk a bit
about HTML forms.
How many folks
are really comfortable
and familiar
with HTML forms?
Okay, so maybe only 50%,
so this is probably
a good digression.
Any time you log
into a website,
chances are you're using
an HTML form.
You put in your user name
and your password,
and you hit a Submit button.
And here's the actual HTML code
that you'd use to do that.
You see the first line
defines it
as a form with
a particular action.
I've truncated the URL there,
and the details from the form
are going to be posted back,
so it's a post method.
And we ask
for the user name,
and then we have
this input type field,
and the first one is a text
field called user name
and then, the second one
is a password field,
which means that we don't
display the content,
and it's called password.
And then the third input type
is actually a submit,
and that's the action.
And in this case,
it's called submit too.
So we can mimic this
inside Script,
and we're going to write
a little test function
called sendEmail,
and we're going to set
a variable called HTML
to be a form,
and then we're going to send
that variable
as a payload in an email
and basically see what happens.
Okay.
So that's my little form.
It asks a question.
"What is the name
of the Lone Ranger's horse?"
It gives me an input box
to fill in the answer,
and it takes a Submit button
to do an action.
So next, we want to figure out
how we capture that action.
So if we look back at
the original example
of the login page,
you'll see that first line
is the critical line,
and it's going to tell
the email client, in fact,
what to do with the content
that's entered in the form,
once this Submit action
is chosen.
And so we're going to have
to publish something at a URL
to receive that feedback.
So we've talked about unopened,
and we've talked about doGet.
Now, we're going to talk
about doPost.
It's another
magic function.
And when we have a post
back to Apps Script,
it's going to ask this function
to handle the payload,
which is (e), the event.
So you can see
in my example here,
I'm going to create
a little UI application.
I'm going to add a panel,
and into that panel,
I'm going to put a label
for every parameter
that was passed to me.
Okay?
So I don't know what's
going to come in the post.
Anybody can post anything
to this handler.
So let's see where
we go with this.
Eric showed you earlier
how to publish into a site.
This is very similar.
You can publish as a service,
and then you get back
a URL to use.
So let's watch
that in action.
I've added my doPost code
into my script.
You can see it all there.
I'm going to go to Share
and Publish as Service.
And I get the same three
familiar options:
allow only me, only members
of my domain or organization,
or anybody at all
to invoke the service.
And that's the critical URL.
That's now live on the web.
And what I want to do is
go back to my email payload
and use that action,
and in the payload,
so that the email client
knows where to send the click.
It's a little bit convoluted,
but I hope everybody
is following along.
Now, when I send it, my email
comes down to the email client.
I get the question.
I fill in the answer.
And this time,
when I hit Submit,
we actually are going to post
all the content of the form
back to our script.
Our script is going to
handle the payload
and print it out
on the screen
with a little UI app,
very, very basic.
It's going to give us
the service that's been invoked,
which is actually
the ID of the URL,
and it gives me
back the answer,
and that's the really
important part.
So that field
that I named answer,
that was a text box,
is now coming back to my server,
back to my script.
So to approve from an email,
we're going to use
that HTML form capability.
We're going to embed the article
and the approval button
into the email.
That's actually
not sufficient.
We also need a hidden field
that tells us
which row of the spreadsheet
we're going to approve.
And then lastly,
we want to ensure
that our doPost is checking
which user is posting.
We don't want to let just any
user run the approval function.
We might find
we're under attack.
So our approval flow
that Eric showed us
then becomes an author,
goes to a site,
submits an article
for publication.
It gets written to our
spreadsheet Datastore,
and it gets emailed
out to the approver,
and then the approver
in the email
can just click a button
to approve,
and it'll get posted back
to the site for publication.
So let's look at the first
part of that, the request.
This is almost identical
to before.
We send the body of the email
out in the message,
but we also include
a little HTML form,
and it's exactly
as I mentioned before,
a form action that's going to
point back to our script server,
a hidden value called row
that's going to reference
the spreadsheet row,
and then a submit button
that we're going to call
Approve, in this case,
and then we send it.
So let's see that happening.
We're going to write yet
another article to send,
maybe Google I/O,
an appropriate topic.
And when we submit that,
you'll see when we go
to the email,
it goes into
the spreadsheet,
and then when we go
to the email,
you'll see that we get
the full article,
along with the Approve button.
So now we just need to wire up
the Approve button.
We have a simple doPost,
and now this time,
instead of throwing up
the content
that was posted onto the screen
for the approver,
we're going to call Eric's
very, very simple publishRow,
and invoke
that part of the workflow,
and we're also going to
let the user know,
the approving user,
know that it worked.
So we click Approve--
and we have a very simple
acknowledgment page
that just says Published.
You could obviously make this
as fancy as you like,
but then when we go back
to the site,
we now have an article
about Google I/O.
All right.
So this is all getting
a little bit better.
One thing, though,
that I didn't talk about yet
is the security of this.
How do we know
who clicked the Approve button?
And this is
obviously critical.
What happens if the approver
accidentally forwarded the email
to somebody else?
Could they just click this
button, and would it work?
Well, we showed you
that the service itself
is restricted to the domain
or organization,
but we really need
a user level check.
So inside our doPost function,
let's--
this is a good example
of what we could do.
We could set the message
to be "Access Denied!"
and then only if the active user
is the approving user
would we actually call
the publish function
and update the message
to be published;
otherwise, we'd just
throw up whatever message
we have on the screen
for the approver,
be it "Access Denied!"
or published.
Now, obviously, we're not
advocating hard coding approvers
into your script,
but for clarity,
this is the easiest way
to show it.
We'll talk a little bit
later on about roles
and how we can handle
that more gracefully.
So to get a little deeper
into the security model--
and there's two user concepts
inside Apps Script,
when it's used as a service.
There's the Effective User,
and that's the account
running the script
and the publisher, in this case.
And there's an Active User,
and that's the user
whose fingers are
at the keyboard.
Script can only figure out
who the Active User is
in special cases.
Obviously, we don't want these
public on the Internet,
and publishers then scraping
identities of all kinds of users
who happen to visit a page.
So it's only when both users
are part of the same domain
or organization we let you
identify the user,
and the domain must not be
a special domain.
We don't allow this
in partner or edu domains,
so it's mostly
Google Apps Premier,
Google Apps Standard.
So that was
document approval.
That was the entire workflow,
end-to-end.
This next section of the talk
is a little bit different.
We're going to go into
pretty advanced topics.
I know this is
a 101 level course.
We're not going to go through
detailed work examples.
We're going to talk more about
theoretical ways
that you can manage the
complexity and add a few more--
I won't say bells and whistles,
because that kind of implies
they're unnecessary--
but a few more
little advanced tweaks
to your workflow
to make them more usable.
The first thing,
multiple approvers.
The real value of a workflow
is to codify a complex process.
Our process is
ridiculously simple.
It's just one
marketing person
approving a post
to a sites page.
We generally need
more than marketing.
We probably need legal, and PR,
and managerial approvals.
And big companies, probably
an awful lot of people involved.
So how do we handle
this explosion of complexity?
And that's really what this
section of the talk is about.
So what we want to do
is introduce
a better representation
for this state of the approval
at any point in time.
I'm going to use a simple
diagram to illustrate this.
The original workflow
was a submit by anybody
that would go to
our marketing person,
who could then approve an email
or in the spreadsheet.
But, realistically, it would
then go, once approved,
to the PR person,
and then maybe to legal,
and then to the manager,
and then get published,
if we're lucky.
And more often than not,
something's going to go wrong,
and you're going to get rejected
and go back to step one.
To get even more realistic,
it would get submitted to PR
and marketing at the same time.
And then only when
it's approved by both,
a kind of
a synchronization point,
then it might go to legal,
and then to the manager,
and then to publish.
And these are still
probably very basic examples,
compared to what most of you
deal with day-to-day,
but even at this level, we're
not going to include rejection,
because it's just getting
too difficult to draw.
So let's model this
inside our process.
This process
is a sequence of steps.
There's always
one approver per step,
and in the first step, there are
actually multiple approvers.
The approval state is going to
be maintained inside the Step,
and I'm using Step
with a capital "S" here,
because it's an object type that
we'll talk about in a moment.
We're going to use
a JavaScript object
to represent that Step,
and then our process
is simply an array of Steps.
Now, I've given you
a sequential process.
There's definitely
more advanced things we can do,
maybe in a 201-level course
some other time.
So just think
about it that way.
It's sequential,
and so it's an array.
So we're going
to create a Step.
It's going to take a list
of approvers for the Step.
We're going to create
a JavaScript object
in the first row.
We're going to set the number
of approvers in the Step
to be the number of elements
passed in, in our array.
We're going to copy
that array.
Just a little quirk
of JavaScript,
that we don't want to reference
to an array.
We want an actual copy,
so we'll use
the shorthand approver's slice.
And then we're
going to set the status
for the overall Step
to be pending. Okay?
Then we're going to iterate
through all the approvers
that we've been given
and put a field into this object
for each approver, indicating
that approver's specific status.
So on the right hand side,
if you're familiar with JSON,
you'll see that if I create Step
with Evin and Bob,
then I get the object beneath.
Status is set to pending.
Two approvers.
The approvers are Evin or Bob--
Evin and Bob--excuse me.
And then both Evin and Bob have
their status set to pending.
All right,
so pretty straightforward.
Then we're going
to create the process,
and we've got the little diagram
of the example process
in the top right,
and it's going to be an array,
as we mentioned,
so that the first Step in
the array is marketing and PR.
So we just simply push back
that newly-created Step
onto the array.
The second Step is legal.
The third Step is the manager,
and then we return
the process object.
So it's just a slightly
more complex JSONObject.
So now we've got
a state representation
for each Step
and for the entire process,
and we want to be able
to load and save that at will.
So saving the process,
I'm going to show you saving
that to a spreadsheet,
because it's very easy to see.
But as Eric said,
you could use that ideally
in some kind of SQL database.
So I'm going to have a function
that can save my process
to a particular row
inside a sheet.
So I pulled back a status cell
to put this into,
and then I set the value
of that cell
to the JSON stringify
of the process.
Now, if you're not really
familiar with JavaScript,
this isn't that
much magic at all.
It just serializes
that JavaScript process
into something that's
actually human readable.
We'll see that
in a moment.
To load, I just want
to do the exact reverse.
Given a sheet and a row,
I want to pull back that cell
and then turn it back into
a real live JavaScript array
and sub objects,
and all I do is call eval
on the text I get back.
So let's see that working.
It's far easier when you see it
than when I try and explain it.
So we're going to
run our example,
and you'll see that it's very,
very straightforward.
We now have an array of Steps
that's been saved
to the status row.
And as we update that,
we can load it
and save it trivially.
Okay, so now we've got a handle
on how to manage the state.
The algorithm breaks down
to be something
very, very straightforward.
When we get something submitted
into our workflow,
we create an appropriate
approval process. Okay?
We write it back
to our Datastore,
and then we start
the first step,
and we do that by, in our case,
emailing the approvers.
You can just
grab the first step,
iterate through the approvers,
and send each of them
an email.
Then when we get
an approval event,
because somebody has clicked
the approval button or approved
in some other manner,
our script is going to wake up.
It's going to go back
and get the active Step
from our Datastore
and update the status.
Somebody just approved.
I need to update the status.
If the Step now
is fully approved,
I'm going to start
the next Step,
naturally send out
more emails.
And if all the Steps
are approved,
I'm going to go
to the final state,
and in our case,
that's publish.
And this is
pretty generic.
You can use this for almost
any level of complexity
for a sequential workflow.
So let's talk about
dynamic approvals.
Our process structure
is very flexible.
Steps can be added
and removed trivially.
We're just manipulating
a JavaScript array,
and I think everybody knows
how to do that.
So some of the things
we could do is
we could automatically
add a legal Step, perhaps,
if a post contained profanity
or restricted keywords,
or any kind of odd content
that we're able to detect
inside our script.
Another thing we could do is,
along with an approval button,
we could also have
a bunch of reference buttons
or refer buttons.
So if one of the approvers
is concerned about the content,
they could add a Step
by clicking,
not the Approve button,
but the Refer to Legal button,
for example.
So it really is
quite a flexible system.
So from here,
let's look at
another kind of advanced feature
and reminders.
As Eric pointed out,
when we were writing things
to the Datastore,
we can't just rely on emails.
Emails often get overlooked
and ignored.
So the delays that that cause
are annoying
and potentially costly.
We're going to show a way
to issue a daily reminder
of pending approvals,
and this is basically
a nag email.
So the first thing
we're going to do
is figure out
who needs to be reminded.
So we're going to get
the pending approvers
from our process.
The pending approvers
are going to be the approvers
on the active Step.
So we're not really concerned
about spamming everybody
who's in the process;
just those people
who are pending.
The first thing we do
is we get the active Step,
and I'll show you
that code in a moment,
and then we're going to set up
an array of pending approvers.
So we don't want to
spam the people
who have already approved.
We just want those in the Step
who are set to pending.
So for every user
in the list of approvers,
in the active Step, we're
going to check their status.
So active Step
for user X is pending.
Then we push that user back
onto the pending approvers.
Let's have a look at
the getActiveStep function.
We're going to just iterate
through the array of Steps
in the process,
and we're going to find
the first Step that hasn't
been fully approved.
So the first Step
that's pending.
So relatively straightforward,
relatively simple.
Then to send the reminders,
our function is going to
grab open our Datastore,
our spreadsheet.
It's going to pull all the data
from the spreadsheet,
and then it's going to iterate
through every single row
and get the pending approvers
on each row
and send them a reminder email.
Okay?
So in one step,
we're going to iterate
through the entire Datastore
for every article that's pending
and send reminders
to everybody.
And then we need a way
to run this automatically.
This is pretty important.
Inside the spreadsheet editor,
inside the script editor,
you can set a function--
in this case,
send reminders--
to run automatically every day
at a particular time,
and so we're going to do it
every morning
between 8:00 and 9:00 a.m.
So now, when folks
get in to work,
they'll have a bright
shiny email nagging them
about approving
whatever is on their plate.
Okay.
So that's a very basic
reminder strategy.
I think, before you know it,
you'll be getting nasty emails
telling you to back off,
that it's only been 24 hours,
et cetera, et cetera
but it's very easy to extend
and customize.
You could, for example,
store the last date of nagging
in the Step
and then check how long
it's been since you sent a nag,
so that you're only sending
a nag every few days.
It really is a very solid
foundation for Escalations.
If you're going to escalate
something, though,
you really need to know about
the organizational hierarchy,
and that's what we're
going to talk about next.
This is one of the most
important pieces of workflow,
and I think it's something
that traditionally,
we haven't had
a great solution for.
You typically do need
to know a user's manager.
You need to know
who fills the PR role,
who fills the marketing role,
who fills the legal role.
We need to be able to find
the users responsible
for the approval,
and we don't want to hard code
these things into script
that has to be updated every
time there's an org change.
We'd go crazy.
So it is very important
that a script be able
to get access to an
organization hierarchy.
So at Google, we've got these
things called Google Profiles,
and I guess--has everybody here
seen their own Google Profile?
No, blank stares.
Okay, one or two people.
It really isn't all that useful
in an enterprise setting.
You go there, and you see
some security settings,
you see your dashboard,
you see your own email address,
all things that you already
know about,
and generally
not all that helpful.
But behind that, there is
an incredible Datastore
that has a huge amount
of information,
and that's all thanks
to a thing called
Google Apps Directory Sync.
Directory Sync allows you
to take an LDAP server's content
and synchronize it out
to Google Profiles
or maybe even an active
directory server.
This Apps Directory Sync
runs inside your organization,
periodically scraping
all that data
and pushing it out
into the cloud,
so then anything
that's running inside the--
running outside your firewall
and in the cloud
still has access
to all that information
that's centrally managed
inside your organization
by LDAP, or Active Directory,
or whatever system you're using.
And you can see
in this screenshot
that there's all kinds of
attributes like manager,
Assistant, Division,
so it's a wealth of information,
and it's exactly
what we need for workflow.
Along with the Directory Sync
and the Profiles,
there's a directory service
that's going to be announced.
I guess I'm
announcing it now.
It's going to be released
in the next couple of weeks.
So we can use this
to look up managers.
So how do we call
these APIs from App Script?
So App Script has a beautiful
range of very easy-to-use APIs,
but obviously there's a lot more
APIs that are available
that aren't yet available
in Apps Script.
So we're going to walk
through an example
of how to call these APIs
from script.
So I've thrown a big monster
chunk of code onscreen here,
and I think you'll all
be relieved to know
that most of it
is boilerplate.
And also, I should highlight
that in this case,
we're going to call
the Profiles API,
and we're going to access
the Datastore directly.
The Profiles API
has a weird characteristic
in that it's admin access only,
and the API that we're releasing
in a couple of weeks
is called a Directory Service,
and that's available
to everybody,
and the main difference is that
the Profiles API is read/write,
and the Directory Service API
is read-only.
Our example is read-only,
and so you'll be able
to use this code
pretty much exactly
to call the Directory Service
when it's released
in a few weeks,
and you won't need to be
a domain administrator.
All right, so the first
part of the code here,
what it does is--
as I said, boilerplate--
you tell it which service
you're accessing,
and then the documentation
for that service gives you
the access token URL,
the request token URL,
and the authorization URL.
You then also set a consumer key
and a consumer secret.
And for every Google service,
they're anonymous. Okay?
It confuses everybody.
Why would your key and secret
be anonymous?
But they are.
And then we can set options.
In this case, we're just going
to give it a name--Directory.
We're going to say
to use this OAuthToken.
That's actually
what we're creating
in the boilerplate code.
We're creating
an OAuthToken,
and then we're going to
set the headers
to tell it which version
of GData to use.
And really,
the only important code here
is at the very bottom
of the screen,
where we construct a URL
to hit this service.
And again, it's a URL
from the documentation.
And then, on the very
second last line,
we're going to add
the user name
that we're looking up
and the options,
and then we're going to return
the content text. Okay?
It's a mouthful,
but, trust me, it works.
Actually, and this is just
a larger version of what
I was talking about--
we're going to fetch that URL,
and the user name
we took in as a parameter
is just going to be
appended at the end,
and then we're going to
return the result.
So when I call that function
on myself inside Google,
this is actually
what I get back,
and I know it's very small
for you to read,
and that's intentional.
There's nothing
all that interesting
about what my entry is,
but there's all kinds of
stuff in there
like my telephone numbers,
my email address,
my manager's email address,
my manager's name,
the projects that I work on,
the OUs that I'm a part of.
Okay? So this is kind of
a gold mine,
when it comes to process
and workflow.
So we now have
a single source of truth
for our org structure,
and it's available today
in the Profiles API,
if you're a domain
administrator.
and if you're not,
it'll be available to you
in the Directory API
in the next few weeks.
So that's pretty much
what we have for you today,
and I'm going to do a quick
recap of what both Eric and I
have talked about, and then
we'll take some questions.
We started with Script
in Sites,
and Sites is the hub
for workflow, we believe.
Actions on a Site,
embedding Scripts
into a Site's page.
Then Eric told you
all about simple workflow,
private comments
to a site owner,
very straightforward,
and then we went into
document approval
and the simple publishing
workflow with a single approver,
and then we talked about
approval mechanisms,
how we can actually capture
that approval action
and translate it
into motion.
We talked about approving
from a spreadsheet
and approving from an email,
using HTML forms.
Then we talked about
multi-step approvals,
and this is really where
workflow gets realistic,
and saving the state of
the approval is the key thing.
It helps you
manage the complexity,
if you have a good
representation of the state.
And I can't emphasize
that enough.
It may be a dry topic,
but it'll save you weeks
and weeks of frustration
in the future,
if you do it nicely.
We talked about dynamically
altering the process,
which is often essential,
and then we talked about
the additional features
that are going to be essential:
reminders, escalation,
and figuring out
how to navigate your org's
hierarchy from Script
and from the cloud.
All right, so that's what
we have today.
Maybe we'll hold it there
for questions.
[applause]
man: I have a question.
Levey: Yes.
man: It's more something
I thought about
when you were talking about
the reminders,
and you can set those scripts
to run as a service.
Levey: Mm-hm.
man: So you know,
there's turnaround
in companies,
and maybe the person who wrote
that service is gone,
and somebody is getting
this constant reminder,
and we don't know
where it's coming from.
Is there a way to see
what services are running,
or, better still,
running out there in the cloud?
Levey:
Yes, absolutely.
So I think the question is,
with all these different
services
that you may be running,
and all these different triggers
that you may have set up,
is there any central place
to see what you've got?
man:
Right.
Levey: For triggers,
the answer is yes.
Inside the script editor,
when you go to
the triggers menu,
you can look at the triggers
for the current script,
or you can look at
all your triggers.
man: Okay.
Levey: And so you can get
a central view of everything
that you've set up to run
and respond to an event.
man: So at an administrative
level,
we could see it across
maybe all the scripts
that other people have created
within the organization?
Levey:
No, it's on a per user basis.
man:
Per user basis?
Levey: And so it's intended
to be user granularity,
absolutely.
man: Okay, great.
Thank you.
man: Hi. So you mentioned
saving a state to a database.
Levey:
Mm-hm.
man: And so I know that hosted
SQLs and Trusted Tester--
are you going to be able
to write from Apps Script
to hosted SQL?
Levey: Yes, absolutely.
That's one of the most
exciting things
that we'll be able to do
shortly.
And for any folks who are on
the hosted SQL Trusted Tester,
feel free to come by,
and I think I can hook you up
with something that'll
do that for you.
Okay, I guess
that's all the questions.
Thank you very much, everybody.
[applause]
