diff --git a/.env-sample b/.env-sample index c55fd29b..12ff8f3a 100644 --- a/.env-sample +++ b/.env-sample @@ -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 diff --git a/README.md b/README.md index 5b30f851..fb5cc044 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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. @@ -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 diff --git a/app.json b/app.json index b9bcdc45..06098983 100644 --- a/app.json +++ b/app.json @@ -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": { diff --git a/src/commands/help_command.ts b/src/commands/help_command.ts index eecbc6d2..06c50630 100644 --- a/src/commands/help_command.ts +++ b/src/commands/help_command.ts @@ -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}._`, }) } diff --git a/src/looker.ts b/src/looker.ts index 7dea1daa..a8cc6c4d 100644 --- a/src/looker.ts +++ b/src/looker.ts @@ -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 { @@ -15,7 +15,7 @@ interface ILookerOptions { apiBaseUrl: string clientId: string clientSecret: string - customCommandSpaceId: string + customCommandFolderId: string url: string webhookToken: string } @@ -37,9 +37,9 @@ 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, }]) @@ -47,14 +47,14 @@ export class Looker { } 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({ @@ -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 = { diff --git a/src/looker_api_types.ts b/src/looker_api_types.ts index 020d8cb7..d8bcf06b 100644 --- a/src/looker_api_types.ts +++ b/src/looker_api_types.ts @@ -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[] @@ -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[], } } @@ -41,7 +41,7 @@ export interface IDashboardFilter { } export interface ILook { - id: number + id: string query: IQuery } @@ -50,7 +50,7 @@ export interface IQueryFilters { } export interface IQuery { - id: number + id: string slug: string share_url: string vis_config: { diff --git a/src/repliers/look_finder.ts b/src/repliers/look_finder.ts index 6a0055bd..06d87bb0 100644 --- a/src/repliers/look_finder.ts +++ b/src/repliers/look_finder.ts @@ -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}`, } @@ -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, ) diff --git a/src/repliers/look_query_runner.ts b/src/repliers/look_query_runner.ts index 4f38d12a..0d595dec 100644 --- a/src/repliers/look_query_runner.ts +++ b/src/repliers/look_query_runner.ts @@ -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 diff --git a/src/repliers/query_runner.ts b/src/repliers/query_runner.ts index ba280c4c..b9552db3 100644 --- a/src/repliers/query_runner.ts +++ b/src/repliers/query_runner.ts @@ -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