@budibase/server 2.4.40 → 2.4.42-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/__mocks__/node-fetch.ts +6 -1
  2. package/builder/assets/index.3cb1022d.css +6 -0
  3. package/builder/assets/{index.07ed2ead.js → index.4b6c8c0e.js} +384 -383
  4. package/builder/index.html +7 -7
  5. package/dist/api/controllers/application.js +28 -24
  6. package/dist/api/controllers/datasource.js +2 -1
  7. package/dist/api/controllers/public/metrics.js +113 -0
  8. package/dist/api/controllers/row/external.js +16 -8
  9. package/dist/api/controllers/row/index.js +11 -1
  10. package/dist/api/controllers/row/internal.js +1 -10
  11. package/dist/api/controllers/row/utils.js +7 -7
  12. package/dist/api/controllers/static/index.js +84 -24
  13. package/dist/api/controllers/static/templates/BudibaseApp.svelte +33 -10
  14. package/dist/api/controllers/table/external.js +16 -12
  15. package/dist/api/controllers/table/utils.js +15 -1
  16. package/dist/api/routes/public/index.js +8 -0
  17. package/dist/api/routes/public/metrics.js +30 -0
  18. package/dist/app.js +1 -0
  19. package/dist/constants/index.js +2 -1
  20. package/dist/integrations/googlesheets.js +125 -59
  21. package/dist/integrations/redis.js +1 -1
  22. package/dist/integrations/utils.js +17 -2
  23. package/dist/package.json +13 -12
  24. package/dist/sdk/users/utils.js +2 -1
  25. package/dist/tsconfig.build.tsbuildinfo +1 -1
  26. package/dist/utilities/global.js +17 -7
  27. package/jest.config.ts +1 -0
  28. package/package.json +14 -13
  29. package/scripts/test.sh +3 -3
  30. package/specs/openapi.json +39 -0
  31. package/specs/openapi.yaml +169 -0
  32. package/specs/resources/application.ts +11 -0
  33. package/specs/resources/index.ts +2 -0
  34. package/specs/resources/metrics.ts +81 -0
  35. package/src/api/controllers/application.ts +20 -21
  36. package/src/api/controllers/datasource.ts +2 -1
  37. package/src/api/controllers/public/metrics.ts +251 -0
  38. package/src/api/controllers/row/external.ts +26 -16
  39. package/src/api/controllers/row/index.ts +12 -2
  40. package/src/api/controllers/row/internal.ts +0 -7
  41. package/src/api/controllers/row/utils.ts +7 -7
  42. package/src/api/controllers/static/index.ts +69 -26
  43. package/src/api/controllers/static/templates/BudibaseApp.svelte +33 -10
  44. package/src/api/controllers/table/external.ts +24 -17
  45. package/src/api/controllers/table/index.ts +9 -9
  46. package/src/api/controllers/table/utils.ts +18 -2
  47. package/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap +48 -48
  48. package/src/api/routes/public/index.ts +10 -1
  49. package/src/api/routes/public/metrics.ts +28 -0
  50. package/src/api/routes/public/tests/metrics.spec.js +34 -0
  51. package/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap +22 -22
  52. package/src/api/routes/tests/__snapshots__/view.spec.js.snap +5 -5
  53. package/src/api/routes/tests/appSync.spec.ts +31 -0
  54. package/src/api/routes/tests/internalSearch.spec.js +8 -7
  55. package/src/app.ts +2 -1
  56. package/src/automations/automationUtils.ts +1 -1
  57. package/src/automations/tests/automation.spec.ts +99 -0
  58. package/src/constants/index.ts +1 -0
  59. package/src/definitions/openapi.ts +15 -0
  60. package/src/integration-test/postgres.spec.ts +46 -52
  61. package/src/integrations/googlesheets.ts +143 -71
  62. package/src/integrations/redis.ts +1 -1
  63. package/src/integrations/tests/googlesheets.spec.ts +13 -13
  64. package/src/integrations/tests/redis.spec.ts +9 -5
  65. package/src/integrations/utils.ts +16 -4
  66. package/src/middleware/currentapp.ts +2 -2
  67. package/src/sdk/users/utils.ts +4 -1
  68. package/src/tests/jestEnv.ts +1 -0
  69. package/src/tests/jestSetup.ts +5 -1
  70. package/src/tests/utilities/TestConfiguration.ts +13 -0
  71. package/src/tests/utilities/structures.ts +13 -1
  72. package/src/utilities/global.ts +21 -9
  73. package/builder/assets/favicon.e7fc7733.png +0 -0
  74. package/builder/assets/index.b0e3aca6.css +0 -6
  75. package/src/automations/tests/automation.spec.js +0 -84
