From 421d956daa20339e06042d16384f70f3f581f1bf Mon Sep 17 00:00:00 2001 From: richbenwell Date: Fri, 17 Apr 2026 14:28:23 -0400 Subject: [PATCH 1/4] Snowflake v1 --- plugins/Snowflake/v1/README.md | 3 + plugins/Snowflake/v1/configValidation.json | 43 ++++++++ .../v1/dataStreams/scripts/sqlQuery-post.js | 36 +++++++ .../Snowflake/v1/dataStreams/sqlQuery.json | 80 +++++++++++++++ plugins/Snowflake/v1/docs/setup.md | 99 +++++++++++++++++++ plugins/Snowflake/v1/icon.svg | 1 + plugins/Snowflake/v1/metadata.json | 50 ++++++++++ plugins/Snowflake/v1/ui.json | 50 ++++++++++ 8 files changed, 362 insertions(+) create mode 100644 plugins/Snowflake/v1/README.md create mode 100644 plugins/Snowflake/v1/configValidation.json create mode 100644 plugins/Snowflake/v1/dataStreams/scripts/sqlQuery-post.js create mode 100644 plugins/Snowflake/v1/dataStreams/sqlQuery.json create mode 100644 plugins/Snowflake/v1/docs/setup.md create mode 100644 plugins/Snowflake/v1/icon.svg create mode 100644 plugins/Snowflake/v1/metadata.json create mode 100644 plugins/Snowflake/v1/ui.json diff --git a/plugins/Snowflake/v1/README.md b/plugins/Snowflake/v1/README.md new file mode 100644 index 0000000..369130a --- /dev/null +++ b/plugins/Snowflake/v1/README.md @@ -0,0 +1,3 @@ +## Overview + +A simple data source for Snowflake that supports Snowflake SQL queries. Requires an OAuth connection. \ No newline at end of file diff --git a/plugins/Snowflake/v1/configValidation.json b/plugins/Snowflake/v1/configValidation.json new file mode 100644 index 0000000..4b1b524 --- /dev/null +++ b/plugins/Snowflake/v1/configValidation.json @@ -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 + } + ] +} + diff --git a/plugins/Snowflake/v1/dataStreams/scripts/sqlQuery-post.js b/plugins/Snowflake/v1/dataStreams/scripts/sqlQuery-post.js new file mode 100644 index 0000000..1437115 --- /dev/null +++ b/plugins/Snowflake/v1/dataStreams/scripts/sqlQuery-post.js @@ -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"); +} \ No newline at end of file diff --git a/plugins/Snowflake/v1/dataStreams/sqlQuery.json b/plugins/Snowflake/v1/dataStreams/sqlQuery.json new file mode 100644 index 0000000..9e63477 --- /dev/null +++ b/plugins/Snowflake/v1/dataStreams/sqlQuery.json @@ -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" +} \ No newline at end of file diff --git a/plugins/Snowflake/v1/docs/setup.md b/plugins/Snowflake/v1/docs/setup.md new file mode 100644 index 0000000..f633110 --- /dev/null +++ b/plugins/Snowflake/v1/docs/setup.md @@ -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 bby 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 (default): + +``` +CREATE SECURITY INTEGRATION oauth_squaredup + TYPE = oauth + 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. + +The account identifier It is in the format -, e.g. ABCDEFG-XYZ12345 + +For example: `https://:9200` + +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. diff --git a/plugins/Snowflake/v1/icon.svg b/plugins/Snowflake/v1/icon.svg new file mode 100644 index 0000000..c7a0eba --- /dev/null +++ b/plugins/Snowflake/v1/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/Snowflake/v1/metadata.json b/plugins/Snowflake/v1/metadata.json new file mode 100644 index 0000000..baf71f3 --- /dev/null +++ b/plugins/Snowflake/v1/metadata.json @@ -0,0 +1,50 @@ +{ + "name": "snowflake-preview", + "displayName": "Snowflake (Preview)", + "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" + } + ] +} \ No newline at end of file diff --git a/plugins/Snowflake/v1/ui.json b/plugins/Snowflake/v1/ui.json new file mode 100644 index 0000000..fcaaed9 --- /dev/null +++ b/plugins/Snowflake/v1/ui.json @@ -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 -, e.g. ABCDEFG-XYZ12345", + "validation": { + "required": true + }, + "placeholder": "-, 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 + } + } +] \ No newline at end of file From 3919b20967085a9e8c8c417b2b7dd5b8437155b4 Mon Sep 17 00:00:00 2001 From: richbenwell Date: Fri, 17 Apr 2026 15:01:25 -0400 Subject: [PATCH 2/4] Docs tidy up. --- plugins/Snowflake/v1/README.md | 2 +- plugins/Snowflake/v1/docs/setup.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Snowflake/v1/README.md b/plugins/Snowflake/v1/README.md index 369130a..9853655 100644 --- a/plugins/Snowflake/v1/README.md +++ b/plugins/Snowflake/v1/README.md @@ -1,3 +1,3 @@ -## Overview +# Snowflake plugin A simple data source for Snowflake that supports Snowflake SQL queries. Requires an OAuth connection. \ No newline at end of file diff --git a/plugins/Snowflake/v1/docs/setup.md b/plugins/Snowflake/v1/docs/setup.md index f633110..36f2215 100644 --- a/plugins/Snowflake/v1/docs/setup.md +++ b/plugins/Snowflake/v1/docs/setup.md @@ -4,7 +4,7 @@ The Snowflake data source authenticates using OAuth. -Before configuring the data source you will need to register SquaredUp with your Snowflake account bby creating a custom integration. +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. @@ -23,7 +23,7 @@ CREATE SECURITY INTEGRATION oauth_squaredup COMMENT = 'Used by SquaredUp to connect to this Snowflake account' ``` -If your SquaredUp account is in the EU region (default): +If your SquaredUp account is in the EU region: ``` CREATE SECURITY INTEGRATION oauth_squaredup @@ -66,9 +66,9 @@ Enter your Snowflake account identifier. This can be found in the Snowflake portal under 'Your Username' > Account > Account Identifier. -The account identifier It is in the format -, e.g. ABCDEFG-XYZ12345 +The account identifier is in the format -. -For example: `https://:9200` +For example: `ABCDEFG-XYZ12345` Alternatively, run the following Snowflake query: From 4d5afe514062e3d7dd3dbcc38c1d33cecb71ac66 Mon Sep 17 00:00:00 2001 From: richbenwell Date: Fri, 17 Apr 2026 15:04:01 -0400 Subject: [PATCH 3/4] Remove `preview` suffix --- plugins/Snowflake/v1/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Snowflake/v1/metadata.json b/plugins/Snowflake/v1/metadata.json index baf71f3..beafd55 100644 --- a/plugins/Snowflake/v1/metadata.json +++ b/plugins/Snowflake/v1/metadata.json @@ -1,6 +1,6 @@ { - "name": "snowflake-preview", - "displayName": "Snowflake (Preview)", + "name": "snowflake", + "displayName": "Snowflake", "version": "1.0.0", "author": { "name": "SquaredUp Labs", From 7b384e0091925337a9526db47f5f59c7c8bf97d4 Mon Sep 17 00:00:00 2001 From: richbenwell Date: Mon, 20 Apr 2026 18:20:21 -0400 Subject: [PATCH 4/4] =?UTF-8?q?Remove=20explicit=20pass=20through=20?= =?UTF-8?q?=E2=80=93=C2=A0framework=20already=20does=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/Snowflake/v1/dataStreams/sqlQuery.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/Snowflake/v1/dataStreams/sqlQuery.json b/plugins/Snowflake/v1/dataStreams/sqlQuery.json index 9e63477..586f2a3 100644 --- a/plugins/Snowflake/v1/dataStreams/sqlQuery.json +++ b/plugins/Snowflake/v1/dataStreams/sqlQuery.json @@ -16,10 +16,7 @@ }, "postRequestScript": "sqlQuery-post.js", "getArgs": [], - "headers": [], - "valueColumn": "{{typeof valueColumn !== 'undefined' ? valueColumn : undefined}}", - "errorOnEmptyResults": "{{typeof errorOnEmptyResults !== 'undefined' ? errorOnEmptyResults : undefined}}" - + "headers": [] }, "ui": [ {