diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 4b241c5165..14cc110214 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -204,7 +204,7 @@ jobs: check-pro-submodule: runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase') steps: - name: Checkout repo and submodules uses: actions/checkout@v3 @@ -254,7 +254,7 @@ jobs: check-accountportal-submodule: runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase') steps: - name: Checkout repo and submodules uses: actions/checkout@v3 diff --git a/.github/workflows/close-featurebranch.yml b/.github/workflows/close-featurebranch.yml index 46cb781730..5da3eb52cd 100644 --- a/.github/workflows/close-featurebranch.yml +++ b/.github/workflows/close-featurebranch.yml @@ -2,9 +2,7 @@ name: close-featurebranch on: pull_request: - types: [closed] - branches: - - master + types: [closed, unlabeled] workflow_dispatch: inputs: BRANCH: @@ -14,6 +12,9 @@ on: jobs: release: + if: | + (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'feature-branch')) || + github.event.label.name == 'feature-branch' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/deploy-featurebranch.yml b/.github/workflows/deploy-featurebranch.yml index c70f2fff20..a5636fe912 100644 --- a/.github/workflows/deploy-featurebranch.yml +++ b/.github/workflows/deploy-featurebranch.yml @@ -2,12 +2,19 @@ name: deploy-featurebranch on: pull_request: - branches: - - master + types: [ + labeled, + # default types below (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) + opened, + synchronize, + reopened, + ] jobs: release: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' + if: | + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase') && + contains(github.event.pull_request.labels.*.name, 'feature-branch') runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.vscode/settings.json b/.vscode/settings.json index ece537efac..e22d5a8866 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" }, "editor.defaultFormatter": "esbenp.prettier-vscode", "[json]": { diff --git a/charts/budibase/README.md b/charts/budibase/README.md index d8191026ce..342011bdb1 100644 --- a/charts/budibase/README.md +++ b/charts/budibase/README.md @@ -157,6 +157,17 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml | services.apps.replicaCount | int | `1` | The number of apps replicas to run. | | services.apps.resources | object | `{}` | The resources to use for apps pods. See for more information on how to set these. | | services.apps.startupProbe | object | HTTP health checks. | Startup probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.automationWorkers.autoscaling.enabled | bool | `false` | Whether to enable horizontal pod autoscaling for the apps service. | +| services.automationWorkers.autoscaling.maxReplicas | int | `10` | | +| services.automationWorkers.autoscaling.minReplicas | int | `1` | | +| services.automationWorkers.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the automation worker service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the automation worker pods. | +| services.automationWorkers.enabled | bool | `true` | Whether or not to enable the automation worker service. If you disable this, automations will be processed by the apps service. | +| services.automationWorkers.livenessProbe | object | HTTP health checks. | Liveness probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.automationWorkers.logLevel | string | `"info"` | The log level for the automation worker service. | +| services.automationWorkers.readinessProbe | object | HTTP health checks. | Readiness probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: | +| services.automationWorkers.replicaCount | int | `1` | The number of automation worker replicas to run. | +| services.automationWorkers.resources | object | `{}` | The resources to use for automation worker pods. See for more information on how to set these. | +| services.automationWorkers.startupProbe | object | HTTP health checks. | Startup probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: | | services.couchdb.backup.enabled | bool | `false` | Whether or not to enable periodic CouchDB backups. This works by replicating to another CouchDB instance. | | services.couchdb.backup.interval | string | `""` | Backup interval in seconds | | services.couchdb.backup.resources | object | `{}` | The resources to use for CouchDB backup pods. See for more information on how to set these. | diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 7358e474ca..9fb435c2a3 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -192,7 +192,14 @@ spec: - name: NODE_TLS_REJECT_UNAUTHORIZED value: {{ .Values.services.tlsRejectUnauthorized }} {{ end }} - + {{- if .Values.services.automationWorkers.enabled }} + - name: APP_FEATURES + value: "api" + {{- end }} + {{- range .Values.services.apps.extraEnv }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }} imagePullPolicy: Always {{- if .Values.services.apps.startupProbe }} diff --git a/charts/budibase/templates/automation-worker-service-deployment.yaml b/charts/budibase/templates/automation-worker-service-deployment.yaml new file mode 100644 index 0000000000..46be6a4435 --- /dev/null +++ b/charts/budibase/templates/automation-worker-service-deployment.yaml @@ -0,0 +1,248 @@ +{{- if .Values.services.automationWorkers.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: +{{ if .Values.services.automationWorkers.deploymentAnnotations }} +{{- toYaml .Values.services.automationWorkers.deploymentAnnotations | indent 4 -}} +{{ end }} + labels: + io.kompose.service: automation-worker-service +{{ if .Values.services.automationWorkers.deploymentLabels }} +{{- toYaml .Values.services.automationWorkers.deploymentLabels | indent 4 -}} +{{ end }} + name: automation-worker-service +spec: + replicas: {{ .Values.services.automationWorkers.replicaCount }} + selector: + matchLabels: + io.kompose.service: automation-worker-service + strategy: + type: RollingUpdate + template: + metadata: + annotations: +{{ if .Values.services.automationWorkers.templateAnnotations }} +{{- toYaml .Values.services.automationWorkers.templateAnnotations | indent 8 -}} +{{ end }} + labels: + io.kompose.service: automation-worker-service +{{ if .Values.services.automationWorkers.templateLabels }} +{{- toYaml .Values.services.automationWorkers.templateLabels | indent 8 -}} +{{ end }} + spec: + containers: + - env: + - name: BUDIBASE_ENVIRONMENT + value: {{ .Values.globals.budibaseEnv }} + - name: DEPLOYMENT_ENVIRONMENT + value: "kubernetes" + - name: COUCH_DB_URL + {{ if .Values.services.couchdb.url }} + value: {{ .Values.services.couchdb.url }} + {{ else }} + value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }} + {{ end }} + {{ if .Values.services.couchdb.enabled }} + - name: COUCH_DB_USER + valueFrom: + secretKeyRef: + name: {{ template "couchdb.fullname" . }} + key: adminUsername + - name: COUCH_DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "couchdb.fullname" . }} + key: adminPassword + {{ end }} + - name: ENABLE_ANALYTICS + value: {{ .Values.globals.enableAnalytics | quote }} + - name: API_ENCRYPTION_KEY + value: {{ .Values.globals.apiEncryptionKey | quote }} + - name: HTTP_LOGGING + value: {{ .Values.services.automationWorkers.httpLogging | quote }} + - name: INTERNAL_API_KEY + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: internalApiKey + - name: INTERNAL_API_KEY_FALLBACK + value: {{ .Values.globals.internalApiKeyFallback | quote }} + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: jwtSecret + - name: JWT_SECRET_FALLBACK + value: {{ .Values.globals.jwtSecretFallback | quote }} + {{ if .Values.services.objectStore.region }} + - name: AWS_REGION + value: {{ .Values.services.objectStore.region }} + {{ end }} + - name: MINIO_ENABLED + value: {{ .Values.services.objectStore.minio | quote }} + - name: MINIO_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: objectStoreAccess + - name: MINIO_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ template "budibase.fullname" . }} + key: objectStoreSecret + - name: CLOUDFRONT_CDN + value: {{ .Values.services.objectStore.cloudfront.cdn | quote }} + - name: CLOUDFRONT_PUBLIC_KEY_ID + value: {{ .Values.services.objectStore.cloudfront.publicKeyId | quote }} + - name: CLOUDFRONT_PRIVATE_KEY_64 + value: {{ .Values.services.objectStore.cloudfront.privateKey64 | quote }} + - name: MINIO_URL + value: {{ .Values.services.objectStore.url }} + - name: PLUGIN_BUCKET_NAME + value: {{ .Values.services.objectStore.pluginBucketName | quote }} + - name: APPS_BUCKET_NAME + value: {{ .Values.services.objectStore.appsBucketName | quote }} + - name: GLOBAL_BUCKET_NAME + value: {{ .Values.services.objectStore.globalBucketName | quote }} + - name: BACKUPS_BUCKET_NAME + value: {{ .Values.services.objectStore.backupsBucketName | quote }} + - name: PORT + value: {{ .Values.services.automationWorkers.port | quote }} + {{ if .Values.services.worker.publicApiRateLimitPerSecond }} + - name: API_REQ_LIMIT_PER_SEC + value: {{ .Values.globals.automationWorkers.publicApiRateLimitPerSecond | quote }} + {{ end }} + - name: MULTI_TENANCY + value: {{ .Values.globals.multiTenancy | quote }} + - name: OFFLINE_MODE + value: {{ .Values.globals.offlineMode | quote }} + - name: LOG_LEVEL + value: {{ .Values.services.automationWorkers.logLevel | quote }} + - name: REDIS_PASSWORD + value: {{ .Values.services.redis.password }} + - name: REDIS_URL + {{ if .Values.services.redis.url }} + value: {{ .Values.services.redis.url }} + {{ else }} + value: redis-service:{{ .Values.services.redis.port }} + {{ end }} + - name: SELF_HOSTED + value: {{ .Values.globals.selfHosted | quote }} + - name: POSTHOG_TOKEN + value: {{ .Values.globals.posthogToken | quote }} + - name: WORKER_URL + value: http://worker-service:{{ .Values.services.worker.port }} + - name: PLATFORM_URL + value: {{ .Values.globals.platformUrl | quote }} + - name: ACCOUNT_PORTAL_URL + value: {{ .Values.globals.accountPortalUrl | quote }} + - name: ACCOUNT_PORTAL_API_KEY + value: {{ .Values.globals.accountPortalApiKey | quote }} + - name: COOKIE_DOMAIN + value: {{ .Values.globals.cookieDomain | quote }} + - name: HTTP_MIGRATIONS + value: {{ .Values.globals.httpMigrations | quote }} + - name: GOOGLE_CLIENT_ID + value: {{ .Values.globals.google.clientId | quote }} + - name: GOOGLE_CLIENT_SECRET + value: {{ .Values.globals.google.secret | quote }} + - name: AUTOMATION_MAX_ITERATIONS + value: {{ .Values.globals.automationMaxIterations | quote }} + - name: TENANT_FEATURE_FLAGS + value: {{ .Values.globals.tenantFeatureFlags | quote }} + - name: ENCRYPTION_KEY + value: {{ .Values.globals.bbEncryptionKey | quote }} + {{ if .Values.globals.bbAdminUserEmail }} + - name: BB_ADMIN_USER_EMAIL + value: {{ .Values.globals.bbAdminUserEmail | quote }} + {{ end }} + {{ if .Values.globals.bbAdminUserPassword }} + - name: BB_ADMIN_USER_PASSWORD + value: {{ .Values.globals.bbAdminUserPassword | quote }} + {{ end }} + {{ if .Values.globals.pluginsDir }} + - name: PLUGINS_DIR + value: {{ .Values.globals.pluginsDir | quote }} + {{ end }} + {{ if .Values.services.automationWorkers.nodeDebug }} + - name: NODE_DEBUG + value: {{ .Values.services.automationWorkers.nodeDebug | quote }} + {{ end }} + {{ if .Values.globals.datadogApmEnabled }} + - name: DD_LOGS_INJECTION + value: {{ .Values.globals.datadogApmEnabled | quote }} + - name: DD_APM_ENABLED + value: {{ .Values.globals.datadogApmEnabled | quote }} + - name: DD_APM_DD_URL + value: https://trace.agent.datadoghq.eu + {{ end }} + {{ if .Values.globals.globalAgentHttpProxy }} + - name: GLOBAL_AGENT_HTTP_PROXY + value: {{ .Values.globals.globalAgentHttpProxy | quote }} + {{ end }} + {{ if .Values.globals.globalAgentHttpsProxy }} + - name: GLOBAL_AGENT_HTTPS_PROXY + value: {{ .Values.globals.globalAgentHttpsProxy | quote }} + {{ end }} + {{ if .Values.globals.globalAgentNoProxy }} + - name: GLOBAL_AGENT_NO_PROXY + value: {{ .Values.globals.globalAgentNoProxy | quote }} + {{ end }} + {{ if .Values.services.tlsRejectUnauthorized }} + - name: NODE_TLS_REJECT_UNAUTHORIZED + value: {{ .Values.services.tlsRejectUnauthorized }} + {{ end }} + - name: APP_FEATURES + value: "automations" + {{- range .Values.services.automationWorkers.extraEnv }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + + image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }} + imagePullPolicy: Always + {{- if .Values.services.automationWorkers.startupProbe }} + {{- with .Values.services.automationWorkers.startupProbe }} + startupProbe: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + {{- if .Values.services.automationWorkers.livenessProbe }} + {{- with .Values.services.automationWorkers.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + {{- if .Values.services.automationWorkers.readinessProbe }} + {{- with .Values.services.automationWorkers.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + name: bbautomationworker + ports: + - containerPort: {{ .Values.services.automationWorkers.port }} + {{ with .Values.services.automationWorkers.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{ end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{ if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName | quote }} + {{ end }} + {{ if .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml .Values.imagePullSecrets | nindent 6 }} + {{ end }} + restartPolicy: Always + serviceAccountName: "" +status: {} +{{- end }} \ No newline at end of file diff --git a/charts/budibase/templates/automation-worker-service-hpa.yaml b/charts/budibase/templates/automation-worker-service-hpa.yaml new file mode 100644 index 0000000000..f29223b61b --- /dev/null +++ b/charts/budibase/templates/automation-worker-service-hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.services.automationWorkers.autoscaling.enabled }} +apiVersion: {{ ternary "autoscaling/v2" "autoscaling/v2beta2" (.Capabilities.APIVersions.Has "autoscaling/v2") }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "budibase.fullname" . }}-apps + labels: + {{- include "budibase.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: automation-worker-service + minReplicas: {{ .Values.services.automationWorkers.autoscaling.minReplicas }} + maxReplicas: {{ .Values.services.automationWorkers.autoscaling.maxReplicas }} + metrics: + {{- if .Values.services.automationWorkers.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.services.automationWorkers.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.services.automationWorkers.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.services.automationWorkers.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index 6427aa70e8..1d90aaf954 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -182,6 +182,10 @@ spec: - name: NODE_TLS_REJECT_UNAUTHORIZED value: {{ .Values.services.tlsRejectUnauthorized }} {{ end }} + {{- range .Values.services.worker.extraEnv }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} image: budibase/worker:{{ .Values.globals.appVersion | default .Chart.AppVersion }} imagePullPolicy: Always {{- if .Values.services.worker.startupProbe }} diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 13054e75fc..09262df463 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -220,6 +220,9 @@ services: # # for more information on how to set these. resources: {} + # -- Extra environment variables to set for apps pods. Takes a list of + # name=value pairs. + extraEnv: [] # -- Startup probe configuration for apps pods. You shouldn't need to # change this, but if you want to you can find more information here: # @@ -272,6 +275,78 @@ services: # and resources set for the apps pods. targetCPUUtilizationPercentage: 80 + automationWorkers: + # -- Whether or not to enable the automation worker service. If you disable this, + # automations will be processed by the apps service. + enabled: true + # @ignore (you shouldn't need to change this) + port: 4002 + # -- The number of automation worker replicas to run. + replicaCount: 1 + # -- The log level for the automation worker service. + logLevel: info + # -- The resources to use for automation worker pods. See + # + # for more information on how to set these. + resources: {} + # -- Extra environment variables to set for automation worker pods. Takes a list of + # name=value pairs. + extraEnv: [] + # -- Startup probe configuration for automation worker pods. You shouldn't + # need to change this, but if you want to you can find more information + # here: + # + # @default -- HTTP health checks. + startupProbe: + # @ignore + httpGet: + path: /health + port: 4002 + scheme: HTTP + # @ignore + failureThreshold: 30 + # @ignore + periodSeconds: 3 + # -- Readiness probe configuration for automation worker pods. You shouldn't + # need to change this, but if you want to you can find more information + # here: + # + # @default -- HTTP health checks. + readinessProbe: + # @ignore + httpGet: + path: /health + port: 4002 + scheme: HTTP + # @ignore + periodSeconds: 3 + # @ignore + failureThreshold: 1 + # -- Liveness probe configuration for automation worker pods. You shouldn't + # need to change this, but if you want to you can find more information + # here: + # + # @default -- HTTP health checks. + livenessProbe: + # @ignore + httpGet: + path: /health + port: 4002 + scheme: HTTP + # @ignore + failureThreshold: 3 + # @ignore + periodSeconds: 30 + autoscaling: + # -- Whether to enable horizontal pod autoscaling for the apps service. + enabled: false + minReplicas: 1 + maxReplicas: 10 + # -- Target CPU utilization percentage for the automation worker service. + # Note that for autoscaling to work, you will need to have metrics-server + # configured, and resources set for the automation worker pods. + targetCPUUtilizationPercentage: 80 + worker: # @ignore (you shouldn't need to change this) port: 4003 @@ -285,6 +360,9 @@ services: # # for more information on how to set these. resources: {} + # -- Extra environment variables to set for worker pods. Takes a list of + # name=value pairs. + extraEnv: [] # -- Startup probe configuration for worker pods. You shouldn't need to # change this, but if you want to you can find more information here: # diff --git a/hosting/proxy/nginx.prod.conf b/hosting/proxy/nginx.prod.conf index 88f9645f80..65cc3ff390 100644 --- a/hosting/proxy/nginx.prod.conf +++ b/hosting/proxy/nginx.prod.conf @@ -257,6 +257,7 @@ http { access_log off; allow 127.0.0.1; + allow 10.0.0.0/8; deny all; location /nginx_status { diff --git a/lerna.json b/lerna.json index 3d309f0881..6a7fb47923 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.38", + "version": "2.13.45", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/account-portal b/packages/account-portal index a0b13270c3..a1832145f0 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit a0b13270c36dd188e2a953d026b4560a1208008e +Subproject commit a1832145f08fca9b923cc145930d5d3a4c2f1d1f diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts index 24e519dc7f..5cafe418d7 100644 --- a/packages/backend-core/src/cache/writethrough.ts +++ b/packages/backend-core/src/cache/writethrough.ts @@ -56,11 +56,8 @@ async function put( const writeDb = async (toWrite: any) => { // doc should contain the _id and _rev const response = await db.put(toWrite, { force: true }) - output = { - ...doc, - _id: response.id, - _rev: response.rev, - } + output._id = response.id + output._rev = response.rev } try { await writeDb(doc) diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index 8ef34196ed..aee099e10a 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -11,24 +11,7 @@ export enum Cookie { OIDC_CONFIG = "budibase:oidc:config", } -export enum Header { - API_KEY = "x-budibase-api-key", - LICENSE_KEY = "x-budibase-license-key", - API_VER = "x-budibase-api-version", - APP_ID = "x-budibase-app-id", - SESSION_ID = "x-budibase-session-id", - TYPE = "x-budibase-type", - PREVIEW_ROLE = "x-budibase-role", - TENANT_ID = "x-budibase-tenant-id", - VERIFICATION_CODE = "x-budibase-verification-code", - RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code", - RESET_PASSWORD_CODE = "x-budibase-reset-password-code", - RETURN_RESET_PASSWORD_CODE = "x-budibase-return-reset-password-code", - TOKEN = "x-budibase-token", - CSRF_TOKEN = "x-csrf-token", - CORRELATION_ID = "x-budibase-correlation-id", - AUTHORIZATION = "authorization", -} +export { Header } from "@budibase/shared-core" export enum GlobalRole { OWNER = "owner", diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index a8add7ecb6..ac7cdf550b 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -68,6 +68,10 @@ class InMemoryQueue { }) } + async isReady() { + return true + } + // simply puts a message to the queue and emits to the queue for processing /** * Simple function to replicate the add message functionality of Bull, putting diff --git a/packages/backend-core/src/utils/utils.ts b/packages/backend-core/src/utils/utils.ts index ee1ef6da0c..0554737518 100644 --- a/packages/backend-core/src/utils/utils.ts +++ b/packages/backend-core/src/utils/utils.ts @@ -96,7 +96,7 @@ export async function getAppIdFromCtx(ctx: Ctx) { } // look in the path - const pathId = parseAppIdFromUrl(ctx.path) + const pathId = parseAppIdFromUrlPath(ctx.path) if (!appId && pathId) { appId = confirmAppId(pathId) } @@ -116,18 +116,21 @@ export async function getAppIdFromCtx(ctx: Ctx) { // referer header is present from a builder redirect const referer = ctx.request.headers.referer if (!appId && referer?.includes(BUILDER_APP_PREFIX)) { - const refererId = parseAppIdFromUrl(ctx.request.headers.referer) + const refererId = parseAppIdFromUrlPath(ctx.request.headers.referer) appId = confirmAppId(refererId) } return appId } -function parseAppIdFromUrl(url?: string) { +function parseAppIdFromUrlPath(url?: string) { if (!url) { return } - return url.split("/").find(subPath => subPath.startsWith(APP_PREFIX)) + return url + .split("?")[0] // Remove any possible query string + .split("/") + .find(subPath => subPath.startsWith(APP_PREFIX)) } /** diff --git a/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte b/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte index a5b0b19c9b..4b68c285f8 100644 --- a/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte +++ b/packages/builder/src/pages/builder/portal/account/auditLogs/index.svelte @@ -257,7 +257,7 @@ { diff --git a/packages/builder/src/pages/builder/portal/account/usage.svelte b/packages/builder/src/pages/builder/portal/account/usage.svelte index cbe84973ef..9e7c402e91 100644 --- a/packages/builder/src/pages/builder/portal/account/usage.svelte +++ b/packages/builder/src/pages/builder/portal/account/usage.svelte @@ -15,6 +15,7 @@ import { DashCard, Usage } from "components/usage" import { PlanModel } from "constants" import { sdk } from "@budibase/shared-core" + import { PlanType } from "@budibase/types" let staticUsage = [] let monthlyUsage = [] @@ -106,7 +107,14 @@ } const planTitle = () => { - return `${capitalise(license?.plan.type)} Plan` + const planType = license?.plan.type + let planName = license?.plan.type + if (planType === PlanType.PREMIUM_PLUS) { + planName = "Premium" + } else if (planType === PlanType.ENTERPRISE_BASIC) { + planName = "Enterprise" + } + return `${capitalise(planName)} Plan` } const getDaysRemaining = timestamp => { diff --git a/packages/builder/src/pages/builder/portal/settings/auth/index.svelte b/packages/builder/src/pages/builder/portal/settings/auth/index.svelte index 36cf5c13a8..8c7cad7eeb 100644 --- a/packages/builder/src/pages/builder/portal/settings/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/settings/auth/index.svelte @@ -283,7 +283,7 @@ {#if !$licensing.enforceableSSO} - Enterprise + Enterprise plan {/if} diff --git a/packages/builder/src/pages/builder/portal/settings/environment/index.svelte b/packages/builder/src/pages/builder/portal/settings/environment/index.svelte index cff578febd..0d4c7e1479 100644 --- a/packages/builder/src/pages/builder/portal/settings/environment/index.svelte +++ b/packages/builder/src/pages/builder/portal/settings/environment/index.svelte @@ -59,7 +59,7 @@ { diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js index aefc3522a7..d4b4f3636e 100644 --- a/packages/frontend-core/src/api/index.js +++ b/packages/frontend-core/src/api/index.js @@ -1,4 +1,5 @@ import { Helpers } from "@budibase/bbui" +import { Header } from "@budibase/shared-core" import { ApiVersion } from "../constants" import { buildAnalyticsEndpoints } from "./analytics" import { buildAppEndpoints } from "./app" @@ -62,6 +63,11 @@ const defaultAPIClientConfig = { * invoked before the actual JS error is thrown up the stack. */ onError: null, + + /** + * A function can be passed to be called when an API call returns info about a migration running for a specific app + */ + onMigrationDetected: null, } /** @@ -133,9 +139,9 @@ export const createAPIClient = config => { // Build headers let headers = { Accept: "application/json" } - headers["x-budibase-session-id"] = APISessionID + headers[Header.SESSION_ID] = APISessionID if (!external) { - headers["x-budibase-api-version"] = ApiVersion + headers[Header.API_VER] = ApiVersion } if (json) { headers["Content-Type"] = "application/json" @@ -170,6 +176,7 @@ export const createAPIClient = config => { // Handle response if (response.status >= 200 && response.status < 400) { + handleMigrations(response) try { if (parseResponse) { return await parseResponse(response) @@ -186,7 +193,18 @@ export const createAPIClient = config => { } } - // Performs an API call to the server and caches the response. + const handleMigrations = response => { + if (!config.onMigrationDetected) { + return + } + const migration = response.headers.get(Header.MIGRATING_APP) + + if (migration) { + config.onMigrationDetected(migration) + } + } + + // Performs an API call to the server and caches the response. // Future invocation for this URL will return the cached result instead of // hitting the server again. const makeCachedApiCall = async params => { @@ -242,7 +260,7 @@ export const createAPIClient = config => { getAppID: () => { let headers = {} config?.attachHeaders(headers) - return headers?.["x-budibase-app-id"] + return headers?.[Header.APP_ID] }, } diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index f737570fcd..2af37ae288 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -68,9 +68,13 @@ COPY packages/server/builder/ builder/ COPY packages/server/client/ client/ ARG BUDIBASE_VERSION +ARG GIT_COMMIT_SHA # Ensuring the version argument is sent RUN test -n "$BUDIBASE_VERSION" ENV BUDIBASE_VERSION=$BUDIBASE_VERSION +ENV DD_GIT_REPOSITORY_URL=https://github.com/budibase/budibase +ENV DD_GIT_COMMIT_SHA=$GIT_COMMIT_SHA +ENV DD_VERSION=$BUDIBASE_VERSION EXPOSE 4001 diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index bb4f447f79..70298c7172 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -340,7 +340,7 @@ async function performAppCreate(ctx: UserCtx) { // Initialise the app migration version as the latest one await appMigrations.updateAppMigrationMetadata({ appId, - version: appMigrations.latestMigration, + version: appMigrations.getLatestMigrationId(), }) await cache.app.invalidateAppMetadata(appId, newApplication) diff --git a/packages/server/src/api/controllers/migrations.ts b/packages/server/src/api/controllers/migrations.ts index 8f1bfa22db..c8f786578d 100644 --- a/packages/server/src/api/controllers/migrations.ts +++ b/packages/server/src/api/controllers/migrations.ts @@ -1,14 +1,34 @@ +import { context } from "@budibase/backend-core" import { migrate as migrationImpl, MIGRATIONS } from "../../migrations" -import { BBContext } from "@budibase/types" +import { Ctx } from "@budibase/types" +import { + getAppMigrationVersion, + getLatestMigrationId, +} from "../../appMigrations" -export async function migrate(ctx: BBContext) { +export async function migrate(ctx: Ctx) { const options = ctx.request.body // don't await as can take a while, just return migrationImpl(options) ctx.status = 200 } -export async function fetchDefinitions(ctx: BBContext) { +export async function fetchDefinitions(ctx: Ctx) { ctx.body = MIGRATIONS ctx.status = 200 } + +export async function getMigrationStatus(ctx: Ctx) { + const appId = context.getAppId() + + if (!appId) { + ctx.throw("AppId could not be found") + } + + const latestAppliedMigration = await getAppMigrationVersion(appId) + + const migrated = latestAppliedMigration === getLatestMigrationId() + + ctx.body = { migrated } + ctx.status = 200 +} diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index 56c61ce85a..ad3d8307da 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -4,15 +4,24 @@ import currentApp from "../middleware/currentapp" import zlib from "zlib" import { mainRoutes, staticRoutes, publicRoutes } from "./routes" import { middleware as pro } from "@budibase/pro" -import { apiEnabled } from "../features" +import { apiEnabled, automationsEnabled } from "../features" import migrations from "../middleware/appMigrations" +import { automationQueue } from "../automations" export { shutdown } from "./routes/public" const compress = require("koa-compress") export const router: Router = new Router() -router.get("/health", ctx => (ctx.status = 200)) +router.get("/health", async ctx => { + if (automationsEnabled()) { + if (!(await automationQueue.isReady())) { + ctx.status = 503 + return + } + } + ctx.status = 200 +}) router.get("/version", ctx => (ctx.body = envCore.VERSION)) router.use(middleware.errorHandling) diff --git a/packages/server/src/api/routes/migrations.ts b/packages/server/src/api/routes/migrations.ts index f530647c78..918b197de2 100644 --- a/packages/server/src/api/routes/migrations.ts +++ b/packages/server/src/api/routes/migrations.ts @@ -11,4 +11,6 @@ router auth.internalApi, migrationsController.fetchDefinitions ) + .get("/api/migrations/status", migrationsController.getMigrationStatus) + export default router diff --git a/packages/server/src/appMigrations/index.ts b/packages/server/src/appMigrations/index.ts index a4ffe64604..b382d8b533 100644 --- a/packages/server/src/appMigrations/index.ts +++ b/packages/server/src/appMigrations/index.ts @@ -1,6 +1,9 @@ import queue from "./queue" +import { Next } from "koa" import { getAppMigrationVersion } from "./appMigrationMetadata" import { MIGRATIONS } from "./migrations" +import { UserCtx } from "@budibase/types" +import { Header } from "@budibase/backend-core" export * from "./appMigrationMetadata" @@ -9,14 +12,20 @@ export type AppMigration = { func: () => Promise } -export const latestMigration = MIGRATIONS.map(m => m.id) - .sort() - .reverse()[0] +export const getLatestMigrationId = () => + MIGRATIONS.map(m => m.id) + .sort() + .reverse()[0] const getTimestamp = (versionId: string) => versionId?.split("_")[0] -export async function checkMissingMigrations(appId: string) { +export async function checkMissingMigrations( + ctx: UserCtx, + next: Next, + appId: string +) { const currentVersion = await getAppMigrationVersion(appId) + const latestMigration = getLatestMigrationId() if (getTimestamp(currentVersion) < getTimestamp(latestMigration)) { await queue.add( @@ -29,5 +38,9 @@ export async function checkMissingMigrations(appId: string) { removeOnFail: true, } ) + + ctx.response.set(Header.MIGRATING_APP, appId) } + + return next() } diff --git a/packages/server/src/appMigrations/tests/migrations.integrity.spec.ts b/packages/server/src/appMigrations/tests/migrations.integrity.spec.ts new file mode 100644 index 0000000000..145a06d7f5 --- /dev/null +++ b/packages/server/src/appMigrations/tests/migrations.integrity.spec.ts @@ -0,0 +1,25 @@ +import { context } from "@budibase/backend-core" +import * as setup from "../../api/routes/tests/utilities" +import * as migrations from "../migrations" + +describe("migration integrity", () => { + // These test is checking that each migration is "idempotent". + // We should be able to rerun any migration, with any rerun not modifiying anything. The code should be aware that the migration already ran + it("each migration can rerun safely", async () => { + const config = setup.getConfig() + await config.init() + + await config.doInContext(config.getAppId(), async () => { + const db = context.getAppDB() + for (const migration of migrations.MIGRATIONS) { + await migration.func() + const docs = await db.allDocs({ include_docs: true }) + + await migration.func() + const latestDocs = await db.allDocs({ include_docs: true }) + + expect(docs).toEqual(latestDocs) + } + }) + }) +}) diff --git a/packages/server/src/appMigrations/tests/migrations.spec.ts b/packages/server/src/appMigrations/tests/migrations.spec.ts index 9d80cc5f99..5eb8535695 100644 --- a/packages/server/src/appMigrations/tests/migrations.spec.ts +++ b/packages/server/src/appMigrations/tests/migrations.spec.ts @@ -1,25 +1,53 @@ -import { context } from "@budibase/backend-core" +import { Header } from "@budibase/backend-core" import * as setup from "../../api/routes/tests/utilities" -import { MIGRATIONS } from "../migrations" +import * as migrations from "../migrations" +import { getAppMigrationVersion } from "../appMigrationMetadata" -describe("migration", () => { - // These test is checking that each migration is "idempotent". - // We should be able to rerun any migration, with any rerun not modifiying anything. The code should be aware that the migration already ran - it("each migration can rerun safely", async () => { +jest.mock("../migrations", () => ({ + MIGRATIONS: [ + { + id: "20231211101320_test", + func: async () => {}, + }, + ], +})) + +describe("migrations", () => { + it("new apps are created with the latest app migration version set", async () => { const config = setup.getConfig() await config.init() await config.doInContext(config.getAppId(), async () => { - const db = context.getAppDB() - for (const migration of MIGRATIONS) { - await migration.func() - const docs = await db.allDocs({ include_docs: true }) + const migrationVersion = await getAppMigrationVersion(config.getAppId()) - await migration.func() - const latestDocs = await db.allDocs({ include_docs: true }) - - expect(docs).toEqual(latestDocs) - } + expect(migrationVersion).toEqual("20231211101320_test") }) }) + + it("accessing an app that has no pending migrations will not attach the migrating header", async () => { + const config = setup.getConfig() + await config.init() + + const appId = config.getAppId() + + const response = await config.api.application.getRaw(appId) + + expect(response.headers[Header.MIGRATING_APP]).toBeUndefined() + }) + + it("accessing an app that has pending migrations will attach the migrating header", async () => { + const config = setup.getConfig() + await config.init() + + const appId = config.getAppId() + + migrations.MIGRATIONS.push({ + id: "20231211105812_new-test", + func: async () => {}, + }) + + const response = await config.api.application.getRaw(appId) + + expect(response.headers[Header.MIGRATING_APP]).toEqual(appId) + }) }) diff --git a/packages/server/src/appMigrations/tests/migrationsProcessor.spec.ts b/packages/server/src/appMigrations/tests/migrationsProcessor.spec.ts index 189f6c068b..3b8e90b526 100644 --- a/packages/server/src/appMigrations/tests/migrationsProcessor.spec.ts +++ b/packages/server/src/appMigrations/tests/migrationsProcessor.spec.ts @@ -4,12 +4,14 @@ import { getAppMigrationVersion } from "../appMigrationMetadata" import { context } from "@budibase/backend-core" import { AppMigration } from ".." +const futureTimestamp = `20500101174029` + describe("migrationsProcessor", () => { it("running migrations will update the latest applied migration", async () => { const testMigrations: AppMigration[] = [ - { id: "123", func: async () => {} }, - { id: "124", func: async () => {} }, - { id: "125", func: async () => {} }, + { id: `${futureTimestamp}_123`, func: async () => {} }, + { id: `${futureTimestamp}_124`, func: async () => {} }, + { id: `${futureTimestamp}_125`, func: async () => {} }, ] const config = setup.getConfig() @@ -23,13 +25,13 @@ describe("migrationsProcessor", () => { expect( await config.doInContext(appId, () => getAppMigrationVersion(appId)) - ).toBe("125") + ).toBe(`${futureTimestamp}_125`) }) it("no context can be initialised within a migration", async () => { const testMigrations: AppMigration[] = [ { - id: "123", + id: `${futureTimestamp}_123`, func: async () => { await context.doInAppMigrationContext("any", () => {}) }, diff --git a/packages/server/src/db/linkedRows/linkUtils.ts b/packages/server/src/db/linkedRows/linkUtils.ts index 5942e7e5a1..20f752407f 100644 --- a/packages/server/src/db/linkedRows/linkUtils.ts +++ b/packages/server/src/db/linkedRows/linkUtils.ts @@ -56,7 +56,7 @@ export async function getLinkDocuments(args: { try { let linkRows = (await db.query(getQueryIndex(ViewName.LINK), params)).rows // filter to get unique entries - const foundIds: string[] = [] + const foundIds = new Set() linkRows = linkRows.filter(link => { // make sure anything unique is the correct key if ( @@ -65,9 +65,9 @@ export async function getLinkDocuments(args: { ) { return false } - const unique = foundIds.indexOf(link.id) === -1 + const unique = !foundIds.has(link.id) if (unique) { - foundIds.push(link.id) + foundIds.add(link.id) } return unique }) @@ -99,9 +99,15 @@ export async function getLinkDocuments(args: { } export function getUniqueByProp(array: any[], prop: string) { - return array.filter((obj, pos, arr) => { - return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos - }) + const seen = new Set() + const filteredArray = [] + for (const item of array) { + if (!seen.has(item[prop])) { + seen.add(item[prop]) + filteredArray.push(item) + } + } + return filteredArray } export function getLinkedTableIDs(table: Table): string[] { diff --git a/packages/server/src/middleware/appMigrations.ts b/packages/server/src/middleware/appMigrations.ts index a94b8823e8..36e021c7ed 100644 --- a/packages/server/src/middleware/appMigrations.ts +++ b/packages/server/src/middleware/appMigrations.ts @@ -8,7 +8,5 @@ export default async (ctx: UserCtx, next: any) => { return next() } - await checkMissingMigrations(appId) - - return next() + return checkMissingMigrations(ctx, next, appId) } diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 984dd8e5e9..4b11b933af 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -12,6 +12,7 @@ import { getCachedSelf } from "../utilities/global" import env from "../environment" import { isWebhookEndpoint } from "./utils" import { UserCtx, ContextUser } from "@budibase/types" +import tracer from "dd-trace" export default async (ctx: UserCtx, next: any) => { // try to get the appID from the request @@ -20,6 +21,11 @@ export default async (ctx: UserCtx, next: any) => { return next() } + if (requestAppId) { + const span = tracer.scope().active() + span?.addTags({ app_id: requestAppId }) + } + // deny access to application preview if (!env.isTest()) { if ( diff --git a/packages/server/src/tests/utilities/api/application.ts b/packages/server/src/tests/utilities/api/application.ts index 85bc4e4173..9c784bade1 100644 --- a/packages/server/src/tests/utilities/api/application.ts +++ b/packages/server/src/tests/utilities/api/application.ts @@ -1,3 +1,4 @@ +import { Response } from "supertest" import { App } from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" @@ -7,12 +8,17 @@ export class ApplicationAPI extends TestAPI { super(config) } - get = async (appId: string): Promise => { + getRaw = async (appId: string): Promise => { const result = await this.request .get(`/api/applications/${appId}/appPackage`) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) + return result + } + + get = async (appId: string): Promise => { + const result = await this.getRaw(appId) return result.body.application as App } } diff --git a/packages/server/src/utilities/rowProcessor/utils.ts b/packages/server/src/utilities/rowProcessor/utils.ts index 9eb725dd7c..cafd366cae 100644 --- a/packages/server/src/utilities/rowProcessor/utils.ts +++ b/packages/server/src/utilities/rowProcessor/utils.ts @@ -11,6 +11,7 @@ import { Row, Table, } from "@budibase/types" +import tracer from "dd-trace" interface FormulaOpts { dynamic?: boolean @@ -50,33 +51,42 @@ export function processFormulas( inputRows: T, { dynamic, contextRows }: FormulaOpts = { dynamic: true } ): T { - const rows = Array.isArray(inputRows) ? inputRows : [inputRows] - if (rows) - for (let [column, schema] of Object.entries(table.schema)) { - if (schema.type !== FieldTypes.FORMULA) { - continue - } + return tracer.trace("processFormulas", {}, span => { + const numRows = Array.isArray(inputRows) ? inputRows.length : 1 + span?.addTags({ table_id: table._id, dynamic, numRows }) + const rows = Array.isArray(inputRows) ? inputRows : [inputRows] + if (rows) { + for (let [column, schema] of Object.entries(table.schema)) { + if (schema.type !== FieldTypes.FORMULA) { + continue + } - const isStatic = schema.formulaType === FormulaTypes.STATIC + const isStatic = schema.formulaType === FormulaTypes.STATIC - if ( - schema.formula == null || - (dynamic && isStatic) || - (!dynamic && !isStatic) - ) { - continue - } - // iterate through rows and process formula - for (let i = 0; i < rows.length; i++) { - let row = rows[i] - let context = contextRows ? contextRows[i] : row - rows[i] = { - ...row, - [column]: processStringSync(schema.formula, context), + if ( + schema.formula == null || + (dynamic && isStatic) || + (!dynamic && !isStatic) + ) { + continue + } + // iterate through rows and process formula + for (let i = 0; i < rows.length; i++) { + let row = rows[i] + let context = contextRows ? contextRows[i] : row + let formula = schema.formula + rows[i] = { + ...row, + [column]: tracer.trace("processStringSync", {}, span => { + span?.addTags({ table_id: table._id, column, static: isStatic }) + return processStringSync(formula, context) + }), + } } } } - return Array.isArray(inputRows) ? rows : rows[0] + return Array.isArray(inputRows) ? rows : rows[0] + }) } /** diff --git a/packages/shared-core/src/constants/api.ts b/packages/shared-core/src/constants/api.ts new file mode 100644 index 0000000000..d6633649e6 --- /dev/null +++ b/packages/shared-core/src/constants/api.ts @@ -0,0 +1,19 @@ +export enum Header { + API_KEY = "x-budibase-api-key", + LICENSE_KEY = "x-budibase-license-key", + API_VER = "x-budibase-api-version", + APP_ID = "x-budibase-app-id", + SESSION_ID = "x-budibase-session-id", + TYPE = "x-budibase-type", + PREVIEW_ROLE = "x-budibase-role", + TENANT_ID = "x-budibase-tenant-id", + VERIFICATION_CODE = "x-budibase-verification-code", + RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code", + RESET_PASSWORD_CODE = "x-budibase-reset-password-code", + RETURN_RESET_PASSWORD_CODE = "x-budibase-return-reset-password-code", + TOKEN = "x-budibase-token", + CSRF_TOKEN = "x-csrf-token", + CORRELATION_ID = "x-budibase-correlation-id", + AUTHORIZATION = "authorization", + MIGRATING_APP = "x-budibase-migrating-app", +} diff --git a/packages/shared-core/src/constants.ts b/packages/shared-core/src/constants/index.ts similarity index 99% rename from packages/shared-core/src/constants.ts rename to packages/shared-core/src/constants/index.ts index 0787b8bed1..a23913dd11 100644 --- a/packages/shared-core/src/constants.ts +++ b/packages/shared-core/src/constants/index.ts @@ -1,3 +1,5 @@ +export * from "./api" + export const OperatorOptions = { Equals: { value: "equal", diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index c5996c25f0..53baec8613 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -56,6 +56,10 @@ module.exports.processJS = (handlebars, context) => { const res = { data: runJS(js, sandboxContext) } return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}` } catch (error) { + console.log(`JS error: ${typeof error} ${JSON.stringify(error)}`) + if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") { + return "Timed out while executing JS" + } return "Error while executing JS" } } diff --git a/packages/string-templates/test/javascript.spec.js b/packages/string-templates/test/javascript.spec.js index b35bf8b040..d8b42678a7 100644 --- a/packages/string-templates/test/javascript.spec.js +++ b/packages/string-templates/test/javascript.spec.js @@ -115,7 +115,7 @@ describe("Test the JavaScript helper", () => { it("should timeout after one second", () => { const output = processJS(`while (true) {}`) - expect(output).toBe("Error while executing JS") + expect(output).toBe("Timed out while executing JS") }) it("should prevent access to the process global", () => { diff --git a/packages/worker/Dockerfile b/packages/worker/Dockerfile index 4706ca155a..b03a817c8b 100644 --- a/packages/worker/Dockerfile +++ b/packages/worker/Dockerfile @@ -51,8 +51,12 @@ ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR ENV ACCOUNT_PORTAL_URL=https://account.budibase.app ARG BUDIBASE_VERSION +ARG GIT_COMMIT_SHA # Ensuring the version argument is sent RUN test -n "$BUDIBASE_VERSION" ENV BUDIBASE_VERSION=$BUDIBASE_VERSION +ENV DD_GIT_REPOSITORY_URL=https://github.com/budibase/budibase +ENV DD_GIT_COMMIT_SHA=$GIT_COMMIT_SHA +ENV DD_VERSION=$BUDIBASE_VERSION CMD ["./docker_run.sh"]