diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index 90ca6ffd9f..f10c44f81f 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -28,7 +28,9 @@ let loading = false let deleteConfirmationDialog - $: defaultName = getSequentialName($snippets, "MySnippet", x => x.name) + $: defaultName = getSequentialName($snippets, "MySnippet", { + getName: x => x.name, + }) $: key = snippet?.name $: name = snippet?.name || defaultName $: code = snippet?.code ? encodeJSBinding(snippet.code) : "" diff --git a/packages/builder/src/constants/backend/automations.js b/packages/builder/src/constants/backend/automations.ts similarity index 100% rename from packages/builder/src/constants/backend/automations.js rename to packages/builder/src/constants/backend/automations.ts diff --git a/packages/builder/src/constants/backend/backups.js b/packages/builder/src/constants/backend/backups.ts similarity index 100% rename from packages/builder/src/constants/backend/backups.js rename to packages/builder/src/constants/backend/backups.ts diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.ts similarity index 97% rename from packages/builder/src/constants/backend/index.js rename to packages/builder/src/constants/backend/index.ts index 6ddf4c2138..b7d3f584be 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.ts @@ -16,7 +16,10 @@ export { export const AUTO_COLUMN_SUB_TYPES = AutoFieldSubType -export const AUTO_COLUMN_DISPLAY_NAMES = { +export const AUTO_COLUMN_DISPLAY_NAMES: Record< + keyof typeof AUTO_COLUMN_SUB_TYPES, + string +> = { AUTO_ID: "Auto ID", CREATED_BY: "Created By", CREATED_AT: "Created At", @@ -209,13 +212,6 @@ export const Roles = { BUILDER: "BUILDER", } -export function isAutoColumnUserRelationship(subtype) { - return ( - subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY || - subtype === AUTO_COLUMN_SUB_TYPES.UPDATED_BY - ) -} - export const PrettyRelationshipDefinitions = { MANY: "Many rows", ONE: "One row", diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.ts similarity index 100% rename from packages/builder/src/constants/index.js rename to packages/builder/src/constants/index.ts diff --git a/packages/builder/src/helpers/duplicate.js b/packages/builder/src/helpers/duplicate.ts similarity index 86% rename from packages/builder/src/helpers/duplicate.js rename to packages/builder/src/helpers/duplicate.ts index 361e1faa25..b4740b3e52 100644 --- a/packages/builder/src/helpers/duplicate.js +++ b/packages/builder/src/helpers/duplicate.ts @@ -10,13 +10,13 @@ * * Repl */ -export const duplicateName = (name, allNames) => { +export const duplicateName = (name: string, allNames: string[]) => { const duplicatePattern = new RegExp(`\\s(\\d+)$`) const baseName = name.split(duplicatePattern)[0] const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`) // get the sequence from matched names - const sequence = [] + const sequence: number[] = [] allNames.filter(n => { if (n === baseName) { return true @@ -70,12 +70,18 @@ export const duplicateName = (name, allNames) => { * @param getName optional function to extract the name for an item, if not a * flat array of strings */ -export const getSequentialName = ( - items, - prefix, - { getName = x => x, numberFirstItem = false } = {} +export const getSequentialName = ( + items: T[] | null, + prefix: string | null, + { + getName, + numberFirstItem, + }: { + getName?: (item: T) => string + numberFirstItem?: boolean + } = {} ) => { - if (!prefix?.length || !getName) { + if (!prefix?.length) { return null } const trimmedPrefix = prefix.trim() @@ -85,7 +91,7 @@ export const getSequentialName = ( } let max = 0 items.forEach(item => { - const name = getName(item) + const name = getName?.(item) ?? item if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) { return } diff --git a/packages/builder/src/helpers/featureFlags.js b/packages/builder/src/helpers/featureFlags.ts similarity index 54% rename from packages/builder/src/helpers/featureFlags.js rename to packages/builder/src/helpers/featureFlags.ts index e9054e8a9c..faa57892e8 100644 --- a/packages/builder/src/helpers/featureFlags.js +++ b/packages/builder/src/helpers/featureFlags.ts @@ -1,7 +1,8 @@ +import { FeatureFlag } from "@budibase/types" import { auth } from "../stores/portal" import { get } from "svelte/store" -export const isEnabled = featureFlag => { +export const isEnabled = (featureFlag: FeatureFlag | `${FeatureFlag}`) => { const user = get(auth).user return !!user?.flags?.[featureFlag] } diff --git a/packages/builder/src/helpers/fetchData.js b/packages/builder/src/helpers/fetchData.ts similarity index 57% rename from packages/builder/src/helpers/fetchData.js rename to packages/builder/src/helpers/fetchData.ts index 085d0ae5b4..cffb8f4d7d 100644 --- a/packages/builder/src/helpers/fetchData.js +++ b/packages/builder/src/helpers/fetchData.ts @@ -1,13 +1,21 @@ import { writable } from "svelte/store" import { API } from "@/api" -export default function (url) { - const store = writable({ status: "LOADING", data: {}, error: {} }) +export default function (url: string) { + const store = writable<{ + status: "LOADING" | "SUCCESS" | "ERROR" + data: object + error?: unknown + }>({ + status: "LOADING", + data: {}, + error: {}, + }) async function get() { store.update(u => ({ ...u, status: "LOADING" })) try { - const data = await API.get({ url }) + const data = await API.get({ url }) store.set({ data, status: "SUCCESS" }) } catch (e) { store.set({ data: {}, error: e, status: "ERROR" }) diff --git a/packages/builder/src/helpers/helpers.js b/packages/builder/src/helpers/helpers.js deleted file mode 100644 index 99483d40e2..0000000000 --- a/packages/builder/src/helpers/helpers.js +++ /dev/null @@ -1,46 +0,0 @@ -import { last, flow } from "lodash/fp" - -export const buildStyle = styles => { - let str = "" - for (let s in styles) { - if (styles[s]) { - let key = convertCamel(s) - str += `${key}: ${styles[s]}; ` - } - } - return str -} - -export const convertCamel = str => { - return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) -} - -export const pipe = (arg, funcs) => flow(funcs)(arg) - -export const capitalise = s => { - if (!s) { - return s - } - return s.substring(0, 1).toUpperCase() + s.substring(1) -} - -export const lowercase = s => s.substring(0, 1).toLowerCase() + s.substring(1) - -export const lowercaseExceptFirst = s => - s.charAt(0) + s.substring(1).toLowerCase() - -export const get_name = s => (!s ? "" : last(s.split("/"))) - -export const get_capitalised_name = name => pipe(name, [get_name, capitalise]) - -export const isBuilderInputFocused = e => { - const activeTag = document.activeElement?.tagName.toLowerCase() - const inCodeEditor = document.activeElement?.classList?.contains("cm-content") - if ( - (inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) && - e.key !== "Escape" - ) { - return true - } - return false -} diff --git a/packages/builder/src/helpers/helpers.ts b/packages/builder/src/helpers/helpers.ts new file mode 100644 index 0000000000..f767e6f59f --- /dev/null +++ b/packages/builder/src/helpers/helpers.ts @@ -0,0 +1,50 @@ +import type { Many } from "lodash" +import { last, flow } from "lodash/fp" + +export const buildStyle = (styles: Record) => { + let str = "" + for (let s in styles) { + if (styles[s]) { + let key = convertCamel(s) + str += `${key}: ${styles[s]}; ` + } + } + return str +} + +export const convertCamel = (str: string) => { + return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) +} + +export const pipe = (arg: string, funcs: Many<(...args: any[]) => any>) => + flow(funcs)(arg) + +export const capitalise = (s: string) => { + if (!s) { + return s + } + return s.substring(0, 1).toUpperCase() + s.substring(1) +} + +export const lowercase = (s: string) => + s.substring(0, 1).toLowerCase() + s.substring(1) + +export const lowercaseExceptFirst = (s: string) => + s.charAt(0) + s.substring(1).toLowerCase() + +export const get_name = (s: string) => (!s ? "" : last(s.split("/"))) + +export const get_capitalised_name = (name: string) => + pipe(name, [get_name, capitalise]) + +export const isBuilderInputFocused = (e: KeyboardEvent) => { + const activeTag = document.activeElement?.tagName.toLowerCase() + const inCodeEditor = document.activeElement?.classList?.contains("cm-content") + if ( + (inCodeEditor || ["input", "textarea"].indexOf(activeTag!) !== -1) && + e.key !== "Escape" + ) { + return true + } + return false +} diff --git a/packages/builder/src/helpers/index.js b/packages/builder/src/helpers/index.ts similarity index 100% rename from packages/builder/src/helpers/index.js rename to packages/builder/src/helpers/index.ts diff --git a/packages/builder/src/helpers/keyUtils.js b/packages/builder/src/helpers/keyUtils.js deleted file mode 100644 index 8d6dfb06dc..0000000000 --- a/packages/builder/src/helpers/keyUtils.js +++ /dev/null @@ -1,7 +0,0 @@ -function handleEnter(fnc) { - return e => e.key === "Enter" && fnc() -} - -export const keyUtils = { - handleEnter, -} diff --git a/packages/builder/src/helpers/keyUtils.ts b/packages/builder/src/helpers/keyUtils.ts new file mode 100644 index 0000000000..f78b05ec2b --- /dev/null +++ b/packages/builder/src/helpers/keyUtils.ts @@ -0,0 +1,7 @@ +function handleEnter(fnc: () => void) { + return (e: KeyboardEvent) => e.key === "Enter" && fnc() +} + +export const keyUtils = { + handleEnter, +} diff --git a/packages/builder/src/helpers/pagination.js b/packages/builder/src/helpers/pagination.ts similarity index 76% rename from packages/builder/src/helpers/pagination.js rename to packages/builder/src/helpers/pagination.ts index 122973f1a1..0280cd1052 100644 --- a/packages/builder/src/helpers/pagination.js +++ b/packages/builder/src/helpers/pagination.ts @@ -1,6 +1,16 @@ import { writable } from "svelte/store" -function defaultValue() { +interface PaginationStore { + nextPage: string | null | undefined + page: string | null | undefined + hasPrevPage: boolean + hasNextPage: boolean + loading: boolean + pageNumber: number + pages: string[] +} + +function defaultValue(): PaginationStore { return { nextPage: null, page: undefined, @@ -29,13 +39,13 @@ export function createPaginationStore() { update(state => { state.pageNumber++ state.page = state.nextPage - state.pages.push(state.page) + state.pages.push(state.page!) state.hasPrevPage = state.pageNumber > 1 return state }) } - function fetched(hasNextPage, nextPage) { + function fetched(hasNextPage: boolean, nextPage: string) { update(state => { state.hasNextPage = hasNextPage state.nextPage = nextPage diff --git a/packages/builder/src/helpers/planTitle.js b/packages/builder/src/helpers/planTitle.ts similarity index 86% rename from packages/builder/src/helpers/planTitle.js rename to packages/builder/src/helpers/planTitle.ts index c08b8bf3fe..ab342b4d93 100644 --- a/packages/builder/src/helpers/planTitle.js +++ b/packages/builder/src/helpers/planTitle.ts @@ -1,6 +1,6 @@ import { PlanType } from "@budibase/types" -export function getFormattedPlanName(userPlanType) { +export function getFormattedPlanName(userPlanType: PlanType) { let planName switch (userPlanType) { case PlanType.PRO: @@ -29,6 +29,6 @@ export function getFormattedPlanName(userPlanType) { return `${planName} Plan` } -export function isPremiumOrAbove(userPlanType) { +export function isPremiumOrAbove(userPlanType: PlanType) { return ![PlanType.PRO, PlanType.TEAM, PlanType.FREE].includes(userPlanType) } diff --git a/packages/builder/src/helpers/sanitizeUrl.js b/packages/builder/src/helpers/sanitizeUrl.ts similarity index 88% rename from packages/builder/src/helpers/sanitizeUrl.js rename to packages/builder/src/helpers/sanitizeUrl.ts index 4d00c503fb..5b76930037 100644 --- a/packages/builder/src/helpers/sanitizeUrl.js +++ b/packages/builder/src/helpers/sanitizeUrl.ts @@ -1,4 +1,4 @@ -export default function (url) { +export default function (url: string) { return url .split("/") .map(part => { diff --git a/packages/builder/src/helpers/tests/duplicate.test.js b/packages/builder/src/helpers/tests/duplicate.test.ts similarity index 100% rename from packages/builder/src/helpers/tests/duplicate.test.js rename to packages/builder/src/helpers/tests/duplicate.test.ts diff --git a/packages/builder/src/helpers/tests/nameHelpers.spec.js b/packages/builder/src/helpers/tests/nameHelpers.spec.ts similarity index 100% rename from packages/builder/src/helpers/tests/nameHelpers.spec.js rename to packages/builder/src/helpers/tests/nameHelpers.spec.ts diff --git a/packages/builder/src/helpers/utils.js b/packages/builder/src/helpers/utils.js deleted file mode 100644 index 7af7d4ad22..0000000000 --- a/packages/builder/src/helpers/utils.js +++ /dev/null @@ -1,72 +0,0 @@ -import { FieldType } from "@budibase/types" -import { ActionStepID } from "@/constants/backend/automations" -import { TableNames } from "@/constants" -import { - AUTO_COLUMN_DISPLAY_NAMES, - AUTO_COLUMN_SUB_TYPES, - FIELDS, - isAutoColumnUserRelationship, -} from "@/constants/backend" - -export function getAutoColumnInformation(enabled = true) { - let info = {} - for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) { - // Because it's possible to replicate the functionality of CREATED_AT and - // CREATED_BY columns with user column default values, we disable their creation - if ( - subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT || - subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY - ) { - continue - } - - info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] } - } - return info -} - -export function buildAutoColumn(tableName, name, subtype) { - let type, constraints - switch (subtype) { - case AUTO_COLUMN_SUB_TYPES.UPDATED_BY: - case AUTO_COLUMN_SUB_TYPES.CREATED_BY: - type = FieldType.LINK - constraints = FIELDS.LINK.constraints - break - case AUTO_COLUMN_SUB_TYPES.AUTO_ID: - type = FieldType.NUMBER - constraints = FIELDS.NUMBER.constraints - break - case AUTO_COLUMN_SUB_TYPES.UPDATED_AT: - case AUTO_COLUMN_SUB_TYPES.CREATED_AT: - type = FieldType.DATETIME - constraints = FIELDS.DATETIME.constraints - break - default: - type = FieldType.STRING - constraints = FIELDS.STRING.constraints - break - } - if (Object.values(AUTO_COLUMN_SUB_TYPES).indexOf(subtype) === -1) { - throw "Cannot build auto column with supplied subtype" - } - const base = { - name, - type, - subtype, - icon: "ri-magic-line", - autocolumn: true, - constraints, - } - if (isAutoColumnUserRelationship(subtype)) { - base.tableId = TableNames.USERS - base.fieldName = `${tableName}-${name}` - } - return base -} - -export function checkForCollectStep(automation) { - return automation.definition.steps.some( - step => step.stepId === ActionStepID.COLLECT - ) -} diff --git a/packages/builder/src/helpers/utils.ts b/packages/builder/src/helpers/utils.ts new file mode 100644 index 0000000000..140daaef3b --- /dev/null +++ b/packages/builder/src/helpers/utils.ts @@ -0,0 +1,96 @@ +import { + AutoFieldSubType, + Automation, + DateFieldMetadata, + FieldType, + NumberFieldMetadata, + RelationshipFieldMetadata, + RelationshipType, +} from "@budibase/types" +import { ActionStepID } from "@/constants/backend/automations" +import { TableNames } from "@/constants" +import { + AUTO_COLUMN_DISPLAY_NAMES, + AUTO_COLUMN_SUB_TYPES, + FIELDS, +} from "@/constants/backend" +import { utils } from "@budibase/shared-core" + +type AutoColumnInformation = Partial< + Record +> + +export function getAutoColumnInformation( + enabled = true +): AutoColumnInformation { + const info: AutoColumnInformation = {} + for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) { + // Because it's possible to replicate the functionality of CREATED_AT and + // CREATED_BY columns with user column default values, we disable their creation + if ( + subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT || + subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY + ) { + continue + } + const typedKey = key as keyof typeof AUTO_COLUMN_SUB_TYPES + info[subtype] = { + enabled, + name: AUTO_COLUMN_DISPLAY_NAMES[typedKey], + } + } + return info +} + +export function buildAutoColumn( + tableName: string, + name: string, + subtype: AutoFieldSubType +): RelationshipFieldMetadata | NumberFieldMetadata | DateFieldMetadata { + const base = { + name, + icon: "ri-magic-line", + autocolumn: true, + } + + switch (subtype) { + case AUTO_COLUMN_SUB_TYPES.UPDATED_BY: + case AUTO_COLUMN_SUB_TYPES.CREATED_BY: + return { + ...base, + type: FieldType.LINK, + subtype, + constraints: FIELDS.LINK.constraints, + tableId: TableNames.USERS, + fieldName: `${tableName}-${name}`, + relationshipType: RelationshipType.MANY_TO_ONE, + } + + case AUTO_COLUMN_SUB_TYPES.AUTO_ID: + return { + ...base, + type: FieldType.NUMBER, + subtype, + constraints: FIELDS.NUMBER.constraints, + } + case AUTO_COLUMN_SUB_TYPES.UPDATED_AT: + case AUTO_COLUMN_SUB_TYPES.CREATED_AT: + return { + ...base, + type: FieldType.DATETIME, + subtype, + constraints: FIELDS.DATETIME.constraints, + } + + default: + throw utils.unreachable(subtype, { + message: "Cannot build auto column with supplied subtype", + }) + } +} + +export function checkForCollectStep(automation: Automation) { + return automation.definition.steps.some( + step => step.stepId === ActionStepID.COLLECT + ) +} diff --git a/packages/builder/src/helpers/warnings.js b/packages/builder/src/helpers/warnings.ts similarity index 85% rename from packages/builder/src/helpers/warnings.js rename to packages/builder/src/helpers/warnings.ts index ad943a8578..ae6c65666c 100644 --- a/packages/builder/src/helpers/warnings.js +++ b/packages/builder/src/helpers/warnings.ts @@ -1,4 +1,4 @@ -export const suppressWarnings = warnings => { +export const suppressWarnings = (warnings: string[]) => { if (!warnings?.length) { return } diff --git a/packages/builder/src/stores/builder/rowActions.ts b/packages/builder/src/stores/builder/rowActions.ts index 9576eccd1b..2b3077926e 100644 --- a/packages/builder/src/stores/builder/rowActions.ts +++ b/packages/builder/src/stores/builder/rowActions.ts @@ -62,7 +62,7 @@ export class RowActionStore extends BudiStore { const existingRowActions = get(this)[tableId] || [] name = getSequentialName(existingRowActions, "New row action ", { getName: x => x.name, - }) + })! } if (!name) {