Skip to content

Commit cb53e1b

Browse files
committed
feat: Extension framework firebase example
And integration with Looker google workspace auth. Extension framework SDK updated to get google access token and to allow scopes to be reauthorized if necessary.
1 parent 66e7013 commit cb53e1b

19 files changed

+1651
-5
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"dev:xapix": "yarn workspace @looker/extension-api-explorer develop",
4545
"dev:playground": "yarn workspace @looker/extension-playground develop",
4646
"dev:tile": "yarn workspace @looker/extension-tile-playground develop",
47+
"dev:firestore": "yarn workspace @looker/extension-firestore develop",
4748
"clean": "rm -Rf packages/*/lib",
4849
"prepublishOnly": "jest packages/sdk-rtl packages/sdk-node/test",
4950
"fix": "yarn lint:es --fix",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
dist
3+
.cache
4+
.vscode
5+
.env
6+
.eslintcache
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Looker Extension Firestore Demo (React & Typescript)
2+
3+
TODO - add firebase setup
4+
5+
This repository demonstrate using firestore in a Looker extension using Typescript.
6+
7+
It uses [React](https://reactjs.org/) and [Typescript](https://typescriptlang.org) for writing your extension, the [React Extension SDK](https://github.com/looker-open-source/sdk-codegen/tree/main/packages/extension-sdk-react) for interacting with Looker, [Looker Components](https://components.looker.com) for UI, and [Webpack](https://webpack.js.org/) for building your code.
8+
9+
## Getting Started for Development
10+
11+
1. Install the dependencies with [Yarn](https://yarnpkg.com/).
12+
13+
```
14+
yarn install
15+
```
16+
17+
2. Start the development server
18+
19+
```
20+
yarn develop
21+
```
22+
23+
The extension is now running and serving the JavaScript locally at https://localhost:8080/dist/bundle.js.
24+
25+
3. Log in to Looker and create a new project.
26+
27+
This is found under **Develop** => **Manage LookML Projects** => **New LookML Project**.
28+
29+
Select "Blank Project" as your "Starting Point". This will create a new project with no files.
30+
31+
1. The extension folder has a `manifest.lkml` file.
32+
33+
Either drag & upload this file into your Looker project, or create a `manifest.lkml` with the same content. Change the `id`, `label`, or `url` as needed.
34+
35+
```
36+
project_name: "firestore"
37+
application: firestore {
38+
label: "Firestore Example"
39+
url: "https://localhost:8080/dist/bundle.js"
40+
entitlements: {
41+
external_api_urls : ["https://firestore.googleapis.com","https://identitytoolkit.googleapis.com","https://securetoken.googleapis.com"]
42+
google_api_scopes: ["https://www.googleapis.com/auth/datastore","https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/firebase.database"]
43+
scoped_user_attributes: ["firebase_config"]
44+
}
45+
}
46+
```
47+
48+
4. Create a `model` LookML file in your project. The name doesn't matter but the convention is to name it the same as the project— in this case, helloworld-js.
49+
50+
- Add a connection in this model.
51+
- [Configure the model you created](https://docs.looker.com/data-modeling/getting-started/create-projects#configuring_a_model) so that it has access to the selected connection.
52+
We do this because Looker permissions data access via models— In order to grant / limit access to an extension, it must be associated with a model.
53+
54+
5. Connect the project to Git. This can be done in multiple ways:
55+
56+
- Create a new repository on GitHub or a similar service, and follow the instructions to [connect your project to Git](https://docs.looker.com/data-modeling/getting-started/setting-up-git-connection)
57+
- A simpler but less powerful approach is to set up git with the "Bare" repository option which does not require connecting to an external Git Service.
58+
59+
6. Commit the changes and deploy them to production through the Project UI.
60+
61+
7. Reload the page and click the `Browse` dropdown menu. You will see the extension in the list.
62+
63+
- The extension will load the JavaScript from the `url` provided in the `application` definition. By default, this is http://localhost:8080/bundle.js. If you change the port your server runs on in the package.json, you will need to also update it in the manifest.lkml.
64+
- Refreshing the extension page will bring in any new code changes from the extension template, although some changes will hot reload.
65+
66+
## Deployment
67+
68+
The process above describes how to run the extension for development. Once you're done developing and ready to deploy, the production version of the extension may be deployed as follows:
69+
70+
1. In the extension project directory build the extension by running `yarn build`.
71+
2. Drag and drop the generated `dist/bundle.js` file into the Looker project interface
72+
3. Modify the `manifest.lkml` to use `file` instead of `url`:
73+
74+
```
75+
project_name: "firestore"
76+
application: firestore {
77+
label: "Firestore Example"
78+
file: "bundle.js
79+
entitlements: {
80+
external_api_urls : ["https://firestore.googleapis.com","https://identitytoolkit.googleapis.com","https://securetoken.googleapis.com"]
81+
google_api_scopes: ["https://www.googleapis.com/auth/datastore","https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/firebase.database"]
82+
scoped_user_attributes: ["firebase_config"]
83+
}
84+
}
85+
```
86+
87+
## Related Projects
88+
89+
- [Looker Extension SDK React](https://github.com/looker-open-source/sdk-codegen/tree/main/packages/extension-sdk-react)
90+
- [Looker Extension SDK](https://github.com/looker-open-source/sdk-codegen/tree/main/packages/extension-sdk)
91+
- [Looker SDK](https://github.com/looker-open-source/sdk-codegen/tree/main/packages/sdk)
92+
- [Looker Embed SDK](https://github.com/looker-open-source/embed-sdk)
93+
- [Looker Components](https://components.looker.com/)
94+
- [Styled components](https://www.styled-components.com/docs)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
project_name: "firestore"
2+
3+
application: firestore {
4+
label: "Firestore Example"
5+
url: "https://localhost:8080/dist/bundle.js"
6+
# file: "bundle.js
7+
entitlements: {
8+
external_api_urls : ["https://firestore.googleapis.com","https://identitytoolkit.googleapis.com","https://securetoken.googleapis.com"]
9+
google_api_scopes: ["https://www.googleapis.com/auth/datastore","https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/firebase.database"]
10+
scoped_user_attributes: ["firebase_config"]
11+
}
12+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "@looker/extension-firestore",
3+
"version": "1.1.0",
4+
"description": "Looker Extension SDK Firestore Demo",
5+
"main": "dist/bundle.js",
6+
"scripts": {
7+
"analyze": "export ANALYZE_MODE=static && yarn bundle",
8+
"bundle": "tsc && webpack --config webpack.prod.config.js",
9+
"develop": "webpack serve --hot --disable-host-check --port 8080 --https --config webpack.dev.config.js"
10+
},
11+
"author": "Looker",
12+
"license": "MIT",
13+
"dependencies": {
14+
"@looker/extension-sdk": "^22.4.2",
15+
"@looker/extension-sdk-react": "22.4.2",
16+
"@looker/sdk": "^22.0.0",
17+
"@looker/sdk-codegen": "^21.3.2",
18+
"@looker/components": "^2.8.1",
19+
"@looker/icons": "^1.5.3",
20+
"@styled-icons/material": "^10.28.0",
21+
"@styled-icons/material-outlined": "^10.28.0",
22+
"@styled-icons/material-rounded": "^10.28.0",
23+
"firebase": "^9.6.11",
24+
"lodash": "^4.17.21",
25+
"react": "^16.14.0",
26+
"react-dom": "^16.14.0",
27+
"react-is": "^16.13.1",
28+
"react-router-dom": "^5.3.0",
29+
"reactfire": "^4.2.1",
30+
"semver": "^7.3.4",
31+
"styled-components": "^5.3.1"
32+
},
33+
"devDependencies": {
34+
"@types/lodash": "^4.14.178",
35+
"@types/redux": "^3.6.0",
36+
"@types/styled-components": "^5.1.7",
37+
"@looker/components-test-utils": "^1.5.5",
38+
"@testing-library/react": "^11.2.2",
39+
"webpack-bundle-analyzer": "^4.2.0",
40+
"webpack-cli": "^4.6.0",
41+
"webpack-dev-server": "^3.11.2",
42+
"webpack-merge": "^5.7.3"
43+
}
44+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2022 Looker Data Sciences, Inc.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
import React, { useState } from 'react'
27+
import { useFirestore, useFirestoreCollectionData } from 'reactfire'
28+
import type { DataTableColumns } from '@looker/components'
29+
import {
30+
Button,
31+
Spinner,
32+
DataTable,
33+
DataTableItem,
34+
DataTableCell,
35+
SpaceVertical,
36+
} from '@looker/components'
37+
import { collection, orderBy, query, addDoc } from 'firebase/firestore'
38+
39+
/**
40+
* Example based upon https://github.com/FirebaseExtended/reactfire/blob/main/example/withoutSuspense/Firestore.tsx
41+
*
42+
* License details:
43+
*
44+
* The MIT License (MIT)
45+
*
46+
* Copyright (c) 2016 Firebase
47+
*
48+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
54+
* SOFTWARE.
55+
*/
56+
export const AnimalsList = () => {
57+
const firestore = useFirestore()
58+
const animalsCollection = collection(firestore, 'animals')
59+
const [isAscending, setIsAscending] = useState(false)
60+
const animalsQuery = query(
61+
animalsCollection,
62+
orderBy('commonName', isAscending ? 'asc' : 'desc')
63+
)
64+
const { status, data: animals } = useFirestoreCollectionData(animalsQuery, {
65+
idField: 'id',
66+
})
67+
68+
const addAnimal = () => {
69+
const possibleAnimals = ['Dog', 'Cat', 'Iguana', 'Zebra']
70+
const selectedAnimal =
71+
possibleAnimals[Math.floor(Math.random() * possibleAnimals.length)]
72+
addDoc(animalsCollection, { commonName: selectedAnimal })
73+
}
74+
75+
if (status === 'loading') {
76+
return <Spinner />
77+
}
78+
79+
const animalColumns: DataTableColumns = [
80+
{
81+
id: 'id',
82+
title: 'ID',
83+
type: 'string',
84+
},
85+
{
86+
id: 'name',
87+
title: 'Name',
88+
type: 'string',
89+
},
90+
]
91+
92+
const animalCountColumns: DataTableColumns = [
93+
{
94+
id: 'name',
95+
title: 'Name',
96+
type: 'string',
97+
},
98+
{
99+
id: 'count',
100+
title: 'Count',
101+
type: 'number',
102+
},
103+
]
104+
105+
return (
106+
<SpaceVertical>
107+
<Button
108+
onClick={() => {
109+
setIsAscending(!isAscending)
110+
}}
111+
>
112+
Sort
113+
</Button>
114+
<DataTable caption="Animals" columns={animalColumns}>
115+
{animals.map(({ id, commonName }) => (
116+
<DataTableItem key={id} id={id}>
117+
<DataTableCell>{id}</DataTableCell>
118+
<DataTableCell>{commonName}</DataTableCell>
119+
</DataTableItem>
120+
))}
121+
</DataTable>
122+
<DataTable caption="Animal Counts" columns={animalCountColumns}>
123+
{Array.from<any>(
124+
animals.reduce<any>((animalCountMap, animal) => {
125+
const currentCount = animalCountMap.get(animal.commonName) ?? 0
126+
return animalCountMap.set(animal.commonName, currentCount + 1)
127+
}, new Map<string, number>())
128+
).map((animalStat: [string, number]) => {
129+
const [animalName, animalCount] = animalStat
130+
return (
131+
<DataTableItem key={animalName} id={animalName}>
132+
<DataTableCell>{animalName}</DataTableCell>
133+
<DataTableCell>{animalCount}</DataTableCell>
134+
</DataTableItem>
135+
)
136+
})}
137+
</DataTable>
138+
<Button
139+
onClick={() => {
140+
addAnimal()
141+
}}
142+
>
143+
Add Animal
144+
</Button>
145+
</SpaceVertical>
146+
)
147+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2022 Looker Data Sciences, Inc.
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
import React from 'react'
27+
import { ComponentsProvider } from '@looker/components'
28+
import { ExtensionProvider40 } from '@looker/extension-sdk-react'
29+
import { AppConfigurator } from './AppConfigurator'
30+
31+
export const App = () => (
32+
<ExtensionProvider40>
33+
<ComponentsProvider>
34+
<AppConfigurator />
35+
</ComponentsProvider>
36+
</ExtensionProvider40>
37+
)

0 commit comments

Comments
 (0)