Skip to content

Commit

Permalink
Merge pull request #239 from looker/api-4-upgrade
Browse files Browse the repository at this point in the history
Upgrading Lookerbot tool to use Looker API v4.0
  • Loading branch information
deanlooker authored Oct 13, 2023
2 parents c8984d4 + 2c479b0 commit 607d2ee
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 54 deletions.
8 changes: 4 additions & 4 deletions .env-sample
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
SLACK_API_KEY=fooo-abc123
SLACK_SLASH_COMMAND_TOKEN=abc123
LOOKER_URL=https://me.looker.com
LOOKER_API_BASE_URL=https://me.looker.com:19999/api/3.0
LOOKER_API_3_CLIENT_ID=abcdefghjkl
LOOKER_API_3_CLIENT_SECRET=abcdefghjkl
LOOKER_CUSTOM_COMMAND_SPACE_ID=13
LOOKER_API_BASE_URL=https://me.looker.com:19999/api/4.0
LOOKER_API_CLIENT_ID=abcdefghjkl
LOOKER_API_CLIENT_SECRET=abcdefghjkl
LOOKER_CUSTOM_COMMAND_FOLDER_ID=13
SLACKBOT_S3_BUCKET=my-bucket
SLACKBOT_S3_BUCKET_REGION=us-east-1
AWS_ACCESS_KEY_ID=ABCDEFGHJKL
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Detailed information on how to interact with Lookerbot [can be found in the Look
> By default, Slack Apps are internal to your team. Don't "distribute" your Slack App – that will make it available to all Slack users in the world.

> [!IMPORTANT]
> Please note: some of the Environment Variables below have changed. You may need to adjust them in order to keep this working.
#### Heroku Deployment

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/looker/looker-slackbot/tree/master)
Expand All @@ -72,13 +75,13 @@ The bot is configured entirely via environment variables. You'll want to set up

- `LOOKER_URL` (required) – The web url of your Looker instance.

- `LOOKER_API_BASE_URL` (required) – The API 3.0 endpoint of your Looker instance. In most cases, this will be the web url followed by `:19999/api/3.0` (replace `19999` with your `core_port` if it is different).
- `LOOKER_API_BASE_URL` (required) – The API endpoint of your Looker instance. In most cases, this will be the web url followed by `:19999/api/4.0` (replace `19999` with your `core_port` if it is different).

- `LOOKER_API_3_CLIENT_ID` (required) – The API 3.0 client ID for the user you want the bot to run as. This requires creating an API 3.0 user or an API 3.0 key for an existing user in Looker.
- `LOOKER_API_CLIENT_ID` (required) – The API client ID for the user you want the bot to run as. This requires creating an API user or an API key for an existing user in Looker.

- `LOOKER_API_3_CLIENT_SECRET` (required) – The API 3.0 client secret for the user you want the bot to run as. This requires creating an API 3.0 user or an API 3.0 key for an existing user in Looker.
- `LOOKER_API_CLIENT_SECRET` (required) – The API client secret for the user you want the bot to run as. This requires creating an API user or an API key for an existing user in Looker.

