Skip to content

Commit 4825951

Browse files
author
Luka Fontanilla
committed
feat: first commit of app template
1 parent aa3b3b0 commit 4825951

Some content is hidden

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

57 files changed

+24360
-1
lines changed

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/server/node_modules
6+
/.pnp
7+
.pnp.js
8+
9+
# testing
10+
/coverage
11+
12+
# production
13+
/build
14+
15+
# misc
16+
.DS_Store
17+
.env
18+
.env.local
19+
.env.development.local
20+
.env.test.local
21+
.env.production.local
22+
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*

.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"semi": false,
3+
"singleQuote": true,
4+
"trailingComma": "none"
5+
}

.vscode/launch.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "chrome",
9+
"request": "launch",
10+
"name": "Launch Chrome against localhost",
11+
"runtimeExecutable": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
12+
"url": "http://localhost:3000",
13+
"webRoot": "${workspaceFolder}"
14+
}
15+
]
16+
}

.vscode/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"editor.tabSize": 2,
3+
"editor.insertSpaces": true,
4+
"editor.detectIndentation": false,
5+
"editor.codeActionsOnSave": {
6+
"source.fixAll.eslint": "explicit"
7+
},
8+
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
9+
"editor.formatOnType": false, // required
10+
"editor.formatOnPaste": true, // optional
11+
"editor.formatOnSave": true, // optional
12+
"editor.formatOnSaveMode": "file", // required to format on save
13+
"files.autoSave": "onFocusChange", // optional but recommended
14+
"vs-code-prettier-eslint.prettierLast": false // set as "true" to run 'prettier' last not first
15+
}

