|
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 | + |
| 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 |
0 commit comments