- `LOOKER_CUSTOM_COMMAND_SPACE_ID` (optional) – The ID of a Space that you would like the bot to use to define custom commands. [Read about using custom commands in the Looker Help Center](https://help.looker.com/hc/en-us/articles/360023685434-Using-Lookerbot-for-Slack).
- `LOOKER_CUSTOM_COMMAND_FOLDER_ID` (optional) – The ID of a Folder that you would like the bot to use to define custom commands. [Read about using custom commands in the Looker Help Center](https://help.looker.com/hc/en-us/articles/360023685434-Using-Lookerbot-for-Slack).

- `LOOKER_WEBHOOK_TOKEN` (optional) – The webhook validation token found in Looker's admin panel. This is only required if you're using the bot to send scheduled webhooks.

Expand Down Expand Up @@ -145,19 +148,19 @@ If you would like the bot to connect to multiple instances of Looker, then you c
The JSON objects should have the following keys:

- `url` should be the web url of the instance
- `apiBaseUrl` should be the API 3.0 endpoint
- `clientID` should be the API 3.0 client ID for the user you want the bot to run as
- `clientSecret` should be the secret for that API 3.0 key
- `customCommandSpaceId` is an optional parameter, representing a Space that you would like the bot to use to define custom commands.
- `apiBaseUrl` should be the API endpoint
- `clientID` should be the API client ID for the user you want the bot to run as
- `clientSecret` should be the secret for that API key
- `customCommandFolderId` is an optional parameter, representing a Folder that you would like the bot to use to define custom commands.
- `webhookToken` is an optional parameter. It's the webhook validation token found in Looker's admin panel. This is only required if you're using the bot to send scheduled webhooks.

Here's an example JSON that connects to two Looker instances:

```json
[{"url": "https://me.looker.com", "apiBaseUrl": "https://me.looker.com:19999/api/3.0", "clientId": "abcdefghjkl", "clientSecret": "abcdefghjkl"},{"url": "https://me-staging.looker.com", "apiBaseUrl": "https://me-staging.looker.com:19999/api/3.0", "clientId": "abcdefghjkl", "clientSecret": "abcdefghjkl"}]
[{"url": "https://me.looker.com", "apiBaseUrl": "https://me.looker.com:19999/api/4.0", "clientId": "abcdefghjkl", "clientSecret": "abcdefghjkl"},{"url": "https://me-staging.looker.com", "apiBaseUrl": "https://me-staging.looker.com:19999/api/4.0", "clientId": "abcdefghjkl", "clientSecret": "abcdefghjkl"}]
```

The `LOOKER_URL`, `LOOKER_API_BASE_URL`, `LOOKER_API_3_CLIENT_ID`, `LOOKER_API_3_CLIENT_SECRET`, `LOOKER_WEBHOOK_TOKEN`, and `LOOKER_CUSTOM_COMMAND_SPACE_ID` variables are ignored when `LOOKERS` is set.
The `LOOKER_URL`, `LOOKER_API_BASE_URL`, `LOOKER_API_CLIENT_ID`, `LOOKER_API_CLIENT_SECRET`, `LOOKER_WEBHOOK_TOKEN`, and `LOOKER_CUSTOM_COMMAND_FOLDER_ID` variables are ignored when `LOOKERS` is set.

##### Running the Server

Expand Down
16 changes: 8 additions & 8 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
"value": "https://mycompany.looker.com"
},
"LOOKER_API_BASE_URL": {
"description": "The API 3.0 endpoint of your Looker instance.",
"description": "The API 4.0 endpoint of your Looker instance.",
"required": true,
"value": "https://mycompany.looker.com:19999/api/3.0"
"value": "https://mycompany.looker.com:19999/api/4.0"
},
"LOOKER_API_3_CLIENT_ID": {
"description": "The API 3.0 client ID for the user you want the bot to run as.",
"LOOKER_API_CLIENT_ID": {
"description": "The API client ID for the user you want the bot to run as.",
"required": true
},
"LOOKER_API_3_CLIENT_SECRET": {
"description": "The API 3.0 client secret for the user you want the bot to run as.",
"LOOKER_API_CLIENT_SECRET": {
"description": "The API client secret for the user you want the bot to run as.",
"required": true
},
"LOOKER_CUSTOM_COMMAND_SPACE_ID": {
"description": "The ID of a Space that you would like the bot to use to define custom commands.",
"LOOKER_CUSTOM_COMMAND_FOLDER_ID": {
"description": "The ID of a Folder that you would like the bot to use to define custom commands.",
"required": false
},
"SLACK_SLASH_COMMAND_TOKEN": {
Expand Down
8 changes: 4 additions & 4 deletions src/commands/help_command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ export class HelpCommand extends Command {
title: "Built-in Commands",
})

const spaces = Looker.all.filter((l) => l.customCommandSpaceId).map((l) => {
return `<${l.url}/spaces/${l.customCommandSpaceId}|this space>`
const folders = Looker.all.filter((l) => l.customCommandFolderId).map((l) => {
return `<${l.url}/folders/${l.customCommandFolderId}|this folder>`
}).join(" or ")

if (spaces) {
if (folders) {
helpAttachments.push({
mrkdwn_in: ["text"],
text: `\n_To add your own commands, add a dashboard to ${spaces}._`,
text: `\n_To add your own commands, add a dashboard to ${folders}._`,
})
}

Expand Down
28 changes: 14 additions & 14 deletions src/looker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IDashboard, ISpace } from "./looker_api_types"
import { IDashboard, IFolder } from "./looker_api_types"
import { LookerAPIClient } from "./looker_client"

export interface ICustomCommand {
Expand All @@ -15,7 +15,7 @@ interface ILookerOptions {
apiBaseUrl: string
clientId: string
clientSecret: string
customCommandSpaceId: string
customCommandFolderId: string
url: string
webhookToken: string
}
Expand All @@ -37,24 +37,24 @@ export class Looker {
(console.log("Using Looker information specified in individual environment variables."),
[{
apiBaseUrl: process.env.LOOKER_API_BASE_URL,
clientId: process.env.LOOKER_API_3_CLIENT_ID,
clientSecret: process.env.LOOKER_API_3_CLIENT_SECRET,
customCommandSpaceId: process.env.LOOKER_CUSTOM_COMMAND_SPACE_ID,
clientId: process.env.LOOKER_API_CLIENT_ID,
clientSecret: process.env.LOOKER_API_CLIENT_SECRET,
customCommandFolderId: process.env.LOOKER_CUSTOM_COMMAND_FOLDER_ID,
url: process.env.LOOKER_URL,
webhookToken: process.env.LOOKER_WEBHOOK_TOKEN,
}])
return this.all = configs.map((config) => new Looker(config))
}

public url: string
public customCommandSpaceId: string
public customCommandFolderId: string
public webhookToken: string
public client: LookerAPIClient

constructor(options: ILookerOptions) {

this.url = options.url
this.customCommandSpaceId = options.customCommandSpaceId
this.customCommandFolderId = options.customCommandFolderId
this.webhookToken = options.webhookToken

this.client = new LookerAPIClient({
Expand All @@ -68,25 +68,25 @@ export class Looker {
}

public refreshCommands() {
if (!this.customCommandSpaceId) {
if (!this.customCommandFolderId) {
console.log(`No commands specified for ${this.url}...`)
return
}
console.log(`Refreshing custom commands for ${this.url}...`)

this.client.get(`spaces/${this.customCommandSpaceId}`, (space: ISpace) => {
this.addCommandsForSpace(space, "Shortcuts")
this.client.get(`spaces/${this.customCommandSpaceId}/children`, (children: ISpace[]) => {
this.client.get(`folders/${this.customCommandFolderId}`, (folder: IFolder) => {
this.addCommandsForFolder(folder, "Shortcuts")
this.client.get(`folders/${this.customCommandFolderId}/children`, (children: IFolder[]) => {
children.map((child) =>
this.addCommandsForSpace(child, child.name))
this.addCommandsForFolder(child, child.name))
},
console.log)
},
console.log)
}

private addCommandsForSpace(space: ISpace, category: string) {
space.dashboards.forEach((partialDashboard) =>
private addCommandsForFolder(folder: IFolder, category: string) {
folder.dashboards.forEach((partialDashboard) =>
this.client.get(`dashboards/${partialDashboard.id}`, (dashboard: IDashboard) => {

const command: ICustomCommand = {
Expand Down
16 changes: 8 additions & 8 deletions src/looker_api_types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export interface ISpace {
export interface IFolder {
id: string
name: string
dashboards: IDashboard[]
}

export interface IDashboard {
id: string | number
id: string
description: string
title: string
filters?: IDashboardFilter[]
Expand All @@ -18,11 +18,11 @@ export interface IDashboardElement {
look?: ILook
query?: IQuery
listen?: {[key: string]: string} // deprecated
result_maker_id?: number
result_maker_id?: string
result_maker?: {
id: number,
query_id?: number,
merge_result_id?: number,
id: string,
query_id?: string,
merge_result_id?: string,
filterables?: IDashboardElementResultMakerFilterable[],
}
}
Expand All @@ -41,7 +41,7 @@ export interface IDashboardFilter {
}

export interface ILook {
id: number
id: string
query: IQuery
}

Expand All @@ -50,7 +50,7 @@ export interface IQueryFilters {
}

export interface IQuery {
id: number
id: string
slug: string
share_url: string
vis_config: {
Expand Down
4 changes: 2 additions & 2 deletions src/repliers/look_finder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class LookFinder extends QueryRunner {
attachments: shortResults.map((v: any) => {
const look = v.value
return {
text: `in ${look.space.name}`,
text: `in ${look.folder.name}`,
title: look.title,
title_link: `${this.replyContext.looker.url}${look.short_url}`,
}
Expand All @@ -35,7 +35,7 @@ export class LookFinder extends QueryRunner {

private async matchLooks() {
const looks = await this.replyContext.looker.client.getAsync(
"looks?fields=id,title,short_url,space(name,id)",
"looks?fields=id,title,short_url,folder(name,id)",
this.replyContext,
)

Expand Down
4 changes: 2 additions & 2 deletions src/repliers/look_query_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export class LookQueryRunner extends QueryRunner {

constructor(
replyContext: ReplyContext,
private lookId: number | string,
private filterInfo?: {queryId: number, url: string},
private lookId: string,
private filterInfo?: {queryId: string, url: string},
) {
super(replyContext)
this.lookId = lookId
Expand Down
4 changes: 2 additions & 2 deletions src/repliers/query_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { SlackTableFormatter } from "./slack_table_formatter"
export class QueryRunner extends FancyReplier {

protected querySlug?: string
protected queryId?: number
protected queryId?: string

constructor(replyContext: ReplyContext, queryParam: {slug?: string, id?: number} = {}) {
constructor(replyContext: ReplyContext, queryParam: {slug?: string, id?: string} = {}) {
super(replyContext)
this.querySlug = queryParam.slug
this.queryId = queryParam.id
Expand Down

0 comments on commit 607d2ee

Please sign in to comment.