Skip to content

Commit

Permalink
Merge branch 'main' into jk/judging_md
Browse files Browse the repository at this point in the history
  • Loading branch information
jkaster authored Nov 3, 2021
2 parents 75e6619 + 16528bc commit b52ba6e
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 153 deletions.
19 changes: 10 additions & 9 deletions examples/python/cloud-function-user-provision/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,38 @@

This repository contains a [Google Cloud Function](https://cloud.google.com/functions) that leverages Looker Python SDK. The repository can be used as a starter template to build serverless microservices that interact with Looker through the following workflow:

1. Send a POST request to trigger an HTTP-based Cloud Function
1. Trigger an HTTP-based Cloud Function
2. Initialize the Looker Python SDK
3. Call Looker SDK methods and build custom logic to manage users, content, queries, etc.

In this repository, the `main.py` file takes an email address as an input and checks if this email has been registered with an existing Looker user. If an exisiting user is found, an email to reset the password will be sent to the user. Otherwise, a new user will be created, and a setup email will be sent.
In this repository, the `main.py` file takes an email address as an input and checks if this email has been registered with an existing Looker user. If a current user is found, an email to reset the password will be sent to the user. Otherwise, a new user will be created, and a setup email will be sent.

For more use cases and Python examples, check out [Looker's Python SDK examples](https://github.com/looker-open-source/sdk-codegen/tree/main/examples/python).
Check out [Looker's Python SDK examples](https://github.com/looker-open-source/sdk-codegen/tree/main/examples/python) for more code examples.

## Demo

<p align="center">
<img src="https://storage.googleapis.com/tutorials-img/Cloud%20Function%20Demo%20-%20SD%20480p.gif" alt="Demo">
</p>


## Setup

The following steps assume deployment using Google Cloud UI Console. Check out ["Your First Function: Python"](https://cloud.google.com/functions/docs/first-python) for steps to deploy using the `gcloud` command line tool
The following steps assume deployment using Google Cloud UI Console. Check out ["Your First Function: Python"](https://cloud.google.com/functions/docs/first-python) for steps to deploy using the `gcloud` command-line tool

1. Obtain a [Looker API3 Key](https://docs.looker.com/admin-options/settings/users#api3_keys)

2. Follow the steps [provided here](https://cloud.google.com/functions/docs/quickstart-python) to create a new Google Cloud Function

3. Configure runtime environment variables using the Cloud Function UI: Edit > Configuration > Runtime, build, connections and security settings > Runtime environment variables. Alternatively, environtment variables can be configured through the `os` module or a `.ini`. Check out [Configuring Looker Python SDK](https://github.com/looker-open-source/sdk-codegen/tree/main/python#configuring-the-sdk) to learn more
3. If using Google Sheet: Grant "Viewer" permission to the email address associated with the "Runtime service account" in Cloud Functions. The recommendation is to use the [Default App Engine Service Account](https://cloud.google.com/appengine/docs/standard/python/service-account) and share its email (`YOUR_PROJECT_ID@appspot.gserviceaccount.com`) to the Google Sheet.

4. Configure runtime environment variables using the Cloud Function UI: Edit > Configuration > Runtime, build, connections and security settings > Runtime environment variables. Alternatively, environment variables can be configured through the `os` module or a `.ini` file. Check [Configuring Looker Python SDK](https://github.com/looker-open-source/sdk-codegen/tree/main/python#configuring-the-sdk) for more information

<p align="center">
<img src="https://storage.googleapis.com/tutorials-img/Cloud%20Function_env%20-%20SD%20480p.gif" alt="Setting environmental variables in Cloud Function UI">
</p>

4. Copy and paste the contents of `main.py` in this repository into `main.py` file once inside Cloud Function's inline editor. Change the "Entry point" in the top right to `main`. `main.py` is executed once the function is triggered
5. Copy and paste the contents of `main.py` in this repository into the `main.py` file once inside Cloud Function's inline editor. Change the "Entry point" in the top right to the main function. `main.py` is executed once the function is triggered

5. Copy and paste the contents of `requirements.txt` in this repository to the `requirements.txt` file once inside Cloud Function's inline editor. This file is used to install neccessary libraries to execute the function
6. Copy and paste the contents of `requirements.txt` in this repository to the `requirements.txt` file once inside Cloud Function's inline editor. This file is used to install necessary libraries to execute the function

6. Deploy and test the function. Check out [this article](https://cloud.google.com/functions/docs/quickstart-python#test_the_function) for instruction
7. Deploy and test the function. Check out [this article](https://cloud.google.com/functions/docs/quickstart-python#test_the_function) for instruction
69 changes: 61 additions & 8 deletions examples/python/cloud-function-user-provision/main.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,76 @@
"""This Cloud Function leverages Looker Python SDK to manage user provision. The
`main` function is used as the entry point to the code. It takes an email address
as an input through a POST request, then checks if this email has been associated
with an existing Looker user. If an exisiting user is found, then an email to
reset password will be sent. Otherwise, a new user will be created, and a setup email
will be sent.
"""This Cloud Function leverages Looker Python SDK to manage user provision.
It takes an email address as an input, then checks if this email has been
associated with an existing Looker user. If a current user is found, then an
email to reset the password will be sent. Otherwise, a new user will be created,
and a setup email will be sent.
The `main` function is triggered through an HTTP request. Two example approaches
are provided below:
main(request): take a POST request in form of {"email":"test@test.com"},
and read the email value from the request body
main_gsheet(request): take a GET request and read the email value from a cell
inside an existing Google sheet.
HTTP Cloud Functions: https://cloud.google.com/functions/docs/writing/http#sample_usage"""

# If not using Google Sheet, removing Google modules here and in `requirements.txt`
from googleapiclient.discovery import build
import google.auth

import looker_sdk
sdk = looker_sdk.init40()

# [START main(request)]
def main(request):
"""Take email from JSON body of a POST request, and use the email value
as an input for looker_user_provision() function"""
try:
request_json = request.get_json()
email = request_json["email"]
result = looker_user_provision(email=email)
return result
except:
return 'Please provide JSON in the format of {"email":"test@test.com"}'
# [END main(request)]

# [START main_gsheet(request)]
def main_gsheet(request):
"""Take email from a cell inside an existing Google Sheet"""
try:
email = get_email_from_sheet()
result = looker_user_provision(email=email)
return result
except:
return 'An error occurred.'

def get_email_from_sheet():
""" Authenticate to an existing Google Sheet using the default runtime
service account and extract the email address from a cell inside the sheet.
Refer to Google Sheet API Python Quickstart for details:
https://developers.google.com/sheets/api/quickstart/python
"""
# Get the key of an existing Google Sheet from the URL.
# Example: https://docs.google.com/spreadsheets/d/[KEY HERE]/edit#gid=111
SAMPLE_SPREADSHEET_ID = "foo"

# Google Sheet Range: https://developers.google.com/sheets/api/samples/reading
SAMPLE_RANGE_NAME = "Sheet1!A:A"

creds, _proj_id = google.auth.default()
service = build("sheets", "v4", credentials=creds)
sheet = service.spreadsheets()
result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
range=SAMPLE_RANGE_NAME).execute()

# `values` will be a list of lists (i.e.: [['email1'], ['email2']])
# and we can access value 'email' using index
values = result.get('values', [])
email = values[0][0]
return email
# [END main_gsheet(request)]

# [START looker_user_provision]
def looker_user_provision(email):
user_id = search_users_by_email(email=email)
if user_id is not None:
Expand Down Expand Up @@ -49,15 +101,16 @@ def create_users(email):
models_dir_validated=False
)
)

# Create email credentials for the new user
sdk.create_user_credentials_email(
user_id=new_user.id,
body=looker_sdk.models40.WriteCredentialsEmail(
email=email,
forced_password_reset_at_next_login=False
))

# Send a welcome/setup email
sdk.send_user_credentials_email_password_reset(user_id=new_user["id"])

# [END looker_user_provision]
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Function dependencies, for example:
# package>=version
looker_sdk
requests
google-api-python-client==1.7.9
google-auth-httplib2==0.0.3
google-auth-oauthlib==0.4.0
6 changes: 6 additions & 0 deletions packages/hackathon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ application: hackathon_app {

Remember to add a model to the project that has a valid connection.

## Specific steps to `yarn`
1. run `yarn install` in sdk-codegen
2. run `yarn build` in sdk-codgen
3. run `yarn start` in examples/access_token_server
4. run `yarn dev:hack` in sdk-codegen to start the development server and connect to the extension

## Hackathon Personas

The active user's persona determines the availability of navigation options and data actions.
Expand Down
76 changes: 45 additions & 31 deletions packages/hackathon/src/scenes/HomeScene/agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,146 +48,160 @@ export const agenda = [
start: later(day1, -4), // Nov 9th
// NOTE: if stop values are not defined, they default to the start of the next agenda item
stop: later(day1, -3.5), // Nov 9th
description: { en: '## Welcome APAC', ja_JP: '## 日本 Welcome APAC' },
description: {
en: '## Welcome APAC',
ja_JP: '## 開会 APAC(ご案内・注意事項など)',
},
},
{
start: later(day1, 5), // Nov 10th
stop: later(day1, 5.5), // Nov 10th
description: { en: '## Welcome EMEA', ja_JP: '## 日本 Welcome EMEA' },
description: {
en: '## Welcome EMEA',
ja_JP: '## 開会 EMEA(ご案内・注意事項など)',
},
},
{
start: later(day1, 10),
stop: later(day1, 10.5),
description: { en: '## Welcome AMER', ja_JP: '## 日本 Welcome AMER' },
description: {
en: '## Welcome AMER',
ja_JP: '## 開会 AMER(ご案内・注意事項など)',
},
},
{
start: later(day1, -3.5), // Nov 9th
stop: later(day1, -2),
description: {
en: `_Supported hacking hours_
en: `_Supported hacking hours (ENG/JPN)_
Use [slack](https://app.slack.com/client/T0A4R5X0F/C02ELGL644F) to ask Looker staff for assistance
Use [Slack](https://app.slack.com/client/T0A4R5X0F/C02ELGL644F) to ask Looker staff for assistance
See [looker-open-source](https://github.com/looker-open-source) for cool stuff!`,
ja_JP: `_日本 Supported hacking hours_
ja_JP: `_サポート時間 (対応言語:英・日)_
[Slack](https://app.slack.com/client/T0A4R5X0F/C02ELGL644F)にて日本語での技術サポートを実施しております
Use [slack](https://app.slack.com/client/T0A4R5X0F/C02ELGL644F) to ask Looker staff for assistance`,
活用事例やコードサンプルについて、[looker-open-source](https://github.com/looker-open-source)でご参考いただけます`,
},
},
{
start: later(day1, 1.5), // Nov 10th
stop: later(day1, 3.5),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG/JPN)_',
ja_JP: '_サポート時間 (対応言語:英・日)_',
},
},
{
start: later(day1, 5), // Nov 10th
stop: later(day1, 7),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG)_',
ja_JP: '_サポート時間 (対応言語:英)_',
},
},
{
start: later(day1, 10.5),
stop: later(day1, 13),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG)_',
ja_JP: '_サポート時間 (対応言語:英)_',
},
},
{
start: later(day1, 14.5),
stop: later(day1, 16),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG/JPN)_',
ja_JP: '_サポート時間 (対応言語:英・日)_',
},
},
{
start: later(day1, 19),
description: {
en: '**Hack@Night session**',
ja_JP: '**日本 Hack@Night Session**',
ja_JP: '**Hack@Night Session**', //
},
},
{
start: later(day2, -4),
stop: later(day2, -3.5),
description: {
en: '_Roundtable Check-In APAC_',
ja_JP: '_日本 Roundtable Check-In APAC_',
ja_JP: '_ラウンドテーブル Check-in APAC_',
},
},
{
start: later(day2, 5),
stop: later(day2, 5.5),
description: {
en: '_Roundtable Check-In EMEA_',
ja_JP: '_日本 Roundtable Check-In EMEA_',
ja_JP: '_ラウンドテーブル Check-in EMEA_',
},
},
{
start: later(day2, 10),
stop: later(day2, 10.5),
description: {
en: '_Roundtable Check-In AMER_',
ja_JP: '_日本 Roundtable Check-In AMER_',
ja_JP: '_ラウンドテーブル Check-in AMER_',
},
},
{
start: later(day2, -3.5),
stop: later(day2, -2),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG/JPN)_',
ja_JP: '_サポート時間 (対応言語:英・日)_',
},
},
{
start: later(day2, 1.5),
stop: later(day2, 3.5),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG/JPN)_',
ja_JP: '_サポート時間 (対応言語:英・日)_',
},
},
{
start: later(day2, 5),
stop: later(day2, 7),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG)_',
ja_JP: '_サポート時間 (対応言語:英)_',
},
},
{
start: later(day2, 10.5),
stop: later(day2, 13),
description: {
en: '_Supported hacking hours_',
ja_JP: '_日本 Supported hacking hours_',
en: '_Supported hacking hours (ENG)_',
ja_JP: '_サポート時間 (対応言語:英・日)_',
},
},
{
start: later(day2, 13),
description: {
en: `**Final submissions due**`,
ja_JP: '**日本 Final submissions due**',
ja_JP: '**プロジェクトの提出締め切り**',
},
},
{
start: later(day2, 14),
description: {
en: `## Winner announcements + Demos`,
ja_JP: '## 日本 Winner announcements + Demos',
en: `## Winner announcements & Demos`,
ja_JP: '## 優秀賞発表 & デモ',
},
},
// NOTE: All other stop values can default. The final stop value is required.
{
start: later(day2, 15),
stop: later(day2, 17),
description: { en: `## Hacky Hour`, ja_JP: '## 日本 Hacky Hour' },
description: {
en: `## Hacky Hour (Social Event)`,
ja_JP: '## Hacky Hour (懇親会)',
},
},
]

Expand Down
17 changes: 6 additions & 11 deletions packages/hackathon/src/scenes/ResourceScene/ResourceScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
} from '@looker/components'
import { getExtensionSDK } from '@looker/extension-sdk'
import { Routes } from '../../routes/AppRouter'
import { resources } from './resource_data'
import { resources, ResourceTag } from './resource_data'

interface ResourceSceneProps {}

Expand Down Expand Up @@ -90,16 +90,11 @@ export const ResourceScene: FC<ResourceSceneProps> = () => {
description="Select 1 or more areas"
>
<ButtonGroup value={filterValues} onChange={updateFilterValue}>
<ButtonItem value="embed">Embed</ButtonItem>
<ButtonItem value="extension">Extensions</ButtonItem>
<ButtonItem value="lookml">LookML</ButtonItem>
<ButtonItem value="action">Actions</ButtonItem>
<ButtonItem value="api">API</ButtonItem>
<ButtonItem value="viz">Custom Viz</ButtonItem>
<ButtonItem value="devtool">Dev Tools</ButtonItem>
<ButtonItem value="component">Components</ButtonItem>
<ButtonItem value="dataset">Datasets</ButtonItem>
<ButtonItem value="other">Other</ButtonItem>
{Object.keys(ResourceTag).map((k) => (
<ButtonItem key={k} value={ResourceTag[k]}>
{ResourceTag[k]}
</ButtonItem>
))}
</ButtonGroup>
</Field>
<Grid pt="medium" columns={3}>
Expand Down
Loading

0 comments on commit b52ba6e

Please sign in to comment.