@@ -11,10 +11,12 @@ import {
11
11
  } from "../../../utilities/fileSystem"
12
12
  import env from "../../../environment"
13
13
  import { DocumentType } from "../../../db/utils"
14
- import { context, objectStore, utils } from "@budibase/backend-core"
14
+ import { context, objectStore, utils, configs } from "@budibase/backend-core"
15
15
  import AWS from "aws-sdk"
16
16
  import fs from "fs"
17
17
  import sdk from "../../../sdk"
18
+ import * as pro from "@budibase/pro"
19
+
18
20
  const send = require("koa-send")
19
21
 
20
22
  async function prepareUpload({ s3Key, bucket, metadata, file }: any) {
@@ -98,33 +100,74 @@ export const deleteObjects = async function (ctx: any) {
98
100
  }
99
101
 
100
102
  export const serveApp = async function (ctx: any) {
101
- const db = context.getAppDB({ skip_setup: true })
102
- const appInfo = await db.get(DocumentType.APP_METADATA)
103
- let appId = context.getAppId()
103
+ //Public Settings
104
+ const { config } = await configs.getSettingsConfigDoc()
105
+ const branding = await pro.branding.getBrandingConfig(config)
104
106
 
105
- if (!env.isJest()) {
106
- const App = require("./templates/BudibaseApp.svelte").default
107
- const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
108
- const { head, html, css } = App.render({
109
- metaImage:
110
- "https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
111
- title: appInfo.name,
112
- production: env.isProd(),
113
- appId,
114
- clientLibPath: objectStore.clientLibraryUrl(appId!, appInfo.version),
115
- usedPlugins: plugins,
116
- })
107
+ let db
108
+ try {
109
+ db = context.getAppDB({ skip_setup: true })
110
+ const appInfo = await db.get(DocumentType.APP_METADATA)
111
+ let appId = context.getAppId()
117
112
 
118
- const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
119
- ctx.body = await processString(appHbs, {
120
- head,
121
- body: html,
122
- style: css.code,
123
- appId,
124
- })
125
- } else {
126
- // just return the app info for jest to assert on
127
- ctx.body = appInfo
113
+ if (!env.isJest()) {
114
+ const App = require("./templates/BudibaseApp.svelte").default
115
+ const plugins = objectStore.enrichPluginURLs(appInfo.usedPlugins)
116
+ const { head, html, css } = App.render({
117
+ metaImage:
118
+ branding?.metaImageUrl ||
119
+ "https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
120
+ metaDescription: branding?.metaDescription || "",
121
+ metaTitle:
122
+ branding?.metaTitle || `${appInfo.name} - built with Budibase`,
123
+ title: appInfo.name,
124
+ production: env.isProd(),
125
+ appId,
126
+ clientLibPath: objectStore.clientLibraryUrl(appId!, appInfo.version),
127
+ usedPlugins: plugins,
128
+ favicon:
129
+ branding.faviconUrl !== ""
130
+ ? objectStore.getGlobalFileUrl("settings", "faviconUrl")
131
+ : "",
132
+ logo:
133
+ config?.logoUrl !== ""
134
+ ? objectStore.getGlobalFileUrl("settings", "logoUrl")
135
+ : "",
136
+ })
137
+ const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
138
+ ctx.body = await processString(appHbs, {
139
+ head,
140
+ body: html,
141
+ style: css.code,
142
+ appId,
143
+ })
144
+ } else {
145
+ // just return the app info for jest to assert on
146
+ ctx.body = appInfo
147
+ }
148
+ } catch (error) {
149
+ if (!env.isJest()) {
150
+ const App = require("./templates/BudibaseApp.svelte").default
151
+ const { head, html, css } = App.render({
152
+ title: branding?.metaTitle,
153
+ metaTitle: branding?.metaTitle,
154
+ metaImage:
155
+ branding?.metaImageUrl ||
156
+ "https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
157
+ metaDescription: branding?.metaDescription || "",
158
+ favicon:
159
+ branding.faviconUrl !== ""
160
+ ? objectStore.getGlobalFileUrl("settings", "faviconUrl")
161
+ : "",
162
+ })
163
+
164
+ const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
165
+ ctx.body = await processString(appHbs, {
166
+ head,
167
+ body: html,
168
+ style: css.code,
169
+ })
170
+ }
128
171
  }
129
172
  }
130
173
 
@@ -1,7 +1,10 @@
1
1
  <script>
2
2
  export let title = ""
3
3
  export let favicon = ""
4
+
4
5
  export let metaImage = ""
6
+ export let metaTitle = ""
7
+ export let metaDescription = ""
5
8
 
6
9
  export let clientLibPath
7
10
  export let usedPlugins
@@ -13,18 +16,33 @@
13
16
  name="viewport"
14
17
  content="width=device-width, initial-scale=1.0, viewport-fit=cover"
15
18
  />
19
+
20
+ <!-- Primary Meta Tags -->
21
+ <meta name="title" content={metaTitle} />
22
+ <meta name="description" content={metaDescription} />
23
+
16
24
  <!-- Opengraph Meta Tags -->
17
- <meta name="twitter:card" content="summary_large_image" />
18
- <meta name="twitter:site" content="@budibase" />
19
- <meta name="twitter:image" content={metaImage} />
20
- <meta name="twitter:title" content="{title} - built with Budibase" />
21
25
  <meta property="og:site_name" content="Budibase" />
22
26
  <meta property="og:title" content="{title} - built with Budibase" />
27
+ <meta property="og:description" content={metaDescription} />
23
28
  <meta property="og:type" content="website" />
24
29
  <meta property="og:image" content={metaImage} />
25
30
 
31
+ <!-- Twitter -->
32
+ <meta property="twitter:card" content="summary_large_image" />
33
+ <meta property="twitter:site" content="@budibase" />
34
+ <meta property="twitter:image" content={metaImage} />
35
+ <meta property="twitter:image:alt" content="{title} - built with Budibase" />
36
+ <meta property="twitter:title" content="{title} - built with Budibase" />
37
+ <meta property="twitter:description" content={metaDescription} />
38
+
26
39
  <title>{title}</title>
27
- <link rel="icon" type="image/png" href={favicon} />
40
+ {#if favicon !== ""}
41
+ <link rel="icon" type="image/png" href={favicon} />
42
+ {:else}
43
+ <link rel="icon" type="image/png" href="https://i.imgur.com/Xhdt1YP.png" />
44
+ {/if}
45
+
28
46
  <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
29
47
  <link rel="preconnect" href="https://fonts.gstatic.com" />
30
48
  <link
@@ -83,11 +101,16 @@
83
101
 
84
102
  <body id="app">
85
103
  <div id="error">
86
- <h1>There was an error loading your app</h1>
87
- <h2>
88
- The Budibase client library could not be loaded. Try republishing your
89
- app.
90
- </h2>
104
+ {#if clientLibPath}
105
+ <h1>There was an error loading your app</h1>
106
+ <h2>
107
+ The Budibase client library could not be loaded. Try republishing your
108
+ app.
109
+ </h2>
110
+ {:else}
111
+ <h2>We couldn't find that application</h2>
112
+ <p />
113
+ {/if}
91
114
  </div>
92
115
  <script type="application/javascript">
93
116
  window.INIT_TIME = Date.now()
@@ -7,6 +7,7 @@ import {
7
7
  generateJunctionTableName,
8
8
  foreignKeyStructure,
9
9
  hasTypeChanged,
10
+ setStaticSchemas,
10
11
  } from "./utils"
11
12
  import { FieldTypes } from "../../../constants"
12
13
  import { makeExternalQuery } from "../../../integrations/base/query"
@@ -20,7 +21,7 @@ import {
20
21
  Operation,
21
22
  RenameColumn,
22
23
  FieldSchema,
23
- BBContext,
24
+ UserCtx,
24
25
  TableRequest,
25
26
  RelationshipTypes,
26
27
  } from "@budibase/types"
@@ -194,20 +195,20 @@ function isRelationshipSetup(column: FieldSchema) {
194
195
  return column.foreignKey || column.through
195
196
  }
196
197
 
197
- export async function save(ctx: BBContext) {
198
- const table: TableRequest = ctx.request.body
199
- const renamed = table?._rename
198
+ export async function save(ctx: UserCtx) {
199
+ const inputs: TableRequest = ctx.request.body
200
+ const renamed = inputs?._rename
200
201
  // can't do this right now
201
- delete table.rows
202
+ delete inputs.rows
202
203
  const datasourceId = getDatasourceId(ctx.request.body)!
203
204
  // table doesn't exist already, note that it is created
204
- if (!table._id) {
205
- table.created = true
205
+ if (!inputs._id) {
206
+ inputs.created = true
206
207
  }
207
208
  let tableToSave: TableRequest = {
208
209
  type: "table",
209
- _id: buildExternalTableId(datasourceId, table.name),
210
- ...table,
210
+ _id: buildExternalTableId(datasourceId, inputs.name),
211
+ ...inputs,
211
212
  }
212
213
 
213
214
  let oldTable
@@ -224,6 +225,10 @@ export async function save(ctx: BBContext) {
224
225
  if (!datasource.entities) {
225
226
  datasource.entities = {}
226
227
  }
228
+
229
+ // GSheets is a specific case - only ever has a static primary key
230
+ tableToSave = setStaticSchemas(datasource, tableToSave)
231
+
227
232
  const oldTables = cloneDeep(datasource.entities)
228
233
  const tables: Record<string, Table> = datasource.entities
229
234
 
@@ -246,7 +251,7 @@ export async function save(ctx: BBContext) {
246
251
  const junctionTable = generateManyLinkSchema(
247
252
  datasource,
248
253
  schema,
249
- table,
254
+ tableToSave,
250
255
  relatedTable
251
256
  )
252
257
  if (tables[junctionTable.name]) {
@@ -256,10 +261,12 @@ export async function save(ctx: BBContext) {
256
261
  extraTablesToUpdate.push(junctionTable)
257
262
  } else {
258
263
  const fkTable =
259
- relationType === RelationshipTypes.ONE_TO_MANY ? table : relatedTable
264
+ relationType === RelationshipTypes.ONE_TO_MANY
265
+ ? tableToSave
266
+ : relatedTable
260
267
  const foreignKey = generateLinkSchema(
261
268
  schema,
262
- table,
269
+ tableToSave,
263
270
  relatedTable,
264
271
  relationType
265
272
  )
@@ -271,11 +278,11 @@ export async function save(ctx: BBContext) {
271
278
  fkTable.constrained.push(foreignKey)
272
279
  }
273
280
  // foreign key is in other table, need to save it to external
274
- if (fkTable._id !== table._id) {
281
+ if (fkTable._id !== tableToSave._id) {
275
282
  extraTablesToUpdate.push(fkTable)
276
283
  }
277
284
  }
278
- generateRelatedSchema(schema, relatedTable, table, relatedColumnName)
285
+ generateRelatedSchema(schema, relatedTable, tableToSave, relatedColumnName)
279
286
  schema.main = true
280
287
  }
281
288
 
@@ -313,7 +320,7 @@ export async function save(ctx: BBContext) {
313
320
  return tableToSave
314
321
  }
315
322
 
316
- export async function destroy(ctx: BBContext) {
323
+ export async function destroy(ctx: UserCtx) {
317
324
  const tableToDelete: TableRequest = await sdk.tables.getTable(
318
325
  ctx.params.tableId
319
326
  )
@@ -339,7 +346,7 @@ export async function destroy(ctx: BBContext) {
339
346
  return tableToDelete
340
347
  }
341
348
 
342
- export async function bulkImport(ctx: BBContext) {
349
+ export async function bulkImport(ctx: UserCtx) {
343
350
  const table = await sdk.tables.getTable(ctx.params.tableId)
344
351
  const { rows }: { rows: unknown } = ctx.request.body
345
352
  const schema: unknown = table.schema
@@ -348,7 +355,7 @@ export async function bulkImport(ctx: BBContext) {
348
355
  ctx.throw(400, "Provided data import information is invalid.")
349
356
  }
350
357
 
351
- const parsedRows = await parse(rows, schema)
358
+ const parsedRows = parse(rows, schema)
352
359
  await handleRequest(Operation.BULK_CREATE, table._id!, {
353
360
  rows: parsedRows,
354
361
  })
@@ -8,7 +8,7 @@ import {
8
8
  import { isExternalTable, isSQL } from "../../../integrations/utils"
9
9
  import { getDatasourceParams } from "../../../db/utils"
10
10
  import { context, events } from "@budibase/backend-core"
11
- import { Table, BBContext } from "@budibase/types"
11
+ import { Table, UserCtx } from "@budibase/types"
12
12
  import sdk from "../../../sdk"
13
13
  import csv from "csvtojson"
14
14
 
@@ -25,7 +25,7 @@ function pickApi({ tableId, table }: { tableId?: string; table?: Table }) {
25
25
  }
26
26
 
27
27
  // covers both internal and external
28
- export async function fetch(ctx: BBContext) {
28
+ export async function fetch(ctx: UserCtx) {
29
29
  const db = context.getAppDB()
30
30
 
31
31
  const internal = await sdk.tables.getAllInternalTables()
@@ -53,12 +53,12 @@ export async function fetch(ctx: BBContext) {
53
53
  ctx.body = [...internal, ...external]
54
54
  }
55
55
 
56
- export async function find(ctx: BBContext) {
56
+ export async function find(ctx: UserCtx) {
57
57
  const tableId = ctx.params.tableId
58
58
  ctx.body = await sdk.tables.getTable(tableId)
59
59
  }
60
60
 
61
- export async function save(ctx: BBContext) {
61
+ export async function save(ctx: UserCtx) {
62
62
  const appId = ctx.appId
63
63
  const table = ctx.request.body
64
64
  const isImport = table.rows
@@ -79,7 +79,7 @@ export async function save(ctx: BBContext) {
79
79
  ctx.body = savedTable
80
80
  }
81
81
 
82
- export async function destroy(ctx: BBContext) {
82
+ export async function destroy(ctx: UserCtx) {
83
83
  const appId = ctx.appId
84
84
  const tableId = ctx.params.tableId
85
85
  const deletedTable = await pickApi({ tableId }).destroy(ctx)
@@ -91,7 +91,7 @@ export async function destroy(ctx: BBContext) {
91
91
  ctx.body = { message: `Table ${tableId} deleted.` }
92
92
  }
93
93
 
94
- export async function bulkImport(ctx: BBContext) {
94
+ export async function bulkImport(ctx: UserCtx) {
95
95
  const tableId = ctx.params.tableId
96
96
  await pickApi({ tableId }).bulkImport(ctx)
97
97
  // right now we don't trigger anything for bulk import because it
@@ -101,7 +101,7 @@ export async function bulkImport(ctx: BBContext) {
101
101
  ctx.body = { message: `Bulk rows created.` }
102
102
  }
103
103
 
104
- export async function csvToJson(ctx: BBContext) {
104
+ export async function csvToJson(ctx: UserCtx) {
105
105
  const { csvString } = ctx.request.body
106
106
 
107
107
  const result = await csv().fromString(csvString)
@@ -110,7 +110,7 @@ export async function csvToJson(ctx: BBContext) {
110
110
  ctx.body = result
111
111
  }
112
112
 
113
- export async function validateNewTableImport(ctx: BBContext) {
113
+ export async function validateNewTableImport(ctx: UserCtx) {
114
114
  const { rows, schema }: { rows: unknown; schema: unknown } = ctx.request.body
115
115
 
116
116
  if (isRows(rows) && isSchema(schema)) {
@@ -121,7 +121,7 @@ export async function validateNewTableImport(ctx: BBContext) {
121
121
  }
122
122
  }
123
123
 
124
- export async function validateExistingTableImport(ctx: BBContext) {
124
+ export async function validateExistingTableImport(ctx: UserCtx) {
125
125
  const { rows, tableId }: { rows: unknown; tableId: unknown } =
126
126
  ctx.request.body
127
127
 
@@ -1,7 +1,11 @@
1
1
  import { parse, isSchema, isRows } from "../../../utilities/schema"
2
2
  import { getRowParams, generateRowID, InternalTables } from "../../../db/utils"
3
3
  import { isEqual } from "lodash"
4
- import { AutoFieldSubTypes, FieldTypes } from "../../../constants"
4
+ import {
5
+ AutoFieldSubTypes,
6
+ FieldTypes,
7
+ GOOGLE_SHEETS_PRIMARY_KEY,
8
+ } from "../../../constants"
5
9
  import {
6
10
  inputProcessing,
7
11
  cleanupAttachments,
@@ -16,7 +20,7 @@ import viewTemplate from "../view/viewBuilder"
16
20
  import { cloneDeep } from "lodash/fp"
17
21
  import { quotas } from "@budibase/pro"
18
22
  import { events, context } from "@budibase/backend-core"
19
- import { Database } from "@budibase/types"
23
+ import { Database, Datasource, SourceName, Table } from "@budibase/types"
20
24
 
21
25
  export async function clearColumns(table: any, columnNames: any) {
22
26
  const db: Database = context.getAppDB()
@@ -392,5 +396,17 @@ export function hasTypeChanged(table: any, oldTable: any) {
392
396
  return false
393
397
  }
394
398
 
399
+ // used for external tables, some of them will have static schemas that need
400
+ // to be hard set
401
+ export function setStaticSchemas(datasource: Datasource, table: Table) {
402
+ // GSheets is a specific case - only ever has a static primary key
403
+ if (table && datasource.source === SourceName.GOOGLE_SHEETS) {
404
+ table.primary = [GOOGLE_SHEETS_PRIMARY_KEY]
405
+ // if there is an id column, remove it, should never exist in GSheets
406
+ delete table.schema?.id
407
+ }
408
+ return table
409
+ }
410
+
395
411
  const _TableSaveFunctions = TableSaveFunctions
396
412
  export { _TableSaveFunctions as TableSaveFunctions }
@@ -1,48 +1,48 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`viewBuilder Calculate and filter creates a view with the calculation statistics and filter schema 1`] = `
4
- Object {
4
+ {
5
5
  "map": "function (doc) {
6
- if ((doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && !(
7
- doc[\\"myField\\"] === undefined ||
8
- doc[\\"myField\\"] === null ||
9
- doc[\\"myField\\"] === \\"\\" ||
10
- (Array.isArray(doc[\\"myField\\"]) && doc[\\"myField\\"].length === 0)
11
- )) && (doc[\\"age\\"] > 17)) {
12
- emit(doc[\\"_id\\"], doc[\\"myField\\"]);
6
+ if ((doc.tableId === "14f1c4e94d6a47b682ce89d35d4c78b0" && !(
7
+ doc["myField"] === undefined ||
8
+ doc["myField"] === null ||
9
+ doc["myField"] === "" ||
10
+ (Array.isArray(doc["myField"]) && doc["myField"].length === 0)
11
+ )) && (doc["age"] > 17)) {
12
+ emit(doc["_id"], doc["myField"]);
13
13
  }
14
14
  }",
15
- "meta": Object {
15
+ "meta": {
16
16
  "calculation": "stats",
17
17
  "field": "myField",
18
- "filters": Array [
19
- Object {
18
+ "filters": [
19
+ {
20
20
  "condition": "MT",
21
21
  "key": "age",
22
22
  "value": 17,
23
23
  },
24
24
  ],
25
25
  "groupBy": undefined,
26
- "schema": Object {
27
- "avg": Object {
26
+ "schema": {
27
+ "avg": {
28
28
  "type": "number",
29
29
  },
30
- "count": Object {
30
+ "count": {
31
31
  "type": "number",
32
32
  },
33
- "field": Object {
33
+ "field": {
34
34
  "type": "string",
35
35
  },
36
- "max": Object {
36
+ "max": {
37
37
  "type": "number",
38
38
  },
39
- "min": Object {
39
+ "min": {
40
40
  "type": "number",
41
41
  },
42
- "sum": Object {
42
+ "sum": {
43
43
  "type": "number",
44
44
  },
45
- "sumsqr": Object {
45
+ "sumsqr": {
46
46
  "type": "number",
47
47
  },
48
48
  },
@@ -53,42 +53,42 @@ Object {
53
53
  `;
54
54
 
55
55
  exports[`viewBuilder Calculate creates a view with the calculation statistics schema 1`] = `
56
- Object {
56
+ {
57
57
  "map": "function (doc) {
58
- if ((doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && !(
59
- doc[\\"myField\\"] === undefined ||
60
- doc[\\"myField\\"] === null ||
61
- doc[\\"myField\\"] === \\"\\" ||
62
- (Array.isArray(doc[\\"myField\\"]) && doc[\\"myField\\"].length === 0)
58
+ if ((doc.tableId === "14f1c4e94d6a47b682ce89d35d4c78b0" && !(
59
+ doc["myField"] === undefined ||
60
+ doc["myField"] === null ||
61
+ doc["myField"] === "" ||
62
+ (Array.isArray(doc["myField"]) && doc["myField"].length === 0)
63
63
  )) ) {
64
- emit(doc[\\"_id\\"], doc[\\"myField\\"]);
64
+ emit(doc["_id"], doc["myField"]);
65
65
  }
66
66
  }",
67
- "meta": Object {
67
+ "meta": {
68
68
  "calculation": "stats",
69
69
  "field": "myField",
70
- "filters": Array [],
70
+ "filters": [],
71
71
  "groupBy": undefined,
72
- "schema": Object {
73
- "avg": Object {
72
+ "schema": {
73
+ "avg": {
74
74
  "type": "number",
75
75
  },
76
- "count": Object {
76
+ "count": {
77
77
  "type": "number",
78
78
  },
79
- "field": Object {
79
+ "field": {
80
80
  "type": "string",
81
81
  },
82
- "max": Object {
82
+ "max": {
83
83
  "type": "number",
84
84
  },
85
- "min": Object {
85
+ "min": {
86
86
  "type": "number",
87
87
  },
88
- "sum": Object {
88
+ "sum": {
89
89
  "type": "number",
90
90
  },
91
- "sumsqr": Object {
91
+ "sumsqr": {
92
92
  "type": "number",
93
93
  },
94
94
  },
@@ -99,22 +99,22 @@ Object {
99
99
  `;
100
100
 
101
101
  exports[`viewBuilder Filter creates a view with multiple filters and conjunctions 1`] = `
102
- Object {
102
+ {
103
103
  "map": "function (doc) {
104
- if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && (doc[\\"Name\\"] === \\"Test\\" || doc[\\"Yes\\"] > \\"Value\\")) {
105
- emit(doc[\\"_id\\"], doc[\\"undefined\\"]);
104
+ if (doc.tableId === "14f1c4e94d6a47b682ce89d35d4c78b0" && (doc["Name"] === "Test" || doc["Yes"] > "Value")) {
105
+ emit(doc["_id"], doc["undefined"]);
106
106
  }
107
107
  }",
108
- "meta": Object {
108
+ "meta": {
109
109
  "calculation": undefined,
110
110
  "field": undefined,
111
- "filters": Array [
112
- Object {
111
+ "filters": [
112
+ {
113
113
  "condition": "EQUALS",
114
114
  "key": "Name",
115
115
  "value": "Test",
116
116
  },
117
- Object {
117
+ {
118
118
  "condition": "MT",
119
119
  "conjunction": "OR",
120
120
  "key": "Yes",
@@ -129,16 +129,16 @@ Object {
129
129
  `;
130
130
 
131
131
  exports[`viewBuilder Group By creates a view emitting the group by field 1`] = `
132
- Object {
132
+ {
133
133
  "map": "function (doc) {
134
- if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) {
135
- emit(doc[\\"age\\"], doc[\\"score\\"]);
134
+ if (doc.tableId === "14f1c4e94d6a47b682ce89d35d4c78b0" ) {
135
+ emit(doc["age"], doc["score"]);
136
136
  }
137
137
  }",
138
- "meta": Object {
138
+ "meta": {
139
139
  "calculation": undefined,
140
140
  "field": "score",
141
- "filters": Array [],
141
+ "filters": [],
142
142
  "groupBy": "age",
143
143
  "schema": null,
144
144
  "tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
@@ -1,4 +1,5 @@
1
1
  import appEndpoints from "./applications"
2
+ import metricEndpoints from "./metrics"
2
3
  import queryEndpoints from "./queries"
3
4
  import tableEndpoints from "./tables"
4
5
  import rowEndpoints from "./rows"
@@ -12,7 +13,7 @@ import env from "../../../environment"
12
13
  // below imports don't have declaration files
13
14
  const Router = require("@koa/router")
14
15
  const { RateLimit, Stores } = require("koa2-ratelimit")
15
- import { redis, permissions } from "@budibase/backend-core"
16
+ import { middleware, redis, permissions } from "@budibase/backend-core"
16
17
  const { PermissionType, PermissionLevel } = permissions
17
18
 
18
19
  const PREFIX = "/api/public/v1"
@@ -91,6 +92,13 @@ function addToRouter(endpoints: any) {
91
92
  }
92
93
  }
93
94
 
95
+ function applyAdminRoutes(endpoints: any) {
96
+ addMiddleware(endpoints.read, middleware.builderOrAdmin)
97
+ addMiddleware(endpoints.write, middleware.builderOrAdmin)
98
+ addToRouter(endpoints.read)
99
+ addToRouter(endpoints.write)
100
+ }
101
+
94
102
  function applyRoutes(
95
103
  endpoints: any,
96
104
  permType: string,
@@ -119,6 +127,7 @@ function applyRoutes(
119
127
  addToRouter(endpoints.write)
120
128
  }
121
129
 
130
+ applyAdminRoutes(metricEndpoints)
122
131
  applyRoutes(appEndpoints, PermissionType.APP, "appId")
123
132
  applyRoutes(tableEndpoints, PermissionType.TABLE, "tableId")
124
133
  applyRoutes(userEndpoints, PermissionType.USER, "userId")
@@ -0,0 +1,28 @@
1
+ import controller from "../../controllers/public/metrics"
2
+ import Endpoint from "./utils/Endpoint"
3
+
4
+ const read = []
5
+
6
+ /**
7
+ * @openapi
8
+ * /metrics:
9
+ * get:
10
+ * operationId: metricsGet
11
+ * summary: Retrieve Budibase tenant metrics
12
+ * description: Output metrics in OpenMetrics format compatible with Prometheus
13
+ * tags:
14
+ * - metrics
15
+ * responses:
16
+ * 200:
17
+ * description: Returns tenant metrics.
18
+ * content:
19
+ * text/plain:
20
+ * schema:
21
+ * type: string
22
+ * examples:
23
+ * metrics:
24
+ * $ref: '#/components/examples/metrics'
25
+ */
26
+ read.push(new Endpoint("get", "/metrics", controller.fetch))
27
+
28
+ export default { read }