README.md

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,173 @@
1-
# looker-embedded-analytics-app-template
1+
# Looker Application Template React
2+
3+
## Introduction
4+
5+
The goal of this repository is to showcase how to implement a simple Powered By Looker app, using the Looker SDKs, React and Node. The app is able to display data from a Looker instance using two different methods:
6+
7+
- Embedding a looker dashboard using [Looker Embed SDK](https://developers.looker.com/embed/embed-sdk)
8+
- Using [Looker SDK](https://www.npmjs.com/package/@looker/sdk) to send custom queries to your Looker instance, and display results using custom visualizations
9+
10+
## Architecture
11+
12+
The app is split into 2 main components:
13+
14+
- A React frontend, depending on both Looker SDKs.
15+
- A Node.js backend, serving the front-end and handling authentication requests to Looker.
16+
17+
The architecture is shown in the following picture:
18+
19+
![Architecture](./architecture.png)
20+
21+
### Front-end
22+
23+
By default, the front-end is authenticated using [Google Sign In](https://developers.google.com/identity/gsi/web/guides/overview). The `/login` page displays both the Sign In button and the Google One Tap prompt. For example purposes, this authentication is **local** to the browser. A real-world implementation should of course verify Google's JWT token and send it to the back-end, to create a session for the user and a Looker embed user with the appropriate permissions for their account.
24+
25+
Once logged in, the front-end is able to use the Looker Embed SDK to embed a Looker dashboard (or an explore, or custom view, etc.). The embed SDK is initialized with the URL of the Looker instance, and the backend routes to call to obtain the necessary access token for embedding. See `SimpleDashboard.jsx` for usage.
26+
27+
```js
28+
LookerEmbedSDK.initCookieless(
29+
lookerConfig.host,
30+
'/api/acquire-embed-session',
31+
'/api/generate-embed-tokens'
32+
)
33+
```
34+
35+
The app also uses the Looker SDK, to be able to access raw data from the Looker instance using, predefined looks or custom queries. In this sample app, we use custom queries the System Activity models, retrieve the data as JSON, and display it using [Charts.js](https://www.chartjs.org/). See `NativeDashboard.jsx` for usage.
36+
37+
```js
38+
sdk.run_inline_query({
39+
result_format: 'json',
40+
limit: 500,
41+
body: {
42+
model: 'system__activity',
43+
view: 'api_usage',
44+
fields: ['api_usage.total_usage', 'api_usage.api_query_type'],
45+
sorts: ['api_usage.total_usage desc 0']
46+
}
47+
})
48+
```
49+
50+
The SDK is initialized using a custom `ProxySession`, which retrieves a Looker API token from our back-end to authenticate all calls.
51+
52+
### Back-end
53+
54+
The back-end holds the credentials (`client_id` and `client_secret`) to the Looker instance, as well as the embed user configuration. It also depends on the Node version of the Looker SDK, to facilitate all API calls.
55+
56+
#### User authentication & Security
57+
58+
All endpoints are un-authenticated in this sample implementation. By default, iframes embedded with the Embed SDK will have the permissions of the hardcoded `user.json` embed user specification, located in the server folder.
59+
The session used by the Looker SDK for the Native Dashboard page has **admin rights**, since it's created using `client_id` and `client_secret`. This allows use to display data from the "System Activity" special models.
60+
61+
For a production implementation, we recommend to implement at least following measures:
62+
63+
- **Back-end authentication**: pass the results of GIS authentication calls through your back-end and use it as a basis to determine which Looker permissions/models the user should have access to
64+
- **Protecting endpoints**: generate a token that uniquely identify the user in the authentication step, and protect every call to the back-end (except `/login`)
65+
- **Embed user creation**: upon the first login, create a Looker embed user with the permissions and properties (name, email, etc.) matching the user who logged through GIS. Re-use this embed user for subsequent logins
66+
- **Looker SDK sessions**: in your back-end, always use the session created with your Client ID and Client Secret, but **never return it to the user**. Create a 'sudo session' to return to the client using [ `session.login()` with an Embed User ID](https://github.com/looker-open-source/sdk-codegen/blob/main/packages/sdk-node/src/nodeSession.ts#L118)
67+
- **Embed sessions**: instead of the hardcoded user.json file, always use a dynamic user object with the appropriate permissions
68+
69+
#### Endpoints
70+
71+
On the `/login` endpoint, the back-end uses the credentials to acquire (or re-use) a Looker Session, and returns an API Key.
72+
73+
The `/acquire-embed-session` endpoint is called by the Looker Embed SDK before loading the iframe. It uses the current Looker session to acquire an **embed cookieless** session. See [Cookieless embedding](https://cloud.google.com/looker/docs/cookieless-embed) for details.
74+
75+
The last endpoint, `generate-embed-tokens`, is called periodically by the embed SDK to keep its cookieless session up to date.
76+
77+
## Running locally
78+
79+
To be able to run this app locally, you will need:
80+
81+
- `npm`
82+
- Access to your Looker instance
83+
- Looker API credentials (`client_id` and `client_secret`)
84+
- Optionally, a [Google API client ID](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid) if you want your own Google Sign In.
85+
86+
### Installation
87+
88+
Install all dependencies with the following:
89+
90+
```sh
91+
npm install
92+
cd server/
93+
npm install
94+
```
95+
96+
### Configuration
97+
98+
The front and back ends both needs a working configuration before running. They expect the following environment variables to be defined:
99+
100+
```sh
101+
102+
# URLs to your Looker instance
103+
REACT_APP_LOOKER_HOST=yourinstance.looker.com
104+
REACT_APP_LOOKER_API_URL=https://yourinstance.looker.com
105+
106+
# Looker API Credentials
107+
REACT_APP_LOOKER_CLIENT_ID=YOUR_CLIENT_ID
108+
REACT_APP_LOOKER_CLIENT_SECRET=YOUR_CLIENT_SECRET
109+
110+
# (Optional) URL to your back-end (by default, the Node back-end running in the `server` folder)
111+
REACT_APP_LOOKER_PROXY_URL=http://localhost:8080/api
112+
113+
# ID of the dashboard embedded on the dashboard page (make sure your embed user has access to it)
114+
REACT_APP_LOOKER_DASHBOARD=1234
115+
116+
# ID of the Explore embedded on the Self Service page
117+
REACT_APP_LOOKER_EXPLORE=model::explore
118+
119+
# Enable Google Sign-in authentication
120+
REACT_APP_ENABLE_GSI_AUTH=false
121+
# (Optional) Client ID used for Google sign-in
122+
REACT_APP_GSI_CLIENT_ID=myid.sapps.googleusercontent.com
123+
```
124+
125+
This can also be achieved by filling the `sample-env.env` provided, and renaming it to `.env`.
126+
127+
The back-end also uses a static JSON file to configure its Looker session, `server/user.json`:
128+
129+
```json
130+
{
131+
"external_user_id": "1234",
132+
"first_name": "John",
133+
"last_name": "Doe",
134+
"session_length": 3600,
135+
"force_logout_login": true,
136+
"permissions": ["access_data", "see_looks", "see_user_dashboards", "explore"],
137+
"models": ["yourmodel"],
138+
"user_attributes": { "locale": "en_US" }
139+
}
140+
```
141+
142+
This should be customized to point to an external embed user that exists on your Looker instance, along with a model and permissions they are able to access.
143+
144+
### Running
145+
146+
Single command to both build the app and serve it on `localhost:8080`:
147+
148+
```sh
149+
npm run server
150+
```
151+
152+
Running the front-end and back-end separately (i.e. to work on the front-end and benefit from hot-reloading). Front:
153+
154+
```sh
155+
npm run start
156+
```
157+
158+
and back:
159+
160+
```sh
161+
cd server
162+
npm start
163+
```
164+
165+
This will have the front-end running on `localhost:3000`.
166+
167+
## Deployment
168+
169+
Automated deployments are out of the scope of this starter kit, but there are several ways of deploying using Google Cloud:
170+
171+
- **Google Cloud Storage / Cloud Run**: Cloud Storage can serve the static files of the React App, while a container of the back-end runs inside Cloud Run
172+
- **Google App Engine**: also capable of serving the static files easily, while also managing basic load-balancing and scaling for the back-end
173+
- **Google Kubernetes Engine**: will provide even more control on the deployment and its infrastructure, at the cost of more configuration

architecture.png

59.1 KB
Loading

jsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "src"
4+
},
5+
"include": [
6+
"src"
7+
]
8+
}

0 commit comments

Comments
 (0)