Merge remote-tracking branch 'origin/feature/app-list-actions' into feature/app-favourites
This commit is contained in:
commit
1cd20781fb
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.21.5",
|
||||
"version": "2.21.6",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
let integration
|
||||
let schemaType
|
||||
|
||||
let autoSchema = {}
|
||||
let schema = {}
|
||||
let nestedSchemaFields = {}
|
||||
let rows = []
|
||||
let keys = {}
|
||||
|
@ -52,6 +52,8 @@
|
|||
schemaType = integration.query[query.queryVerb].type
|
||||
|
||||
newQuery = cloneDeep(query)
|
||||
// init schema from the query if one already exists
|
||||
schema = newQuery.schema
|
||||
// Set the location where the query code will be written to an empty string so that it doesn't
|
||||
// get changed from undefined -> "" by the input, breaking our unsaved changes checks
|
||||
newQuery.fields[schemaType] ??= ""
|
||||
|
@ -86,12 +88,7 @@
|
|||
|
||||
nestedSchemaFields = response.nestedSchemaFields
|
||||
|
||||
if (Object.keys(newQuery.schema).length === 0) {
|
||||
// Assign this to a variable instead of directly to the newQuery.schema so that a user
|
||||
// can change the table they're querying and have the schema update until they first
|
||||
// edit it
|
||||
autoSchema = response.schema
|
||||
}
|
||||
schema = response.schema
|
||||
rows = response.rows
|
||||
|
||||
notifications.success("Query executed successfully")
|
||||
|
@ -118,10 +115,7 @@
|
|||
loading = true
|
||||
const response = await queries.save(newQuery.datasourceId, {
|
||||
...newQuery,
|
||||
schema:
|
||||
Object.keys(newQuery.schema).length === 0
|
||||
? autoSchema
|
||||
: newQuery.schema,
|
||||
schema,
|
||||
nestedSchemaFields,
|
||||
})
|
||||
|
||||
|
@ -320,12 +314,10 @@
|
|||
<QueryViewerSidePanel
|
||||
onClose={() => (showSidePanel = false)}
|
||||
onSchemaChange={newSchema => {
|
||||
newQuery.schema = newSchema
|
||||
schema = newSchema
|
||||
}}
|
||||
{rows}
|
||||
schema={Object.keys(newQuery.schema).length === 0
|
||||
? autoSchema
|
||||
: newQuery.schema}
|
||||
{schema}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -701,7 +701,6 @@ export async function duplicateApp(
|
|||
}
|
||||
|
||||
ctx.body = {
|
||||
message: "app duplicated",
|
||||
duplicateAppId: newApplication?.appId,
|
||||
sourceAppId,
|
||||
}
|
||||
|
|
|
@ -34,6 +34,96 @@ describe("/applications", () => {
|
|||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
// These need to go first for the app totals to make sense
|
||||
describe("permissions", () => {
|
||||
it("should only return apps a user has access to", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
builder: {
|
||||
apps: [config.getProdAppId()],
|
||||
},
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it("should only return apps a user has access to through a custom role", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
const role = await config.api.roles.save({
|
||||
name: "Test",
|
||||
inherits: "PUBLIC",
|
||||
permissionId: "read_only",
|
||||
version: "name",
|
||||
})
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
roles: {
|
||||
[config.getProdAppId()]: role.name,
|
||||
},
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it("should only return apps a user has access to through a custom role on a group", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
const roleName = uuid.v4().replace(/-/g, "")
|
||||
const role = await config.api.roles.save({
|
||||
name: roleName,
|
||||
inherits: "PUBLIC",
|
||||
permissionId: "read_only",
|
||||
version: "name",
|
||||
})
|
||||
|
||||
const group = await config.createGroup(role._id!)
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
userGroups: [group._id!],
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("creates empty app", async () => {
|
||||
const app = await config.api.application.create({ name: utils.newid() })
|
||||
|
@ -96,13 +186,16 @@ describe("/applications", () => {
|
|||
})
|
||||
|
||||
it("should reject with a known name", async () => {
|
||||
await config.api.application.create({ name: app.name }, { status: 400 })
|
||||
await config.api.application.create(
|
||||
{ name: app.name },
|
||||
{ body: { message: "App name is already in use." }, status: 400 }
|
||||
)
|
||||
})
|
||||
|
||||
it("should reject with a known url", async () => {
|
||||
await config.api.application.create(
|
||||
{ name: "made up", url: app?.url! },
|
||||
{ status: 400 }
|
||||
{ body: { message: "App URL is already in use." }, status: 400 }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -279,10 +372,9 @@ describe("/applications", () => {
|
|||
name: app.name,
|
||||
url: "/known-name",
|
||||
},
|
||||
{ status: 400 }
|
||||
{ body: { message: "App name is already in use." }, status: 400 }
|
||||
)
|
||||
|
||||
expect(resp.message).toEqual("App name is already in use.")
|
||||
expect(events.app.duplicated).not.toBeCalled()
|
||||
})
|
||||
|
||||
it("should reject with a known url", async () => {
|
||||
|
@ -292,10 +384,9 @@ describe("/applications", () => {
|
|||
name: "this is fine",
|
||||
url: app.url,
|
||||
},
|
||||
{ status: 400 }
|
||||
{ body: { message: "App URL is already in use." }, status: 400 }
|
||||
)
|
||||
|
||||
expect(resp.message).toEqual("App URL is already in use.")
|
||||
expect(events.app.duplicated).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -319,93 +410,4 @@ describe("/applications", () => {
|
|||
expect(devLogs.data.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("permissions", () => {
|
||||
it("should only return apps a user has access to", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
builder: {
|
||||
apps: [config.getProdAppId()],
|
||||
},
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it("should only return apps a user has access to through a custom role", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
const role = await config.api.roles.save({
|
||||
name: "Test",
|
||||
inherits: "PUBLIC",
|
||||
permissionId: "read_only",
|
||||
version: "name",
|
||||
})
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
roles: {
|
||||
[config.getProdAppId()]: role.name,
|
||||
},
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
it.only("should only return apps a user has access to through a custom role on a group", async () => {
|
||||
let user = await config.createUser({
|
||||
builder: { global: false },
|
||||
admin: { global: false },
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(0)
|
||||
})
|
||||
|
||||
const roleName = uuid.v4().replace(/-/g, "")
|
||||
const role = await config.api.roles.save({
|
||||
name: roleName,
|
||||
inherits: "PUBLIC",
|
||||
permissionId: "read_only",
|
||||
version: "name",
|
||||
})
|
||||
|
||||
const group = await config.createGroup(role._id!)
|
||||
|
||||
user = await config.globalUser({
|
||||
...user,
|
||||
userGroups: [group._id!],
|
||||
})
|
||||
|
||||
await config.withUser(user, async () => {
|
||||
const apps = await config.api.application.fetch()
|
||||
expect(apps).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@ import { context, cache, auth } from "@budibase/backend-core"
|
|||
import { getGlobalIDFromUserMetadataID } from "../db/utils"
|
||||
import sdk from "../sdk"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { Datasource, Query, SourceName } from "@budibase/types"
|
||||
import { Datasource, Query, SourceName, Row } from "@budibase/types"
|
||||
|
||||
import { isSQL } from "../integrations/utils"
|
||||
import { interpolateSQL } from "../integrations/queries/sql"
|
||||
|
@ -115,7 +115,7 @@ class QueryRunner {
|
|||
}
|
||||
|
||||
let output = threadUtils.formatResponse(await integration[queryVerb](query))
|
||||
let rows = output,
|
||||
let rows = output as Row[],
|
||||
info = undefined,
|
||||
extra = undefined,
|
||||
pagination = undefined
|
||||
|
@ -170,7 +170,12 @@ class QueryRunner {
|
|||
}
|
||||
|
||||
// get all the potential fields in the schema
|
||||
let keys = rows.flatMap(Object.keys)
|
||||
const keysSet: Set<string> = new Set()
|
||||
rows.forEach(row => {
|
||||
const keys = Object.keys(row)
|
||||
keys.forEach(key => keysSet.add(key))
|
||||
})
|
||||
const keys: string[] = [...keysSet]
|
||||
|
||||
if (integration.end) {
|
||||
integration.end()
|
||||
|
|
|
@ -22,7 +22,6 @@ export interface DuplicateAppRequest {
|
|||
export interface DuplicateAppResponse {
|
||||
duplicateAppId: string
|
||||
sourceAppId: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface FetchAppDefinitionResponse {
|
||||
|
|
Loading…
Reference in New Issue