From 0d67f000f03e1c27692e92dcf866cd36b55435cd Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 18 Oct 2023 12:19:24 +0100 Subject: [PATCH 01/65] Initial reflow of the block settings --- packages/client/manifest.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 8d0a4e456f..d5fca15fd7 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5288,17 +5288,17 @@ }, "settings": [ { - "type": "select", + "type": "table", + "label": "Data", + "key": "dataSource" + }, + { + "type": "radio", "label": "Type", "key": "actionType", "options": ["Create", "Update", "View"], "defaultValue": "Create" }, - { - "type": "table", - "label": "Data", - "key": "dataSource" - }, { "type": "text", "label": "Title", @@ -5323,7 +5323,7 @@ }, { "type": "text", - "label": "Empty text", + "label": "No rows found", "key": "noRowsMessage", "defaultValue": "We couldn't find a row to display", "nested": true From f59d6222912443484cb98fb666dd92442199abc7 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 19 Oct 2023 12:28:28 +0100 Subject: [PATCH 02/65] Moved buttons section below fields --- packages/client/manifest.json | 102 +++++++++++++++++----------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index d5fca15fd7..1919f4ccd2 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5330,57 +5330,6 @@ } ] }, - { - "section": true, - "name": "Buttons", - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - }, - "settings": [ - { - "type": "text", - "key": "saveButtonLabel", - "label": "Save button", - "nested": true, - "defaultValue": "Save" - }, - { - "type": "text", - "key": "deleteButtonLabel", - "label": "Delete button", - "nested": true, - "defaultValue": "Delete", - "dependsOn": { - "setting": "actionType", - "value": "Update" - } - }, - { - "type": "url", - "label": "Navigate after button press", - "key": "actionUrl", - "placeholder": "Choose a screen", - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } - }, - { - "type": "boolean", - "label": "Hide notifications", - "key": "notificationOverride", - "defaultValue": false, - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } - } - ] - }, { "section": true, "name": "Fields", @@ -5436,6 +5385,57 @@ } } ] + }, + { + "section": true, + "name": "Buttons", + "dependsOn": { + "setting": "actionType", + "value": "View", + "invert": true + }, + "settings": [ + { + "type": "text", + "key": "saveButtonLabel", + "label": "Save button", + "nested": true, + "defaultValue": "Save" + }, + { + "type": "text", + "key": "deleteButtonLabel", + "label": "Delete button", + "nested": true, + "defaultValue": "Delete", + "dependsOn": { + "setting": "actionType", + "value": "Update" + } + }, + { + "type": "url", + "label": "Navigate after button press", + "key": "actionUrl", + "placeholder": "Choose a screen", + "dependsOn": { + "setting": "actionType", + "value": "View", + "invert": true + } + }, + { + "type": "boolean", + "label": "Hide notifications", + "key": "notificationOverride", + "defaultValue": false, + "dependsOn": { + "setting": "actionType", + "value": "View", + "invert": true + } + } + ] } ], "context": [ From 230dc169fa86bdaa63976b03407e1d3ab166db4b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 26 Oct 2023 10:11:34 +0100 Subject: [PATCH 03/65] Revert previous fix for relationship column dragging --- .../grid/cells/RelationshipCell.svelte | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index e6d83e0bea..925c840478 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -260,31 +260,29 @@ class:wrap={editable || contentLines > 1} on:wheel={e => (focused ? e.stopPropagation() : null)} > - {#if Array.isArray(value) && value.length} - {#each value as relationship} - {#if relationship[primaryDisplay] || relationship.primaryDisplay} -
- showRelationship(relationship._id) - : null} - > - {readable( - relationship[primaryDisplay] || relationship.primaryDisplay - )} - - {#if editable} - toggleRow(relationship)} - /> - {/if} -
- {/if} - {/each} - {/if} + {#each value || [] as relationship} + {#if relationship[primaryDisplay] || relationship.primaryDisplay} +
+ showRelationship(relationship._id) + : null} + > + {readable( + relationship[primaryDisplay] || relationship.primaryDisplay + )} + + {#if editable} + toggleRow(relationship)} + /> + {/if} +
+ {/if} + {/each} {#if editable}
@@ -320,7 +318,7 @@
- {:else if Array.isArray(searchResults) && searchResults.length} + {:else if searchResults?.length}
{#each searchResults as row, idx}
Date: Thu, 26 Oct 2023 17:37:59 +0100 Subject: [PATCH 04/65] Update grids to support pagination with REST queries and to simplify some logic --- .../src/components/grid/stores/datasource.js | 6 ------ .../grid/stores/datasources/nonPlus.js | 6 ------ .../grid/stores/datasources/table.js | 6 ------ .../grid/stores/datasources/viewV2.js | 6 ------ .../src/components/grid/stores/rows.js | 10 ++++----- packages/frontend-core/src/fetch/DataFetch.js | 21 +++++++++++++++---- 6 files changed, 22 insertions(+), 33 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js index 958f4541bd..7ee3a19b8a 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.js +++ b/packages/frontend-core/src/components/grid/stores/datasource.js @@ -160,11 +160,6 @@ export const createActions = context => { return getAPI()?.actions.canUseColumn(name) } - // Gets the default number of rows for a single page - const getFeatures = () => { - return getAPI()?.actions.getFeatures() - } - return { datasource: { ...datasource, @@ -177,7 +172,6 @@ export const createActions = context => { getRow, isDatasourceValid, canUseColumn, - getFeatures, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/datasources/nonPlus.js b/packages/frontend-core/src/components/grid/stores/datasources/nonPlus.js index 017c16a03c..acdf509278 100644 --- a/packages/frontend-core/src/components/grid/stores/datasources/nonPlus.js +++ b/packages/frontend-core/src/components/grid/stores/datasources/nonPlus.js @@ -35,11 +35,6 @@ export const createActions = context => { return $columns.some(col => col.name === name) || $sticky?.name === name } - const getFeatures = () => { - // We don't support any features - return {} - } - return { nonPlus: { actions: { @@ -50,7 +45,6 @@ export const createActions = context => { getRow, isDatasourceValid, canUseColumn, - getFeatures, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/datasources/table.js b/packages/frontend-core/src/components/grid/stores/datasources/table.js index 2f49ab1d38..847dfd2c6b 100644 --- a/packages/frontend-core/src/components/grid/stores/datasources/table.js +++ b/packages/frontend-core/src/components/grid/stores/datasources/table.js @@ -1,5 +1,4 @@ import { get } from "svelte/store" -import TableFetch from "../../../../fetch/TableFetch" const SuppressErrors = true @@ -46,10 +45,6 @@ export const createActions = context => { return $columns.some(col => col.name === name) || $sticky?.name === name } - const getFeatures = () => { - return new TableFetch({ API }).determineFeatureFlags() - } - return { table: { actions: { @@ -60,7 +55,6 @@ export const createActions = context => { getRow, isDatasourceValid, canUseColumn, - getFeatures, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/datasources/viewV2.js b/packages/frontend-core/src/components/grid/stores/datasources/viewV2.js index 35f57a5fc4..ed31d0ae44 100644 --- a/packages/frontend-core/src/components/grid/stores/datasources/viewV2.js +++ b/packages/frontend-core/src/components/grid/stores/datasources/viewV2.js @@ -1,5 +1,4 @@ import { get } from "svelte/store" -import ViewV2Fetch from "../../../../fetch/ViewV2Fetch" const SuppressErrors = true @@ -46,10 +45,6 @@ export const createActions = context => { ) } - const getFeatures = () => { - return new ViewV2Fetch({ API }).determineFeatureFlags() - } - return { viewV2: { actions: { @@ -60,7 +55,6 @@ export const createActions = context => { getRow, isDatasourceValid, canUseColumn, - getFeatures, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 51c46f8263..82185d6b91 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -114,10 +114,6 @@ export const createActions = context => { const $allFilters = get(allFilters) const $sort = get(sort) - // Determine how many rows to fetch per page - const features = datasource.actions.getFeatures() - const limit = features?.supportsPagination ? RowPageSize : null - // Create new fetch model const newFetch = fetchData({ API, @@ -126,8 +122,12 @@ export const createActions = context => { filter: $allFilters, sortColumn: $sort.column, sortOrder: $sort.order, - limit, + limit: RowPageSize, paginate: true, + + // Disable client side limiting, so that for queries and custom data + // sources we don't impose fake row limits. We want all the data. + clientSideLimiting: false, }, }) diff --git a/packages/frontend-core/src/fetch/DataFetch.js b/packages/frontend-core/src/fetch/DataFetch.js index 857072601e..92115efef0 100644 --- a/packages/frontend-core/src/fetch/DataFetch.js +++ b/packages/frontend-core/src/fetch/DataFetch.js @@ -43,6 +43,11 @@ export default class DataFetch { // Pagination config paginate: true, + + // Client side feature customisation + clientSideSearching: true, + clientSideSorting: true, + clientSideLimiting: true, } // State of the fetch @@ -208,24 +213,32 @@ export default class DataFetch { * Fetches some filtered, sorted and paginated data */ async getPage() { - const { sortColumn, sortOrder, sortType, limit } = this.options + const { + sortColumn, + sortOrder, + sortType, + limit, + clientSideSearching, + clientSideSorting, + clientSideLimiting, + } = this.options const { query } = get(this.store) // Get the actual data let { rows, info, hasNextPage, cursor, error } = await this.getData() // If we don't support searching, do a client search - if (!this.features.supportsSearch) { + if (!this.features.supportsSearch && clientSideSearching) { rows = runLuceneQuery(rows, query) } // If we don't support sorting, do a client-side sort - if (!this.features.supportsSort) { + if (!this.features.supportsSort && clientSideSorting) { rows = luceneSort(rows, sortColumn, sortOrder, sortType) } // If we don't support pagination, do a client-side limit - if (!this.features.supportsPagination) { + if (!this.features.supportsPagination && clientSideLimiting) { rows = luceneLimit(rows, limit) } From fd15f771ef8266f2f8852cd1a585106add862e40 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 26 Oct 2023 19:03:04 +0100 Subject: [PATCH 05/65] Refactor how relationship cells fetch and cache primary display columns to fix issues with store stale data --- .../grid/cells/RelationshipCell.svelte | 25 ++-------- .../src/components/grid/stores/cache.js | 49 +++++++++++++++++++ .../src/components/grid/stores/index.js | 2 + 3 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 packages/frontend-core/src/components/grid/stores/cache.js diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index 925c840478..0db022777f 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -1,27 +1,10 @@ - - + +{#if dividerState} + +{/if} +{#if heading.length > 0} +
+ {heading} +
+{/if} +
    + {#each dataSet as data} +
  • onSelect(data)} + on:keydown={e => { + if (e.key === "Enter" || e.key === "Space") { + onSelect(data) + } + }} + > + + {data.label} + + +
  • + {/each} +
+ + From df688b059669dc29d5cc5e374077e033012c084f Mon Sep 17 00:00:00 2001 From: Conor Webb Date: Tue, 31 Oct 2023 14:36:50 +0000 Subject: [PATCH 24/65] Fixed linting issues. --- .../settings/controls/DataSourceSelect.svelte | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte index a56614e4e5..0d8e728403 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte @@ -7,10 +7,8 @@ import { Button, Popover, - Divider, Select, Layout, - Heading, Drawer, DrawerContent, Icon, @@ -381,27 +379,6 @@ z-index: 99999999; overflow-y: scroll; } - .title { - padding: 0 var(--spacing-m) var(--spacing-s) var(--spacing-m); - } - - ul { - list-style: none; - padding-left: 0px; - margin: 0px; - width: 100%; - } - - li { - cursor: pointer; - margin: 0px; - padding: var(--spacing-s) var(--spacing-m); - font-size: var(--font-size-m); - } - - li:hover { - background-color: var(--spectrum-global-color-gray-200); - } .icon { margin-left: 8px; From 7e33aacbb1a77b0fa989d8647d9bf680c9b9ddfe Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 31 Oct 2023 14:48:23 +0000 Subject: [PATCH 25/65] Stop the sample data being identified as an external source. --- packages/server/src/integrations/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index b4fff0737a..33895e4fe1 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -11,6 +11,7 @@ import { InvalidColumns, NoEmptyFilterStrings } from "../constants" import { helpers } from "@budibase/shared-core" import * as external from "../api/controllers/table/external" import * as internal from "../api/controllers/table/internal" +import { DEFAULT_BB_DATASOURCE_ID } from "../db/defaultData/datasource_bb_default" const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const ROW_ID_REGEX = /^\[.*]$/g @@ -96,7 +97,8 @@ export function isInternalTableID(tableId: string) { export function isExternalTable(table: Table) { if ( table?.sourceId && - table.sourceId.includes(DocumentType.DATASOURCE + SEPARATOR) + table.sourceId.includes(DocumentType.DATASOURCE + SEPARATOR) && + table?.sourceId !== DEFAULT_BB_DATASOURCE_ID ) { return true } else if (table?.sourceType === TableSourceType.EXTERNAL) { From df6f8dad7e725e5c208bd65682e7db61a3a72d03 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 31 Oct 2023 16:56:19 +0000 Subject: [PATCH 26/65] Updating bull parameters to see if this helps with queue stalling. --- .../backend-core/src/queue/inMemoryQueue.ts | 2 +- packages/backend-core/src/queue/queue.ts | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index af2ec6dbaa..a8add7ecb6 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -36,7 +36,7 @@ class InMemoryQueue { * @param opts This is not used by the in memory queue as there is no real use * case when in memory, but is the same API as Bull */ - constructor(name: string, opts = null) { + constructor(name: string, opts?: any) { this._name = name this._opts = opts this._messages = [] diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts index 0658147709..c6b02921bf 100644 --- a/packages/backend-core/src/queue/queue.ts +++ b/packages/backend-core/src/queue/queue.ts @@ -2,10 +2,16 @@ import env from "../environment" import { getRedisOptions } from "../redis/utils" import { JobQueue } from "./constants" import InMemoryQueue from "./inMemoryQueue" -import BullQueue from "bull" +import BullQueue, { QueueOptions } from "bull" import { addListeners, StalledFn } from "./listeners" import * as timers from "../timers" +import * as Redis from "ioredis" +// the queue lock is held for 5 minutes +const QUEUE_LOCK_MS = 300000 +// queue lock is refreshed every 30 seconds +const QUEUE_LOCK_RENEW_INTERNAL_MS = 30000 +// cleanup the queue every 60 seconds const CLEANUP_PERIOD_MS = 60 * 1000 let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = [] let cleanupInterval: NodeJS.Timeout @@ -21,7 +27,14 @@ export function createQueue( opts: { removeStalledCb?: StalledFn } = {} ): BullQueue.Queue { const { opts: redisOpts, redisProtocolUrl } = getRedisOptions() - const queueConfig: any = redisProtocolUrl || { redis: redisOpts } + const queueConfig: QueueOptions = { + redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions), + settings: { + maxStalledCount: 0, + lockDuration: QUEUE_LOCK_MS, + lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS, + }, + } let queue: any if (!env.isTest()) { queue = new BullQueue(jobQueue, queueConfig) From 17319a6981a79ef41abe8fe4f7a26a6b02d4f9cd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 31 Oct 2023 17:52:39 +0000 Subject: [PATCH 27/65] Moving things around so that DEFAULT_BB_DATASOURCE_ID can be imported without cyclics occurring. --- .../server/src/api/controllers/application.ts | 7 ++----- packages/server/src/constants/index.ts | 5 +++++ .../src/db/defaultData/datasource_bb_default.ts | 16 +++++++++------- packages/server/src/integrations/utils.ts | 9 +++++---- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 4afd7b23f9..4e4c66858e 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -32,11 +32,8 @@ import { tenancy, users, } from "@budibase/backend-core" -import { USERS_TABLE_SCHEMA } from "../../constants" -import { - buildDefaultDocs, - DEFAULT_BB_DATASOURCE_ID, -} from "../../db/defaultData/datasource_bb_default" +import { USERS_TABLE_SCHEMA, DEFAULT_BB_DATASOURCE_ID } from "../../constants" +import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default" import { removeAppFromUserRoles } from "../../utilities/workerRequests" import { stringToReadStream } from "../../utilities" import { doesUserHaveLock } from "../../utilities/redis" diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index 1104fb9b29..fb5c42e7b8 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -172,3 +172,8 @@ export enum AutomationErrors { export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets export const MAX_AUTOMATION_RECURRING_ERRORS = 5 export const GOOGLE_SHEETS_PRIMARY_KEY = "rowNumber" +export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs" +export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory" +export const DEFAULT_EXPENSES_TABLE_ID = "ta_bb_expenses" +export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee" +export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default" diff --git a/packages/server/src/db/defaultData/datasource_bb_default.ts b/packages/server/src/db/defaultData/datasource_bb_default.ts index 584b76f879..b430f9ffb6 100644 --- a/packages/server/src/db/defaultData/datasource_bb_default.ts +++ b/packages/server/src/db/defaultData/datasource_bb_default.ts @@ -1,4 +1,12 @@ -import { AutoFieldSubTypes, FieldTypes } from "../../constants" +import { + AutoFieldSubTypes, + FieldTypes, + DEFAULT_BB_DATASOURCE_ID, + DEFAULT_INVENTORY_TABLE_ID, + DEFAULT_EMPLOYEE_TABLE_ID, + DEFAULT_EXPENSES_TABLE_ID, + DEFAULT_JOBS_TABLE_ID, +} from "../../constants" import { importToRows } from "../../api/controllers/table/utils" import { cloneDeep } from "lodash/fp" import LinkDocument from "../linkedRows/LinkDocument" @@ -16,12 +24,6 @@ import { TableSourceType, } from "@budibase/types" -export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs" -export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory" -export const DEFAULT_EXPENSES_TABLE_ID = "ta_bb_expenses" -export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee" -export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default" - const defaultDatasource = { _id: DEFAULT_BB_DATASOURCE_ID, type: dbCore.BUDIBASE_DATASOURCE_TYPE, diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index 33895e4fe1..fe8a9055b0 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -7,11 +7,12 @@ import { TableSourceType, } from "@budibase/types" import { DocumentType, SEPARATOR } from "../db/utils" -import { InvalidColumns, NoEmptyFilterStrings } from "../constants" +import { + InvalidColumns, + NoEmptyFilterStrings, + DEFAULT_BB_DATASOURCE_ID, +} from "../constants" import { helpers } from "@budibase/shared-core" -import * as external from "../api/controllers/table/external" -import * as internal from "../api/controllers/table/internal" -import { DEFAULT_BB_DATASOURCE_ID } from "../db/defaultData/datasource_bb_default" const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const ROW_ID_REGEX = /^\[.*]$/g From e05821d6d7c78320fe427abd8237738c61c88e31 Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Tue, 31 Oct 2023 18:16:58 +0000 Subject: [PATCH 28/65] License Management Test This test retrieves plans, creates checkout session, and updates license Essential changes have been made to linkStripeCustomer & updatePlan functions to support the test Modified "test:self:ci" to include 'licensing' instead of 'license' Modified environment.ts to include STRIPE_SECRET_KEY --- qa-core/package.json | 2 +- .../src/account-api/api/apis/LicenseAPI.ts | 10 +- qa-core/src/account-api/api/apis/StripeAPI.ts | 13 +- .../tests/licensing/license.manage.spec.ts | 113 ++++++++++++++++++ qa-core/src/environment.ts | 1 + 5 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 qa-core/src/account-api/tests/licensing/license.manage.spec.ts diff --git a/qa-core/package.json b/qa-core/package.json index d266ca9def..cfccd5e650 100644 --- a/qa-core/package.json +++ b/qa-core/package.json @@ -17,7 +17,7 @@ "test:notify": "node scripts/testResultsWebhook", "test:cloud:prod": "yarn run test --testPathIgnorePatterns=\\.integration\\.", "test:cloud:qa": "yarn run test", - "test:self:ci": "yarn run test --testPathIgnorePatterns=\\.integration\\. \\.cloud\\. \\.license\\.", + "test:self:ci": "yarn run test --testPathIgnorePatterns=\\.integration\\. \\.cloud\\. \\.licensing\\.", "serve:test:self:ci": "start-server-and-test dev:built http://localhost:4001/health test:self:ci", "serve": "start-server-and-test dev:built http://localhost:4001/health", "dev:built": "cd ../ && yarn dev:built" diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index b371f00f05..b3b54c5102 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -99,9 +99,15 @@ export default class LicenseAPI extends BaseAPI { }, opts) } - async updatePlan(opts: APIRequestOpts = { status: 200 }) { + async updatePlan( + priceId: string, + opts: APIRequestOpts = { status: 200 } + ) { return this.doRequest(() => { - return this.client.put(`/api/license/plan`) + return this.client.put(`/api/license/plan`, + { + body: { priceId }, + }) }, opts) } diff --git a/qa-core/src/account-api/api/apis/StripeAPI.ts b/qa-core/src/account-api/api/apis/StripeAPI.ts index c9c776e89b..6fef944206 100644 --- a/qa-core/src/account-api/api/apis/StripeAPI.ts +++ b/qa-core/src/account-api/api/apis/StripeAPI.ts @@ -38,9 +38,18 @@ export default class StripeAPI extends BaseAPI { }, opts) } - async linkStripeCustomer(opts: APIRequestOpts = { status: 200 }) { + async linkStripeCustomer( + accountId: string, + stripeCustomerId: string, + opts: APIRequestOpts = { status: 200 }) { return this.doRequest(() => { - return this.client.post(`/api/stripe/link`) + return this.client.post(`/api/stripe/link`, { + body: { + accountId, + stripeCustomerId + }, + internal: true, + }) }, opts) } diff --git a/qa-core/src/account-api/tests/licensing/license.manage.spec.ts b/qa-core/src/account-api/tests/licensing/license.manage.spec.ts new file mode 100644 index 0000000000..a62b733c24 --- /dev/null +++ b/qa-core/src/account-api/tests/licensing/license.manage.spec.ts @@ -0,0 +1,113 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixtures from "../../fixtures" +import {Hosting, PlanType} from "@budibase/types" + +describe("license management", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("retrieves plans, creates checkout session, and updates license", async () => { + // Create cloud account + const createAccountRequest = fixtures.accounts.generateAccount({ + hosting: Hosting.CLOUD, + }) + const [createAccountRes, account] = + await config.accountsApi.accounts.create(createAccountRequest, { + autoVerify: true, + }) + + // Self response has free license + await config.doInNewState(async () => { + await config.loginAsAccount(createAccountRequest) + const [selfRes, selfBody] = await config.api.accounts.self() + expect(selfBody.license.plan.type).toBe(PlanType.FREE) + }) + + // Retrieve plans + const [plansRes, planBody] = await config.api.licenses.getPlans() + + // Select priceId from premium plan + let premiumPriceId = null + let businessPriceId = '' + for (const plan of planBody) { + if (plan.type === PlanType.PREMIUM) { + premiumPriceId = plan.prices[0].priceId + } + if (plan.type === PlanType.BUSINESS) { + businessPriceId = plan.prices[0].priceId + } + } + + // Create checkout session for price + const checkoutSessionRes = await config.api.stripe.createCheckoutSession( + premiumPriceId + ) + const checkoutSessionUrl = checkoutSessionRes[1].url + expect(checkoutSessionUrl).toContain("checkout.stripe.com") + + // Create stripe customer + const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) + const customer = await stripe.customers.create({ + email: createAccountRequest.email + }) + + // Create payment method + const paymentMethod = await stripe.paymentMethods.create({ + type: 'card', + card: { + token: 'tok_visa' // Test Visa Card + }, + }) + + // Attach payment method to customer + await stripe.paymentMethods.attach(paymentMethod.id, { + customer: customer.id, + }) + + // Update customer + await stripe.customers.update(customer.id, { + invoice_settings: { + default_payment_method: paymentMethod.id, + }, + }) + + // Create subscription for premium plan + const subscription = await stripe.subscriptions.create({ + customer: customer.id, + items: [ + { + price: premiumPriceId, + quantity: 1, + }, + ], + default_payment_method: paymentMethod.id, + collection_method: 'charge_automatically', + }) + + await config.doInNewState(async () => { + // License updated from Free to Premium + await config.loginAsAccount(createAccountRequest) + await config.api.stripe.linkStripeCustomer(account.accountId, customer.id) + const [_, selfBodyPremium] = await config.api.accounts.self() + expect(selfBodyPremium.license.plan.type).toBe(PlanType.PREMIUM) + + // Create portal session - Check URL + const [portalRes, portalSessionBody] = await config.api.stripe.createPortalSession(customer.id) + expect(portalSessionBody.url).toContain("billing.stripe.com") + + // Update subscription from premium to business license + await config.api.licenses.updatePlan(businessPriceId) + + // License updated to Business + const [selfRes, selfBodyBusiness] = await config.api.accounts.self() + expect(selfBodyBusiness.license.plan.type).toBe(PlanType.BUSINESS) + }) + }) +}) diff --git a/qa-core/src/environment.ts b/qa-core/src/environment.ts index 0257b10831..a805503474 100644 --- a/qa-core/src/environment.ts +++ b/qa-core/src/environment.ts @@ -28,6 +28,7 @@ const env = { MARIADB_DB: process.env.MARIADB_DB, MARIADB_USER: process.env.MARIADB_USER, MARIADB_PASSWORD: process.env.MARIADB_PASSWORD, + STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, } export = env From 84079450f973ad957d8bd031329832d155603b1b Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Tue, 31 Oct 2023 18:20:27 +0000 Subject: [PATCH 29/65] lint --- .../src/account-api/api/apis/LicenseAPI.ts | 12 +- qa-core/src/account-api/api/apis/StripeAPI.ts | 9 +- .../tests/licensing/license.manage.spec.ts | 205 +++++++++--------- 3 files changed, 112 insertions(+), 114 deletions(-) diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index b3b54c5102..a9b0a35269 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -99,15 +99,11 @@ export default class LicenseAPI extends BaseAPI { }, opts) } - async updatePlan( - priceId: string, - opts: APIRequestOpts = { status: 200 } - ) { + async updatePlan(priceId: string, opts: APIRequestOpts = { status: 200 }) { return this.doRequest(() => { - return this.client.put(`/api/license/plan`, - { - body: { priceId }, - }) + return this.client.put(`/api/license/plan`, { + body: { priceId }, + }) }, opts) } diff --git a/qa-core/src/account-api/api/apis/StripeAPI.ts b/qa-core/src/account-api/api/apis/StripeAPI.ts index 6fef944206..5a4e810655 100644 --- a/qa-core/src/account-api/api/apis/StripeAPI.ts +++ b/qa-core/src/account-api/api/apis/StripeAPI.ts @@ -39,14 +39,15 @@ export default class StripeAPI extends BaseAPI { } async linkStripeCustomer( - accountId: string, - stripeCustomerId: string, - opts: APIRequestOpts = { status: 200 }) { + accountId: string, + stripeCustomerId: string, + opts: APIRequestOpts = { status: 200 } + ) { return this.doRequest(() => { return this.client.post(`/api/stripe/link`, { body: { accountId, - stripeCustomerId + stripeCustomerId, }, internal: true, }) diff --git a/qa-core/src/account-api/tests/licensing/license.manage.spec.ts b/qa-core/src/account-api/tests/licensing/license.manage.spec.ts index a62b733c24..a9109dad3a 100644 --- a/qa-core/src/account-api/tests/licensing/license.manage.spec.ts +++ b/qa-core/src/account-api/tests/licensing/license.manage.spec.ts @@ -1,113 +1,114 @@ import TestConfiguration from "../../config/TestConfiguration" import * as fixtures from "../../fixtures" -import {Hosting, PlanType} from "@budibase/types" +import { Hosting, PlanType } from "@budibase/types" describe("license management", () => { - const config = new TestConfiguration() + const config = new TestConfiguration() - beforeAll(async () => { - await config.beforeAll() + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("retrieves plans, creates checkout session, and updates license", async () => { + // Create cloud account + const createAccountRequest = fixtures.accounts.generateAccount({ + hosting: Hosting.CLOUD, + }) + const [createAccountRes, account] = + await config.accountsApi.accounts.create(createAccountRequest, { + autoVerify: true, + }) + + // Self response has free license + await config.doInNewState(async () => { + await config.loginAsAccount(createAccountRequest) + const [selfRes, selfBody] = await config.api.accounts.self() + expect(selfBody.license.plan.type).toBe(PlanType.FREE) }) - afterAll(async () => { - await config.afterAll() + // Retrieve plans + const [plansRes, planBody] = await config.api.licenses.getPlans() + + // Select priceId from premium plan + let premiumPriceId = null + let businessPriceId = "" + for (const plan of planBody) { + if (plan.type === PlanType.PREMIUM) { + premiumPriceId = plan.prices[0].priceId + } + if (plan.type === PlanType.BUSINESS) { + businessPriceId = plan.prices[0].priceId + } + } + + // Create checkout session for price + const checkoutSessionRes = await config.api.stripe.createCheckoutSession( + premiumPriceId + ) + const checkoutSessionUrl = checkoutSessionRes[1].url + expect(checkoutSessionUrl).toContain("checkout.stripe.com") + + // Create stripe customer + const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY) + const customer = await stripe.customers.create({ + email: createAccountRequest.email, }) - it("retrieves plans, creates checkout session, and updates license", async () => { - // Create cloud account - const createAccountRequest = fixtures.accounts.generateAccount({ - hosting: Hosting.CLOUD, - }) - const [createAccountRes, account] = - await config.accountsApi.accounts.create(createAccountRequest, { - autoVerify: true, - }) - - // Self response has free license - await config.doInNewState(async () => { - await config.loginAsAccount(createAccountRequest) - const [selfRes, selfBody] = await config.api.accounts.self() - expect(selfBody.license.plan.type).toBe(PlanType.FREE) - }) - - // Retrieve plans - const [plansRes, planBody] = await config.api.licenses.getPlans() - - // Select priceId from premium plan - let premiumPriceId = null - let businessPriceId = '' - for (const plan of planBody) { - if (plan.type === PlanType.PREMIUM) { - premiumPriceId = plan.prices[0].priceId - } - if (plan.type === PlanType.BUSINESS) { - businessPriceId = plan.prices[0].priceId - } - } - - // Create checkout session for price - const checkoutSessionRes = await config.api.stripe.createCheckoutSession( - premiumPriceId - ) - const checkoutSessionUrl = checkoutSessionRes[1].url - expect(checkoutSessionUrl).toContain("checkout.stripe.com") - - // Create stripe customer - const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) - const customer = await stripe.customers.create({ - email: createAccountRequest.email - }) - - // Create payment method - const paymentMethod = await stripe.paymentMethods.create({ - type: 'card', - card: { - token: 'tok_visa' // Test Visa Card - }, - }) - - // Attach payment method to customer - await stripe.paymentMethods.attach(paymentMethod.id, { - customer: customer.id, - }) - - // Update customer - await stripe.customers.update(customer.id, { - invoice_settings: { - default_payment_method: paymentMethod.id, - }, - }) - - // Create subscription for premium plan - const subscription = await stripe.subscriptions.create({ - customer: customer.id, - items: [ - { - price: premiumPriceId, - quantity: 1, - }, - ], - default_payment_method: paymentMethod.id, - collection_method: 'charge_automatically', - }) - - await config.doInNewState(async () => { - // License updated from Free to Premium - await config.loginAsAccount(createAccountRequest) - await config.api.stripe.linkStripeCustomer(account.accountId, customer.id) - const [_, selfBodyPremium] = await config.api.accounts.self() - expect(selfBodyPremium.license.plan.type).toBe(PlanType.PREMIUM) - - // Create portal session - Check URL - const [portalRes, portalSessionBody] = await config.api.stripe.createPortalSession(customer.id) - expect(portalSessionBody.url).toContain("billing.stripe.com") - - // Update subscription from premium to business license - await config.api.licenses.updatePlan(businessPriceId) - - // License updated to Business - const [selfRes, selfBodyBusiness] = await config.api.accounts.self() - expect(selfBodyBusiness.license.plan.type).toBe(PlanType.BUSINESS) - }) + // Create payment method + const paymentMethod = await stripe.paymentMethods.create({ + type: "card", + card: { + token: "tok_visa", // Test Visa Card + }, }) + + // Attach payment method to customer + await stripe.paymentMethods.attach(paymentMethod.id, { + customer: customer.id, + }) + + // Update customer + await stripe.customers.update(customer.id, { + invoice_settings: { + default_payment_method: paymentMethod.id, + }, + }) + + // Create subscription for premium plan + const subscription = await stripe.subscriptions.create({ + customer: customer.id, + items: [ + { + price: premiumPriceId, + quantity: 1, + }, + ], + default_payment_method: paymentMethod.id, + collection_method: "charge_automatically", + }) + + await config.doInNewState(async () => { + // License updated from Free to Premium + await config.loginAsAccount(createAccountRequest) + await config.api.stripe.linkStripeCustomer(account.accountId, customer.id) + const [_, selfBodyPremium] = await config.api.accounts.self() + expect(selfBodyPremium.license.plan.type).toBe(PlanType.PREMIUM) + + // Create portal session - Check URL + const [portalRes, portalSessionBody] = + await config.api.stripe.createPortalSession(customer.id) + expect(portalSessionBody.url).toContain("billing.stripe.com") + + // Update subscription from premium to business license + await config.api.licenses.updatePlan(businessPriceId) + + // License updated to Business + const [selfRes, selfBodyBusiness] = await config.api.accounts.self() + expect(selfBodyBusiness.license.plan.type).toBe(PlanType.BUSINESS) + }) + }) }) From 6cf1b13a0c7383b7f7576b25f242378e3da40827 Mon Sep 17 00:00:00 2001 From: Conor Webb Date: Wed, 1 Nov 2023 09:30:54 +0000 Subject: [PATCH 30/65] Feedback related amends. --- .../design/settings/controls/DataSourceSelect.svelte | 1 - .../controls/DataSourceSelectItem/SelectItem.svelte | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte index 0d8e728403..af4da10466 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte @@ -348,7 +348,6 @@ {#if otherSources?.length} {/if} -{#if heading.length > 0} +{#if heading}
{heading}
@@ -26,11 +26,6 @@ aria-selected="true" tabindex="0" on:click={() => onSelect(data)} - on:keydown={e => { - if (e.key === "Enter" || e.key === "Space") { - onSelect(data) - } - }} > {data.label} From 9c71ccd2c48876618819066cc5946ca2f3128b5e Mon Sep 17 00:00:00 2001 From: Conor Webb Date: Wed, 1 Nov 2023 09:34:10 +0000 Subject: [PATCH 31/65] Moved DataSourceSelect into related folder. Changed SelectedItem name. Updated DataSourceSelect imports. --- .../design/settings/componentSettings.js | 2 +- ...tItem.svelte => DataSourceCategory.svelte} | 0 .../DataSourceSelect.svelte | 20 +++++++++---------- .../settings/controls/SchemaSelect.svelte | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename packages/builder/src/components/design/settings/controls/DataSourceSelectItem/{SelectItem.svelte => DataSourceCategory.svelte} (100%) rename packages/builder/src/components/design/settings/controls/{ => DataSourceSelectItem}/DataSourceSelect.svelte (96%) diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index 232b4bef31..b81361dfa5 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -1,5 +1,5 @@ import { Checkbox, Select, RadioGroup, Stepper, Input } from "@budibase/bbui" -import DataSourceSelect from "./controls/DataSourceSelect.svelte" +import DataSourceSelect from "./controls/DataSourceSelectItem/DataSourceSelect.svelte" import S3DataSourceSelect from "./controls/S3DataSourceSelect.svelte" import DataProviderSelect from "./controls/DataProviderSelect.svelte" import ButtonActionEditor from "./controls/ButtonActionEditor/ButtonActionEditor.svelte" diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelectItem/SelectItem.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelectItem/DataSourceCategory.svelte similarity index 100% rename from packages/builder/src/components/design/settings/controls/DataSourceSelectItem/SelectItem.svelte rename to packages/builder/src/components/design/settings/controls/DataSourceSelectItem/DataSourceCategory.svelte diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelectItem/DataSourceSelect.svelte similarity index 96% rename from packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte rename to packages/builder/src/components/design/settings/controls/DataSourceSelectItem/DataSourceSelect.svelte index af4da10466..1f059ee972 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelectItem/DataSourceSelect.svelte @@ -30,7 +30,7 @@ import IntegrationQueryEditor from "components/integration/index.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte" - import DataSourceSelect from "components/design/settings/controls/DataSourceSelectItem/SelectItem.svelte" + import DataSourceCategory from "components/design/settings/controls/DataSourceSelectItem/DataSourceCategory.svelte" import { API } from "api" export let value = {} @@ -278,14 +278,14 @@