Skip to content

Commit c58c453

Browse files
authored
Core-977 - Incentivize module tutorial completion (#855)
These changes extend the demo lambda, deployed as part of our initial module tutorial, to create an event in Mixpanel with the user's GitHub username. when invoked, to signify tutorial completion.
1 parent a7bf8bf commit c58c453

File tree

6 files changed

+3085
-4385
lines changed

6 files changed

+3085
-4385
lines changed

_docs-sources/iac/getting-started/deploying-a-module.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ModuleTutorialCodeBlock from "/src/components/ModuleTutorialCodeBlock"
2+
13
# Deploying your first module
24

35
[Modules](../overview/modules.md) allow you to define an interface to create one or many resources in the cloud or on-premise, similar to how in object oriented programming you can define a class that may have different attribute values across many instances.
@@ -143,10 +145,7 @@ Next, we’ll write a simple Python function that returns a string that will be
143145

144146
Copy the following to `terraform-aws-gw-lambda-tutorial/main.py`.
145147

146-
```py title="terraform-aws-gw-lambda-tutorial/main.py"
147-
def lambda_handler(event, context):
148-
return "Hello from Gruntwork!"
149-
```
148+
<ModuleTutorialCodeBlock />
150149

151150
### Reference the module
152151

@@ -179,6 +178,14 @@ output "function_name" {
179178

180179
## Plan and apply the module
181180

181+
### Run Terraform init
182+
183+
Before you can run plan and apply, you need to run `terraform init`, which will prepare your Terraform configuration for use:
184+
185+
```bash
186+
terraform init
187+
```
188+
182189
### Run Terraform plan
183190

184191
Terraform will generate an execution plan using the `plan` action. The plan will show what resources Terraform determines need to be created or modified.

docs/iac/getting-started/deploying-a-module.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ModuleTutorialCodeBlock from "/src/components/ModuleTutorialCodeBlock"
2+
13
# Deploying your first module
24

35
[Modules](../overview/modules.md) allow you to define an interface to create one or many resources in the cloud or on-premise, similar to how in object oriented programming you can define a class that may have different attribute values across many instances.
@@ -143,10 +145,7 @@ Next, we’ll write a simple Python function that returns a string that will be
143145

144146
Copy the following to `terraform-aws-gw-lambda-tutorial/main.py`.
145147

146-
```py title="terraform-aws-gw-lambda-tutorial/main.py"
147-
def lambda_handler(event, context):
148-
return "Hello from Gruntwork!"
149-
```
148+
<ModuleTutorialCodeBlock />
150149

151150
### Reference the module
152151

@@ -179,6 +178,14 @@ output "function_name" {
179178

180179
## Plan and apply the module
181180

181+
### Run Terraform init
182+
183+
Before you can run plan and apply, you need to run `terraform init`, which will prepare your Terraform configuration for use:
184+
185+
```bash
186+
terraform init
187+
```
188+
182189
### Run Terraform plan
183190

184191
Terraform will generate an execution plan using the `plan` action. The plan will show what resources Terraform determines need to be created or modified.
@@ -257,6 +264,6 @@ Finally, consider what other resources you would create to make your modules rea
257264
<!-- ##DOCS-SOURCER-START
258265
{
259266
"sourcePlugin": "local-copier",
260-
"hash": "d039b6c2cda248bdf74af9e85c9200ef"
267+
"hash": "c5777fbceec4b7fecf415e45f62efaa9"
261268
}
262269
##DOCS-SOURCER-END -->

docusaurus.config.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @ts-check
22
// Note: type annotations allow type checking and IDEs autocompletion
3+
const path = require('path')
34

45
const lightCodeTheme = require("prism-react-renderer/themes/github")
56
const darkCodeTheme = require("prism-react-renderer/themes/dracula")
@@ -21,8 +22,8 @@ const enableGoogleAnalytics =
2122
const siteUrl = cfg.has("siteUrl")
2223
? cfg.get("siteUrl")
2324
: process.env["NETLIFY"]
24-
? process.env["DEPLOY_URL"]
25-
: "http://localhost:3000"
25+
? process.env["DEPLOY_URL"]
26+
: "http://localhost:3000"
2627

2728
const buildVersion = cfg.has("app.buildVersion")
2829
? cfg.get("app.buildVersion")
@@ -67,9 +68,9 @@ const config = {
6768
},
6869
googleAnalytics: enableGoogleAnalytics
6970
? {
70-
trackingID: googleAnalyticsConfig.trackingID,
71-
anonymizeIP: true,
72-
}
71+
trackingID: googleAnalyticsConfig.trackingID,
72+
anonymizeIP: true,
73+
}
7374
: undefined,
7475
},
7576
],
@@ -289,20 +290,20 @@ const config = {
289290
},
290291
algolia: algoliaConfig
291292
? {
292-
appId: algoliaConfig.appId,
293-
// Public API key: safe to commit, but still sourced from config
294-
apiKey: algoliaConfig.apiKey,
295-
indexName: algoliaConfig.indexName,
296-
libraryIndexName: algoliaConfig.libraryIndexName,
297-
contextualSearch: true,
298-
}
293+
appId: algoliaConfig.appId,
294+
// Public API key: safe to commit, but still sourced from config
295+
apiKey: algoliaConfig.apiKey,
296+
indexName: algoliaConfig.indexName,
297+
libraryIndexName: algoliaConfig.libraryIndexName,
298+
contextualSearch: true,
299+
}
299300
: undefined,
300301
zoomSelector: ".markdown :not(em) > img:not(.no-zoom)",
301302
posthog: enablePosthog
302303
? {
303-
apiKey: posthogConfig.apiKey,
304-
appUrl: posthogConfig.appUrl,
305-
}
304+
apiKey: posthogConfig.apiKey,
305+
appUrl: posthogConfig.appUrl,
306+
}
306307
: undefined,
307308
metadata: [
308309
{ name: "buildVersion", content: buildVersion },

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
"@mdx-js/react": "^1.6.21",
2929
"@svgr/webpack": "^5.5.0",
3030
"@types/jest": "^27.4.0",
31+
"buffer": "^6.0.3",
3132
"clsx": "^1.1.1",
3233
"config": "^3.3.6",
3334
"env-cmd": "^10.1.0",
3435
"file-loader": "^6.2.0",
36+
"path": "^0.12.7",
3537
"plugin-image-zoom": "ataft/plugin-image-zoom",
3638
"posthog-docusaurus": "^1.0.3",
3739
"prism-react-renderer": "^1.2.1",
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React, { useEffect, useState } from "react"
2+
3+
import { Buffer } from "buffer"
4+
5+
import CodeBlock from "@theme/CodeBlock"
6+
7+
export type ModuleTutorialCodeBlockProps = {
8+
github_username: string
9+
mixpanel_project_id: string
10+
}
11+
12+
export const ModuleTutorialCodeBlock: React.FC<
13+
ModuleTutorialCodeBlockProps
14+
> = ({ github_username, mixpanel_project_id }) => {
15+
const mixpanelProjectId = "5736098d8918525aa0a75f1d6dda8321"
16+
const buffer = new Buffer(mixpanelProjectId)
17+
const base64data = buffer.toString("base64")
18+
19+
const [githubUsername, setGithubUsername] = useState("unknown")
20+
21+
const getGitHubUsernameFromQueryString = () => {
22+
// Grab query string params out of URL
23+
const urlParams = new URLSearchParams(window.location.search)
24+
let githubUsername = urlParams.get("github_username")
25+
if (githubUsername == null) {
26+
githubUsername = "unknown"
27+
}
28+
return githubUsername
29+
}
30+
31+
const codeBlockSnippet = `import uuid
32+
import base64
33+
import json
34+
from urllib.request import urlopen, Request
35+
36+
37+
def lambda_handler(event, context):
38+
url = "https://api.mixpanel.com/track"
39+
40+
github_username = "%github_username%"
41+
42+
# This code sets up our mixpanel project ID and sends an event into Mixpanel
43+
# that includes your GitHub username to signify that you completed the tutorial
44+
# We don't track anything else about you other than your GitHub username, and we
45+
# only use this data internally to understand who has completed our tutorial
46+
mixpanelClientId = "%mixpanel_project_id%"
47+
tok = base64.b64decode(mixpanelClientId).decode('utf-8')
48+
49+
payload = [
50+
{
51+
"event": "ModuleTutorialDeploymentComplete",
52+
"properties": {
53+
"token": tok,
54+
"distinct_id": github_username,
55+
"github_username": github_username,
56+
"$insert_id": uuid.uuid4().hex
57+
}
58+
}]
59+
60+
headers = {
61+
"accept": "text/plain",
62+
"content-type": "application/json"
63+
}
64+
65+
httprequest = Request(url, headers=headers,
66+
data=json.dumps(payload).encode('utf-8'))
67+
68+
response_object = {}
69+
70+
with urlopen(httprequest) as response:
71+
text = ""
72+
mixpanel_response_text = response.read().decode()
73+
if mixpanel_response_text == "1":
74+
text = "Success"
75+
else:
76+
text = "Unknown error"
77+
78+
response_object["operation_status"] = text
79+
response_object["statusCode"] = response.status
80+
response_object["body"] = f"Hello, {github_username}, from Gruntwork!"
81+
82+
return response_object
83+
`
84+
const [codeSnippet, setCodeSnippet] = useState("")
85+
86+
const produceCodeSnippet = (githubUsername: string): string => {
87+
return codeBlockSnippet
88+
.replace("%github_username%", githubUsername)
89+
.replace("%mixpanel_project_id%", base64data)
90+
}
91+
92+
useEffect(() => {
93+
setGithubUsername(getGitHubUsernameFromQueryString())
94+
}, [])
95+
96+
useEffect(() => {
97+
setCodeSnippet(produceCodeSnippet(githubUsername))
98+
}, [githubUsername])
99+
100+
return <CodeBlock language="python">{codeSnippet}</CodeBlock>
101+
}
102+
103+
export default ModuleTutorialCodeBlock

0 commit comments

Comments
 (0)