Skip to content

Commit

Permalink
Merge pull request #10680 from Budibase/budi-6933-verify-data-source-…
Browse files Browse the repository at this point in the history
…connection-during

Verify data source connection before saves
  • Loading branch information
adrinr authored May 26, 2023
2 parents f27fcec + b70ec9e commit 5a8f9d7
Show file tree
Hide file tree
Showing 24 changed files with 160 additions and 50 deletions.
7 changes: 7 additions & 0 deletions packages/builder/src/builderStore/datasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { datasources, tables } from "../stores/backend"
import { IntegrationNames } from "../constants/backend"
import { get } from "svelte/store"
import cloneDeep from "lodash/cloneDeepWith"
import { API } from "api"

function prepareData(config) {
let datasource = {}
Expand Down Expand Up @@ -37,3 +38,9 @@ export async function createRestDatasource(integration) {
const config = cloneDeep(integration)
return saveDatasource(config)
}

export async function validateDatasourceConfig(config) {
const datasource = prepareData(config)
const resp = await API.validateDatasource(datasource)
return resp
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
config,
schema: selected.datasource,
auth: selected.auth,
features: selected.features || [],
}
if (selected.friendlyName) {
integration.name = selected.friendlyName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,68 @@
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import { IntegrationNames } from "constants/backend"
import cloneDeep from "lodash/cloneDeepWith"
import { saveDatasource as save } from "builderStore/datasource"
import { onMount } from "svelte"
import {
saveDatasource as save,
validateDatasourceConfig,
} from "builderStore/datasource"
import { DatasourceFeature } from "@budibase/types"
export let integration
export let modal
// kill the reference so the input isn't saved
let datasource = cloneDeep(integration)
let skipFetch = false
let isValid = false
$: name =
IntegrationNames[datasource.type] || datasource.name || datasource.type
async function validateConfig() {
const displayError = message =>
notifications.error(message ?? "Error validating datasource")
let connected = false
try {
const resp = await validateDatasourceConfig(datasource)
if (!resp.connected) {
displayError(`Unable to connect - ${resp.error}`)
}
connected = resp.connected
} catch (err) {
displayError(err?.message)
}
return connected
}
async function saveDatasource() {
if (integration.features[DatasourceFeature.CONNECTION_CHECKING]) {
const valid = await validateConfig()
if (!valid) {
return false
}
}
try {
if (!datasource.name) {
datasource.name = name
}
const resp = await save(datasource, skipFetch)
const resp = await save(datasource)
$goto(`./datasource/${resp._id}`)
notifications.success(`Datasource updated successfully.`)
notifications.success(`Datasource created successfully.`)
} catch (err) {
notifications.error(err?.message ?? "Error saving datasource")
// prevent the modal from closing
return false
}
}
onMount(() => {
skipFetch = false
})
</script>
<ModalContent
title={`Connect to ${name}`}
onConfirm={() => saveDatasource()}
onCancel={() => modal.show()}
confirmText={datasource.plus
? "Save and fetch tables"
: "Save and continue to query"}
confirmText={datasource.plus ? "Connect" : "Save and continue to query"}
cancelText="Back"
showSecondaryButton={datasource.plus}
secondaryButtonText={datasource.plus ? "Skip table fetch" : undefined}
secondaryAction={() => {
skipFetch = true
saveDatasource()
return true
}}
size="L"
disabled={!isValid}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import { isEqual } from "lodash"
import { cloneDeep } from "lodash/fp"
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
import { API } from "api"
import { DatasourceFeature } from "@budibase/types"
const querySchema = {
name: {},
Expand All @@ -45,7 +47,30 @@
}
}
async function validateConfig() {
const displayError = message =>
notifications.error(message ?? "Error validating datasource")
let connected = false
try {
const resp = await API.validateDatasource(datasource)
if (!resp.connected) {
displayError(`Unable to connect - ${resp.error}`)
}
connected = resp.connected
} catch (err) {
displayError(err?.message)
}
return connected
}
const saveDatasource = async () => {
if (integration.features[DatasourceFeature.CONNECTION_CHECKING]) {
const valid = await validateConfig()
if (!valid) {
return false
}
}
try {
// Create datasource
await datasources.save(datasource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@
return "Invalid URL"
}
}
$: urlManuallySet = false
const updateUrl = event => {
const appName = event.detail
if (urlManuallySet) {
return
}
const parsedUrl = appName.toLowerCase().replace(/\s+/g, "-")
url = encodeURI(parsedUrl)
}
</script>

<div>
Expand All @@ -43,11 +55,13 @@
bind:value={name}
bind:error={nameError}
validate={validateName}
on:change={updateUrl}
label="Name"
/>
<FancyInput
bind:value={url}
bind:error={urlError}
on:change={() => (urlManuallySet = true)}
validate={validateUrl}
label="URL"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import { Roles } from "constants/backend"
import Spinner from "components/common/Spinner.svelte"
import { helpers } from "@budibase/shared-core"
import { validateDatasourceConfig } from "builderStore/datasource"
import { DatasourceFeature } from "@budibase/types"
let name = "My first app"
let url = "my-first-app"
Expand Down Expand Up @@ -108,7 +110,24 @@
isGoogle,
}) => {
let app
try {
if (
datasourceConfig &&
plusIntegrations[stage].features[DatasourceFeature.CONNECTION_CHECKING]
) {
const resp = await validateDatasourceConfig({
config: datasourceConfig,
type: stage,
})
if (!resp.connected) {
notifications.error(
`Unable to connect - ${resp.error ?? "Error validating datasource"}`
)
return false
}
}
app = await createApp(useSampleData)
let datasource
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/stores/portal/features.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { writable } from "svelte/store"
import { API } from "api"
import { licensing } from "./licensing"
import { ConfigType } from "../../../../types/src/documents"
import { ConfigType } from "@budibase/types"

export const createFeatureStore = () => {
const internalStore = writable({
Expand Down
11 changes: 11 additions & 0 deletions packages/frontend-core/src/api/datasources.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,15 @@ export const buildDatasourceEndpoints = API => ({
url: `/api/datasources/${datasourceId}/${datasourceRev}`,
})
},

/**
* Validate a datasource configuration
* @param datasource the datasource configuration to validate
*/
validateDatasource: async datasource => {
return await API.post({
url: `/api/datasources/verify`,
body: { datasource },
})
},
})
4 changes: 3 additions & 1 deletion packages/server/src/integrations/airtable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const SCHEMA: Integration = {
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
friendlyName: "Airtable",
type: "Spreadsheet",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
apiKey: {
type: DatasourceFieldType.PASSWORD,
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/arangodb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const SCHEMA: Integration = {
type: "Non-relational",
description:
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
url: {
type: DatasourceFieldType.STRING,
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/couchdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const SCHEMA: Integration = {
type: "Non-relational",
description:
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
url: {
type: DatasourceFieldType.STRING,
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/dynamodb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ const SCHEMA: Integration = {
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
friendlyName: "DynamoDB",
type: "Non-relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
region: {
type: DatasourceFieldType.STRING,
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/elasticsearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ const SCHEMA: Integration = {
"Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.",
friendlyName: "ElasticSearch",
type: "Non-relational",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
url: {
type: DatasourceFieldType.STRING,
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const SCHEMA: Integration = {
type: "Non-relational",
description:
"Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
email: {
type: DatasourceFieldType.STRING,
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/integrations/googlesheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ const SCHEMA: Integration = {
"Create and collaborate on online spreadsheets in real-time and from any device.",
friendlyName: "Google Sheets",
type: "Spreadsheet",
features: [
DatasourceFeature.CONNECTION_CHECKING,
DatasourceFeature.FETCH_TABLE_NAMES,
],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
},
datasource: {
spreadsheetId: {
display: "Google Sheet URL",
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/integrations/microsoftSqlServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ const SCHEMA: Integration = {
"Microsoft SQL Server is a relational database management system developed by Microsoft. ",
friendlyName: "MS SQL Server",
type: "Relational",
features: [
DatasourceFeature.CONNECTION_CHECKING,
DatasourceFeature.FETCH_TABLE_NAMES,
],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
},
datasource: {
user: {
type: DatasourceFieldType.STRING,
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/mongodb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ const getSchema = () => {
type: "Non-relational",
description:
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
features: [DatasourceFeature.CONNECTION_CHECKING],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
},
datasource: {
connectionString: {
type: DatasourceFieldType.STRING,
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/integrations/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ const SCHEMA: Integration = {
type: "Relational",
description:
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
features: [
DatasourceFeature.CONNECTION_CHECKING,
DatasourceFeature.FETCH_TABLE_NAMES,
],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
},
datasource: {
host: {
type: DatasourceFieldType.STRING,
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/integrations/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ const SCHEMA: Integration = {
type: "Relational",
description:
"Oracle Database is an object-relational database management system developed by Oracle Corporation",
features: [
DatasourceFeature.CONNECTION_CHECKING,
DatasourceFeature.FETCH_TABLE_NAMES,
],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
},
datasource: {
host: {
type: DatasourceFieldType.STRING,
Expand Down
8 changes: 4 additions & 4 deletions packages/server/src/integrations/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ const SCHEMA: Integration = {
type: "Relational",
description:
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
features: [
DatasourceFeature.CONNECTION_CHECKING,
DatasourceFeature.FETCH_TABLE_NAMES,
],
features: {
[DatasourceFeature.CONNECTION_CHECKING]: true,
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
},
datasource: {
host: {
type: DatasourceFieldType.STRING,
Expand Down
Loading

0 comments on commit 5a8f9d7

Please sign in to comment.