diff --git a/backend/.env.example b/backend/.env.example index aaed501..09007ba 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -3,7 +3,7 @@ PBL_PORT=3000 LOOKERSDK_API_VERSION=4.0 -LOOKERSDK_BASE_URL=https://your-instance.looker.com +LOOKERSDK_BASE_URL=https://your-instance.looker.com:19999 LOOKERSDK_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxx LOOKERSDK_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxx # no protocol for this host diff --git a/backend/config.js b/backend/config.js index 97d799e..6df82b5 100644 --- a/backend/config.js +++ b/backend/config.js @@ -29,7 +29,9 @@ config.authenticatedUser = "embed_browse_spaces" ], "models": ["reference_implementation"], + // "models": ["atom_fashion"], "user_attributes": { "locale": "en_US" } + // "user_attributes": { "brand":"Calvin Klein","locale": "en_US" } }, user2: { "external_user_id": "user2", @@ -39,12 +41,15 @@ config.authenticatedUser = "force_logout_login": true, "external_group_id": "group2", "group_ids": [], + //user2 has reduced permissions "permissions": [ "access_data", "see_looks", "see_user_dashboards" ], "models": ["reference_implementation"], + // "models": ["atom_fashion"], + //user2 will be localized into a different language "user_attributes": { "locale": "es_US" } } } diff --git a/backend/package.json b/backend/package.json index 24c7dc8..cf2486e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,6 +7,7 @@ "start": "node ./bin/www" }, "dependencies": { + "@looker/sdk-node": "^21.0.9", "cookie-parser": "~1.4.4", "cors": "^2.8.5", "debug": "~2.6.9", diff --git a/backend/routes/api.js b/backend/routes/api.js index 500bb7e..e0fb7fa 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -1,10 +1,10 @@ var express = require('express') var config = require('../config') var router = express.Router() -var NodeAPI = require('@looker/sdk/lib/node') -var NodeSettings = require('@looker/sdk-rtl/lib/NodeSettings') -var createSignedUrl = require('../auth/auth_utils') +const { LookerNodeSDK } = require('@looker/sdk-node') +const sdk = LookerNodeSDK.init40() +var createSignedUrl = require('../auth/auth_utils') /***************************************** * Authentication * @@ -41,7 +41,6 @@ router.post('/embed-user/:id/update', async (req, res) => { * Create a signed URL for embedding content */ router.get('/auth', (req, res) => { - console.log(req.headers.usertoken); const src = req.query.src; const host = process.env.LOOKERSDK_EMBED_HOST // Might need to be different than API baseurl (port nums) const secret = process.env.LOOKERSDK_EMBED_SECRET @@ -55,10 +54,6 @@ router.get('/auth', (req, res) => { * Backend Data API calls * ****************************************/ -// Init the Looker SDK using environment variables -// const sdk = NodeAPI.LookerNodeSDK.init40(new NodeSettings.NodeSettings()); -const sdk = NodeAPI.LookerNodeSDK.init40(); - /** * Get details of the current authenticated user */ @@ -97,9 +92,4 @@ router.get('/looks/:id', async (req, res, next) => { res.send(newQueryResults) }); - - -// test endpoint -router.get('/test', (req, res, next) => res.send(config.api.testResponse)) - module.exports = router; diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 6032c0f..1248bef 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -1,10 +1,11 @@ - REACT INDEX + Ref Implementation
-
Acme Corp
-
Basics
-
Advanced
- +
+
+ diff --git a/frontend/package.json b/frontend/package.json index e2f6c1c..1ac99d9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,8 +8,8 @@ "dependencies": { "@looker/components": "^0.9.16", "@looker/embed-sdk": "^1.5.1", - "@looker/sdk": "^0.3.7-beta.4", - "@looker/sdk-rtl": "^0.3.7-beta.4", + "@looker/sdk": "^21.0.9", + "@looker/sdk-rtl": "^21.0.9", "d3-force": "^2.1.1", "d3-scale": "^3.2.3", "d3-scale-chromatic": "^2.0.0", diff --git a/frontend/src/App.js b/frontend/src/App.js index 9fafc00..d44c4d9 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -7,6 +7,8 @@ import CorsExample from './components/CorsExample' import DashboardExternalFilters from './components/DashboardExternalFilters' import D3CustomVis from './components/D3CustomVis' import EmbedTwoInstances from './components/EmbedTwoInstances' +import EmbedExplore from './components/EmbedExplore' +import DashFilters from './components/DashFilters' import { BrowserRouter as Router, @@ -53,6 +55,14 @@ const params = { { text: 'Embed Two', url: '/examples/embed-two' + }, + { + text: 'Embed Explore', + url: '/examples/embed-explore' + }, + { + text: 'Dashboard w External Filters', + url: '/examples/dash-filters' } ] } @@ -91,6 +101,8 @@ function App() { + + ) diff --git a/frontend/src/components/ApiDataBackend/APIData.js b/frontend/src/components/ApiDataBackend/APIData.js index 0188d37..ef5751d 100644 --- a/frontend/src/components/ApiDataBackend/APIData.js +++ b/frontend/src/components/ApiDataBackend/APIData.js @@ -139,9 +139,9 @@ const FetchedLooks = (props) => { /** * Render the main page component */ -const APIData = () => { +const APIData = () => { const [user, setUser] = useState({}); - const fetchUser = () => { + const fetchUser = () => { fetch("/api/me") .then((res) => res.json()) .then((user) => setUser(user)); diff --git a/frontend/src/components/CorsExample/CorsExampleComp.js b/frontend/src/components/CorsExample/CorsExampleComp.js index 5bc61f2..8165c9e 100644 --- a/frontend/src/components/CorsExample/CorsExampleComp.js +++ b/frontend/src/components/CorsExample/CorsExampleComp.js @@ -1,14 +1,21 @@ -import React from 'react' +import React, {useEffect, useState} from 'react' import { sdk } from "../../helpers/CorsSessionHelper" +const CorsExampleComp = () => { + const [user, setUser] = useState(''); + useEffect(() => { + const apiCall = async () => { + const response = await sdk.ok(sdk.me()) + setUser(response.display_name) + } + apiCall() + },[]); -const CorsExampleComp = () => { - const me = sdk.ok(sdk.me()) return ( <> -
- { me.display_name } +
+ {user}
) diff --git a/frontend/src/components/DashFilters/Embed.js b/frontend/src/components/DashFilters/Embed.js new file mode 100644 index 0000000..07d358e --- /dev/null +++ b/frontend/src/components/DashFilters/Embed.js @@ -0,0 +1,100 @@ +import React, { useCallback, useState } from 'react' +import styled from "styled-components" +import { LookerEmbedSDK } from '@looker/embed-sdk' +import { Box,CheckboxGroup } from '@looker/components' + +const Embed = () => { + const [filterState, setFilterState] = useState({}); + const [dashboard, setDashboard] = useState(dashboard); + const setupDashboard = (dash) => { + setDashboard(dash) + console.log(dashboard) + } + + const updateDashboardFilters = (filters) => { + if (dashboard) { + dashboard.updateFilters({ "Status": filters.filter((el) => el != '').join(',')}) + dashboard.run() + } + } + + const mangageFilterState = (event) => { + console.log(event.dashboard.dashboard_filters) + } + + + const makeDashboard = useCallback((el) => { + if (el) { + el.innerHTML = '' + LookerEmbedSDK.init( + process.env.LOOKERSDK_EMBED_HOST, + { + url: '/api/auth' + ,headers: [ + { name: 'usertoken', value: 'user1' } + ] + } + ) + LookerEmbedSDK.createDashboardWithId(20) + .appendTo(el) + .withNext() + .on('dashboard:loaded',mangageFilterState) + .build() + .connect() + .then(setupDashboard) + .catch((error) => { + console.error('An unexpected error occurred', error) + }) + } +}, []) + +const Status = [ + { + label: 'Cancelled', + value: 'Cancelled', + }, + { + label: 'Complete', + value: 'Complete', + }, + { + label: 'Processing', + value: 'Processing', + }, + { + label: 'Returned', + value: 'Returned', + }, + { + label: 'Shipped', + value: 'Shipped', + }, +] + + + return ( + <> + + {updateDashboardFilters(e)}} + /> + + + + + ) +} + +const Dashboard = styled.div` + width: 100%; + height: 95vh; + & > iframe { + width: 100%; + height: 100%; + } +` +export default Embed \ No newline at end of file diff --git a/frontend/src/components/DashFilters/index.js b/frontend/src/components/DashFilters/index.js new file mode 100644 index 0000000..544a453 --- /dev/null +++ b/frontend/src/components/DashFilters/index.js @@ -0,0 +1,22 @@ +import { + useRouteMatch, + Switch, + Route +} from "react-router-dom"; +import * as React from 'react' +import Embed from './Embed' + +import { ComponentsProvider } from '@looker/components' + +const DashFilters = (() => { + let match = useRouteMatch(); + return( + + + + + + ) +}) + +export default DashFilters \ No newline at end of file diff --git a/frontend/src/components/DashboardExternalFilters/DashboardExternalFiltersComponent.js b/frontend/src/components/DashboardExternalFilters/DashboardExternalFiltersComponent.js index b49bc95..d717be4 100644 --- a/frontend/src/components/DashboardExternalFilters/DashboardExternalFiltersComponent.js +++ b/frontend/src/components/DashboardExternalFilters/DashboardExternalFiltersComponent.js @@ -31,7 +31,7 @@ const Dashboard = styled.div` } `; -LookerEmbedSDK.init(process.env.LOOKER_HOST, "/api/auth"); +LookerEmbedSDK.init(process.env.LOOKER_HOST, { url: '/api/auth' ,headers: [{ name: 'usertoken', value: 'user1' } ]}); /** * Show a message bar at the top of the page diff --git a/frontend/src/components/EmbedExplore/Embed.js b/frontend/src/components/EmbedExplore/Embed.js new file mode 100644 index 0000000..5450e35 --- /dev/null +++ b/frontend/src/components/EmbedExplore/Embed.js @@ -0,0 +1,45 @@ +import React, { useCallback } from 'react' +import styled from "styled-components" +import { LookerEmbedSDK } from '@looker/embed-sdk' + +/** + * First initialized the embed sdk using the endpoint in /backend/routes/api.js + * Gets look with ID, can be found in the url by viewing the look via your looker instance */ + + +const Embed = () => { + const LookDiv = useCallback((el) => { + if (el) { + el.innerHTML = '' + LookerEmbedSDK.init( + process.env.LOOKERSDK_EMBED_HOST, + { url: '/api/auth' ,headers: [{ name: 'usertoken', value: 'user1' } ]} + ) + + LookerEmbedSDK.createExploreWithId('reference_implementation::order_items') + .appendTo(el) + .build() + .connect() + .catch((error) => { + console.error('An unexpected error occurred', error) + }) + }},[]) + return ( + <> +
+ +
+ + ) +} + +const Look = styled.div` + width: 100%; + height: 95vh; + & > iframe { + width: 100%; + height: 100%; + } +` + +export default Embed \ No newline at end of file diff --git a/frontend/src/components/EmbedExplore/index.js b/frontend/src/components/EmbedExplore/index.js new file mode 100644 index 0000000..bd1c3e8 --- /dev/null +++ b/frontend/src/components/EmbedExplore/index.js @@ -0,0 +1,18 @@ +import { + useRouteMatch, + Switch, + Route +} from "react-router-dom"; +import * as React from 'react' +import Embed from './Embed' + +const EmbedExplore = (() => { + let match = useRouteMatch(); + return( + + + + ) +}) + +export default EmbedExplore \ No newline at end of file diff --git a/frontend/src/components/EmbedSDK/Embed.js b/frontend/src/components/EmbedSDK/Embed.js index f4973e6..e821e68 100644 --- a/frontend/src/components/EmbedSDK/Embed.js +++ b/frontend/src/components/EmbedSDK/Embed.js @@ -9,7 +9,6 @@ const Embed = () => { /* Step 1) call init() pointing the SDK to a looker host, a service to get the iframe URLs from, and passing user identifying information in the header no call to the auth service is made at this step - (it assumes you will generally be embedding one Looker host per page, and so these settings are global to the page. For configuring multiple hosts on the same page see: https://github.com/llooker/data_application_reference_implementation/blob/main/frontend/src/components/EmbedTwoInstances/Embed.js ) */ LookerEmbedSDK.init( process.env.LOOKERSDK_EMBED_HOST, @@ -18,7 +17,7 @@ const Embed = () => { url: '/api/auth' ,headers: [ //include some factor which your auth service can use to uniquely identify a user, so that a user specific url can be returned. This could be a token or ID - { name: 'usertoken', value: 'user2' } + { name: 'usertoken', value: 'user1' } ] } ) @@ -43,6 +42,7 @@ const Embed = () => { } }, []) + return ( <> {/* Step 0) we have a simple container, which performs a callback to our makeDashboard function */} diff --git a/frontend/src/components/EmbedTwoInstances/Embed.js b/frontend/src/components/EmbedTwoInstances/Embed.js index 6d34bb7..c8772b5 100644 --- a/frontend/src/components/EmbedTwoInstances/Embed.js +++ b/frontend/src/components/EmbedTwoInstances/Embed.js @@ -1,46 +1,64 @@ -import React from 'react' +import React, { useCallback } from 'react' import styled from "styled-components" -import { LookerEmbedSDK } from '@looker/embed-sdk' +//Alias an additional import of the embed sdk +import { LookerEmbedSDK, LookerEmbedSDK as LookerEmbedSDK2 } from '@looker/embed-sdk' -// create iframe of dashboard -const DashboardDiv = (el) => { - LookerEmbedSDK.init(process.env.LOOKERSDK_EMBED_HOST, '/api/auth') +const Embed = () => { + const makeDashboard = useCallback((el) => { + if (el) { + el.innerHTML = '' + LookerEmbedSDK.init( + process.env.LOOKERSDK_EMBED_HOST, + { + url: '/api/auth' + ,headers: [ + { name: 'usertoken', value: 'user2' } + //Pass a value to your backend service to indicate which host to form a URL for + ,{ name: 'host', value: 'host1' } + ] + } + ) + LookerEmbedSDK.createDashboardWithId(20) + .appendTo(el) + .withNext() + .on('dashboard:loaded',(e)=>{alert('Successfully Loaded!')}) + .build() + .connect() + .catch((error) => { + console.error('An unexpected error occurred', error) + }) + } +}, []) - LookerEmbedSDK.createDashboardWithId(1) +const makeDashboard2 = useCallback((el) => { + if (el) { + el.innerHTML = '' + LookerEmbedSDK2.init( + process.env.LOOKERSDK_EMBED_HOST, + { + url: '/api/auth' + ,headers: [ + { name: 'usertoken', value: 'user1' } + //Pass a value to your backend service to indicate which host to form a URL for + ,{ name: 'host', value: 'host2' } + ] + } + ) + LookerEmbedSDK2.createDashboardWithId(1) .appendTo(el) .withNext() + .on('dashboard:loaded',(e)=>{alert('Successfully Loaded!')}) .build() .connect() - .then(() => DashboardDiv2()) .catch((error) => { console.error('An unexpected error occurred', error) }) } - -const DashboardDiv2 = () => { - fetch(`http://localhost:4000/api/auth?src=${encodeURI('/embed/dashboards-next/1?embed_domain=http://localhost:3001&sdk=2')}`) - .then(response => response.json()) - .then(data => DashboardDiv3(data.url)) -} - -const DashboardDiv3 = (url) => { - LookerEmbedSDK.createDashboardWithUrl(url) - .appendTo('#dashboard2') - .withNext() - .build() - .connect() - .catch((error) => { - console.error('An unexpected error occurred', error) - }) -} - -const Embed = () => { +}, []) return ( <> -
- - -
+ + ) } @@ -53,5 +71,4 @@ const Dashboard = styled.div` height: 100%; } ` - export default Embed \ No newline at end of file diff --git a/frontend/src/components/EmbedWithApi/EmbedApi.js b/frontend/src/components/EmbedWithApi/EmbedApi.js index 08d1644..ab270f4 100644 --- a/frontend/src/components/EmbedWithApi/EmbedApi.js +++ b/frontend/src/components/EmbedWithApi/EmbedApi.js @@ -12,7 +12,7 @@ function EmbedApi() { * Gets dashboard with ID 13, can be found in the url by going to the dashboard in your looker instance */ const DashboardDiv = () => { - LookerEmbedSDK.init(process.env.LOOKER_HOST, '/api/auth') + LookerEmbedSDK.init(process.env.LOOKER_HOST, { url: '/api/auth' ,headers: [{ name: 'usertoken', value: 'user1' } ]}) LookerEmbedSDK.createDashboardWithId(13) .appendTo('#lookerdashboard') diff --git a/package.json b/package.json index b6d7dd8..f01a371 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ "concurrently": "^5.3.0" }, "scripts": { - "frontend": "yarn workspace frontend dev", - "build": "yarn workspace frontend build", - "backend": "yarn workspace backend dev", - "dev": "concurrently --kill-others-on-fail \"yarn backend\" \"yarn frontend\"", - "start": "yarn build && yarn workspace backend start" + "frontend": "yarn workspace frontend dev", + "build": "yarn workspace frontend build", + "backend": "yarn workspace backend dev", + "dev": "concurrently --kill-others-on-fail \"yarn backend\" \"yarn frontend\"", + "start": "yarn build && yarn workspace backend start" } }