-
Notifications
You must be signed in to change notification settings - Fork 0
Snowflake plugin #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Snowflake plugin #40
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Snowflake plugin | ||
|
|
||
| A simple data source for Snowflake that supports Snowflake SQL queries. Requires an OAuth connection. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| { | ||
| "steps": [ | ||
| { | ||
| "displayName": "API access", | ||
| "dataStream": { | ||
| "name": "sqlQuery", | ||
| "config": { | ||
| "query": "show databases" | ||
| } | ||
| }, | ||
| "success": "User credentials has Snowflake query permissions.", | ||
| "error": "User does not have permission to access the Snowflake API (query 'SHOW DATABASES' failed).", | ||
| "required": true | ||
| }, | ||
| { | ||
| "displayName": "Compute access", | ||
| "dataStream": { | ||
| "name": "sqlQuery", | ||
| "config": { | ||
| "query": "select 1/1", | ||
| "errorOnEmptyResults": true | ||
| } | ||
| }, | ||
| "success": "User has access to warehouse.", | ||
| "error": "User does not have access to a warehouse (query 'SELECT 1/1' failed). Check user's role is configured with a default warehouse and has warehouse permissions.", | ||
| "required": true | ||
| }, | ||
| { | ||
| "displayName": "Database access", | ||
| "dataStream": { | ||
| "name": "sqlQuery", | ||
| "config": { | ||
| "query": "show databases", | ||
| "errorOnEmptyResults": true | ||
| } | ||
| }, | ||
| "success": "User has access to at least one database.", | ||
| "error": "User does not have permission to access any databases. Check user's default role or specify a role.", | ||
| "required": false | ||
| } | ||
| ] | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| result = data.data.map( r => r.reduce((obj, value, i) => { | ||
| const columnName = data.resultSetMetaData.rowType[i].name; | ||
| obj[columnName] = value; | ||
| return obj; | ||
| }, {})); | ||
|
|
||
| // support value column for autoComplete queries | ||
| if (context.config.valueColumn) { | ||
| metadata = [ | ||
| { | ||
| name: context.config.valueColumn, | ||
| role: "value" | ||
| } | ||
| ] | ||
| } else { | ||
| const typeMapping = { | ||
| "text": "string", | ||
| "fixed": "number", | ||
| "real": "number", | ||
| "varchar": "string", | ||
| "date": "date", | ||
| "timestamp": "date" | ||
| }; | ||
|
|
||
| metadata = data.resultSetMetaData.rowType.map( c => { | ||
| return { | ||
| name: c.name, | ||
| shape: typeMapping[c.type] || "string" | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| // used for validation queries | ||
| if (context.config.errorOnEmptyResults === true && data.data.length === 0) { | ||
| throw new Error("No results"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| { | ||
| "name": "sqlQuery", | ||
| "displayName": "SQL Query", | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "post", | ||
| "paging": { | ||
| "mode": "none" | ||
| }, | ||
| "expandInnerObjects": true, | ||
| "endpointPath": "/v2/statements/", | ||
| "postBody": { | ||
| "database": "{{typeof database !== 'undefined' ? database : undefined}}", | ||
| "schema": "{{typeof schema !== 'undefined' ? schema : undefined}}", | ||
| "statement": "{{query}}" | ||
| }, | ||
| "postRequestScript": "sqlQuery-post.js", | ||
| "getArgs": [], | ||
| "headers": [], | ||
| "valueColumn": "{{typeof valueColumn !== 'undefined' ? valueColumn : undefined}}", | ||
| "errorOnEmptyResults": "{{typeof errorOnEmptyResults !== 'undefined' ? errorOnEmptyResults : undefined}}" | ||
|
|
||
| }, | ||
| "ui": [ | ||
| { | ||
| "name": "database", | ||
| "type": "autocomplete", | ||
| "label": "Database", | ||
| "validation": { | ||
| "required": false | ||
| }, | ||
| "isMulti": false, | ||
| "allowCustomValues": true, | ||
| "data": { | ||
| "source": "dataStream", | ||
| "dataStreamName": "sqlQuery", | ||
| "dataSourceConfig": { | ||
| "valueColumn": "name", | ||
| "query": "show databases" | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "name": "schema", | ||
| "type": "autocomplete", | ||
| "label": "Schema", | ||
| "validation": { | ||
| "required": false | ||
| }, | ||
| "isMulti": false, | ||
| "allowCustomValues": true, | ||
| "data": { | ||
| "source": "dataStream", | ||
| "dataStreamName": "sqlQuery", | ||
| "dataSourceConfig": { | ||
| "valueColumn": "name", | ||
| "database": { | ||
| "fieldName": "database", | ||
| "required": true | ||
| }, | ||
| "query": "show schemas" | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "help": "Enter a query using Snowflake SQL syntax. You can also use parameters like {{timeframe.start}}, e.g. event_time BETWEEN '{{timeframe.start}}' AND '{{timeframe.end}}'", | ||
| "name": "query", | ||
| "language": "sql", | ||
| "label": "SQL query", | ||
| "type": "code", | ||
| "validation": { | ||
| "required": true | ||
| } | ||
| } | ||
| ], | ||
| "manualConfigApply": true, | ||
| "supportsNoneTimeframe": true, | ||
| "requiresParameterTimeframe": true, | ||
| "defaultTimeframe": "none" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Before you start | ||
|
|
||
| ## Creating an OAuth integration in Snowflake | ||
|
|
||
| The Snowflake data source authenticates using OAuth. | ||
|
|
||
| Before configuring the data source you will need to register SquaredUp with your Snowflake account by creating a custom integration. | ||
|
|
||
| Sample Snowflake commands for creating the integration are provided below. | ||
|
|
||
| For more information on creating a Snowflake integration see: | ||
| https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake | ||
|
|
||
|
|
||
| If your SquaredUp account is in the US region (default): | ||
|
|
||
| ``` | ||
| CREATE SECURITY INTEGRATION oauth_squaredup | ||
| TYPE = oauth | ||
| OAUTH_CLIENT = custom | ||
| OAUTH_CLIENT_TYPE = 'CONFIDENTIAL' | ||
| OAUTH_REDIRECT_URI = 'https://app.squaredup.com/settings/pluginsoauth2' | ||
| COMMENT = 'Used by SquaredUp to connect to this Snowflake account' | ||
| ``` | ||
|
|
||
| If your SquaredUp account is in the EU region: | ||
|
|
||
| ``` | ||
| CREATE SECURITY INTEGRATION oauth_squaredup | ||
| TYPE = oauth | ||
|
Comment on lines
+16
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Both the US and EU region OAuth sections in Extended reasoning...What the bug is and how it manifests In The specific code path that triggers it This is purely a documentation issue in the newly added Why existing checks don't prevent it There is no automated lint or validation step for documentation prose in this repository, so the copy-paste error passed undetected. Both code blocks differ only in the Impact A new EU user reading the setup guide sees both sections labeled How to fix it Remove the word Step-by-step proof
|
||
| OAUTH_CLIENT = custom | ||
| OAUTH_CLIENT_TYPE = 'CONFIDENTIAL' | ||
| OAUTH_REDIRECT_URI = 'https://eu.app.squaredup.com/settings/pluginsoauth2' | ||
| COMMENT = 'Used by SquaredUp to connect to this Snowflake account' | ||
| ``` | ||
|
|
||
| Once your integration is created, run: | ||
|
|
||
| ``` | ||
| SELECT | ||
| oauth:OAUTH_CLIENT_SECRET::STRING AS OAUTH_CLIENT_SECRET, | ||
| oauth:OAUTH_CLIENT_ID::STRING AS OAUTH_CLIENT_ID | ||
| FROM (SELECT PARSE_JSON(SYSTEM$SHOW_OAUTH_CLIENT_SECRETS('oauth_squaredup')) AS oauth) | ||
|
|
||
| ``` | ||
|
|
||
| Use the values of the `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` columns in your configuration below. | ||
|
|
||
|
|
||
| ## Creating a read-only user | ||
|
|
||
| To connect to Snowflake you will need the credentials for a Snowflake user. | ||
|
|
||
| By default, it is NOT possible to connect via OAuth using an ACCOUNTADMIN role. Snowflake automatically adds privileged roles to the blocked role list used for OAuth authorization, see https://docs.snowflake.com/en/sql-reference/parameters#oauth-add-privileged-roles-to-blocked-list | ||
|
|
||
| We recommend a dedicated 'squaredup' user account that is assigned read only role. For more information on Snowflake users and roles, see https://docs.snowflake.com/en/user-guide/security-access-control-configure. | ||
|
|
||
| Ensure the user has a default role set, or specify the role when configuring the data source (see below). If the user does not have a default role and no role is specified, the connection will use the PUBLIC role, which typically does not have any permissions to databases. | ||
|
|
||
|
|
||
| # Configuration | ||
|
|
||
| ## Snowflake account identifier | ||
|
|
||
| Enter your Snowflake account identifier. | ||
|
|
||
| This can be found in the Snowflake portal under 'Your Username' > Account > Account Identifier. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The line 'For example: Extended reasoning...What the bug is: Line 71 of The specific code path that triggers it: Any user reading the setup documentation to configure the Snowflake plugin will encounter this misleading example when trying to determine what value to enter for the "Snowflake account identifier" field. Why existing code doesn't prevent it: This is a documentation error — there is no automated check to catch copy-paste mistakes in markdown files. The surrounding text on the previous line already correctly describes the format ( Impact: A user reading this documentation would see two conflicting pieces of information: one saying the format is How to fix it: Simply remove line 71 ( Step-by-step proof:
|
||
|
|
||
| The account identifier is in the format <org_name>-<account_name>. | ||
|
|
||
| For example: `ABCDEFG-XYZ12345` | ||
|
|
||
| Alternatively, run the following Snowflake query: | ||
|
|
||
| ``` | ||
| SELECT CURRENT_ORGANIZATION_NAME() || '-' || CURRENT_ACCOUNT_NAME(); | ||
| ``` | ||
|
|
||
| ## Snowflake OAuth client ID | ||
|
|
||
| The client ID for your Snowflake OAuth application. | ||
|
|
||
| Enter the `OAUTH_CLIENT_ID` value from the integration you created above. | ||
|
|
||
| ## Snowflake OAuth client secret | ||
|
|
||
| The client secret for your Snowflake OAuth application. | ||
|
|
||
| Enter the `OAUTH_CLIENT_SECRET` value from the integration you created above. | ||
|
|
||
| ## Role (optional) | ||
|
|
||
| Restrict OAuth connection to a specific role. If not specified, the user's default role is used. | ||
|
|
||
| If you have created a custom role for your database, for example a read-only role, enter its name here. | ||
|
|
||
| ## Authorize | ||
|
|
||
| Click the Sign-in button to authorize SquaredUp to access Snowflake. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| { | ||
| "name": "snowflake", | ||
| "displayName": "Snowflake", | ||
| "version": "1.0.0", | ||
| "author": { | ||
| "name": "SquaredUp Labs", | ||
| "type": "labs" | ||
| }, | ||
| "description": "Query data from Snowflake.", | ||
| "category": "Database", | ||
| "type": "hybrid", | ||
| "schemaVersion": "2.0", | ||
| "base": { | ||
| "plugin": "WebAPI", | ||
| "majorVersion": "1", | ||
| "config": { | ||
| "queryArgs": [], | ||
| "headers": [], | ||
| "oauth2TokenExtraArgs": [], | ||
| "oauth2ClientSecret": "{{oauth2ClientSecret}}", | ||
| "oauth2ClientSecretLocationDuringAuth": "header", | ||
| "oauth2AuthUrl": "https://{{accountId}}.snowflakecomputing.com/oauth/authorize", | ||
| "authMode": "oauth2", | ||
| "oauth2GrantType": "authCode", | ||
| "baseUrl": "https://{{accountId}}.snowflakecomputing.com/api", | ||
| "oauth2TokenExtraHeaders": [ | ||
| { | ||
| "value": "application/x-www-form-urlencoded", | ||
| "key": "Content-Type" | ||
| } | ||
| ], | ||
| "oauth2ClientId": "{{oauth2ClientId}}", | ||
| "oauth2TokenUrl": "https://{{accountId}}.snowflakecomputing.com/oauth/token-request", | ||
| "oauth2AuthExtraArgs": [], | ||
| "oauth2Scope": "refresh_token {{oauth2Role? 'session:role:' + oauth2Role : ''}}" | ||
| } | ||
| }, | ||
| "links": [ | ||
| { | ||
| "category": "documentation", | ||
| "url": "https://github.com/squaredup/plugins/blob/main/plugins/Snowflake/v1/docs/setup.md", | ||
| "label": "Help adding this plugin" | ||
| }, | ||
| { | ||
| "category": "source", | ||
| "url": "https://github.com/squaredup/plugins/tree/main/plugins/Snowflake/v1", | ||
| "label": "Repository" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| [ | ||
| { | ||
| "type": "text", | ||
| "name": "accountId", | ||
| "label": "Snowflake account identifier", | ||
| "help": "Enter your Snowflake account identifier. Find this in the portal under Your Username > Account > Account Identifier. It is in the format <org_name>-<account_name>, e.g. ABCDEFG-XYZ12345", | ||
| "validation": { | ||
| "required": true | ||
| }, | ||
| "placeholder": "<org_name>-<account_name>, e.g. ABCDEFG-XYZ12345" | ||
| }, | ||
| { | ||
| "type": "text", | ||
| "name": "oauth2ClientId", | ||
| "label": "Snowflake OAuth client ID", | ||
| "help": "The client ID for your Snowflake OAuth application. See documentation for details on how to set up an OAuth application in Snowflake and obtain the client ID.", | ||
| "validation": { | ||
| "required": true | ||
| }, | ||
| "placeholder": "Enter your Snowflake OAuth client ID" | ||
| }, | ||
| { | ||
| "type": "password", | ||
| "name": "oauth2ClientSecret", | ||
| "label": "Snowflake OAuth secret", | ||
| "help": "The client secret for your Snowflake OAuth application. See documentation for details on how to set up an OAuth application in Snowflake and obtain the client secret.", | ||
| "validation": { | ||
| "required": true | ||
| }, | ||
| "placeholder": "Enter your Snowflake OAuth secret" | ||
| }, | ||
| { | ||
| "type": "text", | ||
| "name": "oauth2Role", | ||
| "label": "Role (optional)", | ||
| "help": "Scope OAuth connection to a specific role. If not specified, the user's default role is used.", | ||
| "validation": { | ||
| "required": false | ||
| }, | ||
| "placeholder": "Enter your Snowflake OAuth role" | ||
| }, | ||
| { | ||
| "type": "oAuth2", | ||
| "name": "oauth2AuthCodeSignIn", | ||
| "label": "Authorize", | ||
| "validation": { | ||
| "required": true | ||
| } | ||
| } | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Line 6 of
plugins/Snowflake/v1/docs/setup.mdcontains a typo: "bby" should be "by" in the sentence "register SquaredUp with your Snowflake account bby creating a custom integration." This is a minor documentation error that should be corrected before merging.Extended reasoning...
The typo 'bby' (a double-b error) appears on line 6 of docs/setup.md in the newly added Snowflake plugin documentation. The affected sentence reads: 'Before configuring the data source you will need to register SquaredUp with your Snowflake account bby creating a custom integration.'
The correct word should be 'by', making the sentence: 'Before configuring the data source you will need to register SquaredUp with your Snowflake account by creating a custom integration.'
This is a straightforward typographical error introduced in this PR. The file docs/setup.md is a new file added as part of the Snowflake plugin, so the typo was introduced in this change.
While the error does not affect the functionality of the plugin in any way (it is purely documentation), it presents a poor first impression to users following the setup guide. The sentence appears in the introductory section explaining OAuth setup, which users will read first when configuring the plugin.
The fix is trivial: replace 'bby' with 'by' on line 6 of plugins/Snowflake/v1/docs/setup.md.
Step-by-step proof: Reading the diff for docs/setup.md, line 6 (the 6th non-blank added line) reads: 'Before configuring the data source you will need to register SquaredUp with your Snowflake account bby creating a custom integration.' The word 'bby' is clearly 'by' with an extra 'b' character.