Merge branch 'master' into budi-7786-options-picker-dropdown-opens-above-even-though-it-is-at-the
This commit is contained in:
commit
4b63a06162
18 changed files with 183 additions and 40 deletions
|
@ -2,6 +2,7 @@ export * as configs from "./configs"
|
||||||
export * as events from "./events"
|
export * as events from "./events"
|
||||||
export * as migrations from "./migrations"
|
export * as migrations from "./migrations"
|
||||||
export * as users from "./users"
|
export * as users from "./users"
|
||||||
|
export * as userUtils from "./users/utils"
|
||||||
export * as roles from "./security/roles"
|
export * as roles from "./security/roles"
|
||||||
export * as permissions from "./security/permissions"
|
export * as permissions from "./security/permissions"
|
||||||
export * as accounts from "./accounts"
|
export * as accounts from "./accounts"
|
||||||
|
|
|
@ -251,7 +251,8 @@ export class UserDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = dbUser ? 0 : 1 // no change if there is existing user
|
const change = dbUser ? 0 : 1 // no change if there is existing user
|
||||||
const creatorsChange = isCreator(dbUser) !== isCreator(user) ? 1 : 0
|
const creatorsChange =
|
||||||
|
(await isCreator(dbUser)) !== (await isCreator(user)) ? 1 : 0
|
||||||
return UserDB.quotas.addUsers(change, creatorsChange, async () => {
|
return UserDB.quotas.addUsers(change, creatorsChange, async () => {
|
||||||
await validateUniqueUser(email, tenantId)
|
await validateUniqueUser(email, tenantId)
|
||||||
|
|
||||||
|
@ -335,7 +336,7 @@ export class UserDB {
|
||||||
}
|
}
|
||||||
newUser.userGroups = groups || []
|
newUser.userGroups = groups || []
|
||||||
newUsers.push(newUser)
|
newUsers.push(newUser)
|
||||||
if (isCreator(newUser)) {
|
if (await isCreator(newUser)) {
|
||||||
newCreators.push(newUser)
|
newCreators.push(newUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,12 +433,16 @@ export class UserDB {
|
||||||
_deleted: true,
|
_deleted: true,
|
||||||
}))
|
}))
|
||||||
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
||||||
const creatorsToDelete = usersToDelete.filter(isCreator)
|
|
||||||
|
const creatorsEval = await Promise.all(usersToDelete.map(isCreator))
|
||||||
|
const creatorsToDeleteCount = creatorsEval.filter(
|
||||||
|
creator => !!creator
|
||||||
|
).length
|
||||||
|
|
||||||
for (let user of usersToDelete) {
|
for (let user of usersToDelete) {
|
||||||
await bulkDeleteProcessing(user)
|
await bulkDeleteProcessing(user)
|
||||||
}
|
}
|
||||||
await UserDB.quotas.removeUsers(toDelete.length, creatorsToDelete.length)
|
await UserDB.quotas.removeUsers(toDelete.length, creatorsToDeleteCount)
|
||||||
|
|
||||||
// Build Response
|
// Build Response
|
||||||
// index users by id
|
// index users by id
|
||||||
|
@ -486,7 +491,7 @@ export class UserDB {
|
||||||
|
|
||||||
await db.remove(userId, dbUser._rev)
|
await db.remove(userId, dbUser._rev)
|
||||||
|
|
||||||
const creatorsToDelete = isCreator(dbUser) ? 1 : 0
|
const creatorsToDelete = (await isCreator(dbUser)) ? 1 : 0
|
||||||
await UserDB.quotas.removeUsers(1, creatorsToDelete)
|
await UserDB.quotas.removeUsers(1, creatorsToDelete)
|
||||||
await eventHelpers.handleDeleteEvents(dbUser)
|
await eventHelpers.handleDeleteEvents(dbUser)
|
||||||
await cache.user.invalidateUser(userId)
|
await cache.user.invalidateUser(userId)
|
||||||
|
|
67
packages/backend-core/src/users/test/utils.spec.ts
Normal file
67
packages/backend-core/src/users/test/utils.spec.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { User, UserGroup } from "@budibase/types"
|
||||||
|
import { generator, structures } from "../../../tests"
|
||||||
|
import { DBTestConfiguration } from "../../../tests/extra"
|
||||||
|
import { getGlobalDB } from "../../context"
|
||||||
|
import { isCreator } from "../utils"
|
||||||
|
|
||||||
|
const config = new DBTestConfiguration()
|
||||||
|
|
||||||
|
describe("Users", () => {
|
||||||
|
it("User is a creator if it is configured as a global builder", async () => {
|
||||||
|
const user: User = structures.users.user({ builder: { global: true } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it is configured as a global admin", async () => {
|
||||||
|
const user: User = structures.users.user({ admin: { global: true } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it is configured with creator permission", async () => {
|
||||||
|
const user: User = structures.users.user({ builder: { creator: true } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it is a builder in some application", async () => {
|
||||||
|
const user: User = structures.users.user({ builder: { apps: ["app1"] } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it has CREATOR permission in some application", async () => {
|
||||||
|
const user: User = structures.users.user({ roles: { app1: "CREATOR" } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it has ADMIN permission in some application", async () => {
|
||||||
|
const user: User = structures.users.user({ roles: { app1: "ADMIN" } })
|
||||||
|
expect(await isCreator(user)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("User is a creator if it remains to a group with ADMIN permissions", async () => {
|
||||||
|
const usersInGroup = 10
|
||||||
|
const groupId = "gr_17abffe89e0b40268e755b952f101a59"
|
||||||
|
const group: UserGroup = {
|
||||||
|
...structures.userGroups.userGroup(),
|
||||||
|
...{ _id: groupId, roles: { app1: "ADMIN" } },
|
||||||
|
}
|
||||||
|
const users: User[] = []
|
||||||
|
for (const _ of Array.from({ length: usersInGroup })) {
|
||||||
|
const userId = `us_${generator.guid()}`
|
||||||
|
const user: User = structures.users.user({
|
||||||
|
_id: userId,
|
||||||
|
userGroups: [groupId],
|
||||||
|
})
|
||||||
|
users.push(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
await config.doInTenant(async () => {
|
||||||
|
const db = getGlobalDB()
|
||||||
|
await db.put(group)
|
||||||
|
for (let user of users) {
|
||||||
|
await db.put(user)
|
||||||
|
const creator = await isCreator(user)
|
||||||
|
expect(creator).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -309,7 +309,8 @@ export async function getCreatorCount() {
|
||||||
let creators = 0
|
let creators = 0
|
||||||
async function iterate(startPage?: string) {
|
async function iterate(startPage?: string) {
|
||||||
const page = await paginatedUsers({ bookmark: startPage })
|
const page = await paginatedUsers({ bookmark: startPage })
|
||||||
creators += page.data.filter(isCreator).length
|
const creatorsEval = await Promise.all(page.data.map(isCreator))
|
||||||
|
creators += creatorsEval.filter(creator => !!creator).length
|
||||||
if (page.hasNextPage) {
|
if (page.hasNextPage) {
|
||||||
await iterate(page.nextPage)
|
await iterate(page.nextPage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CloudAccount } from "@budibase/types"
|
import { CloudAccount, ContextUser, User, UserGroup } from "@budibase/types"
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { getPlatformUser } from "./lookup"
|
import { getPlatformUser } from "./lookup"
|
||||||
|
@ -6,17 +6,48 @@ import { EmailUnavailableError } from "../errors"
|
||||||
import { getTenantId } from "../context"
|
import { getTenantId } from "../context"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { getAccountByTenantId } from "../accounts"
|
import { getAccountByTenantId } from "../accounts"
|
||||||
|
import { BUILTIN_ROLE_IDS } from "../security/roles"
|
||||||
|
import * as context from "../context"
|
||||||
|
|
||||||
// extract from shared-core to make easily accessible from backend-core
|
// extract from shared-core to make easily accessible from backend-core
|
||||||
export const isBuilder = sdk.users.isBuilder
|
export const isBuilder = sdk.users.isBuilder
|
||||||
export const isAdmin = sdk.users.isAdmin
|
export const isAdmin = sdk.users.isAdmin
|
||||||
export const isCreator = sdk.users.isCreator
|
|
||||||
export const isGlobalBuilder = sdk.users.isGlobalBuilder
|
export const isGlobalBuilder = sdk.users.isGlobalBuilder
|
||||||
export const isAdminOrBuilder = sdk.users.isAdminOrBuilder
|
export const isAdminOrBuilder = sdk.users.isAdminOrBuilder
|
||||||
export const hasAdminPermissions = sdk.users.hasAdminPermissions
|
export const hasAdminPermissions = sdk.users.hasAdminPermissions
|
||||||
export const hasBuilderPermissions = sdk.users.hasBuilderPermissions
|
export const hasBuilderPermissions = sdk.users.hasBuilderPermissions
|
||||||
export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions
|
export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions
|
||||||
|
|
||||||
|
export async function isCreator(user?: User | ContextUser) {
|
||||||
|
const isCreatorByUserDefinition = sdk.users.isCreator(user)
|
||||||
|
if (!isCreatorByUserDefinition && user) {
|
||||||
|
return await isCreatorByGroupMembership(user)
|
||||||
|
}
|
||||||
|
return isCreatorByUserDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isCreatorByGroupMembership(user?: User | ContextUser) {
|
||||||
|
const userGroups = user?.userGroups || []
|
||||||
|
if (userGroups.length > 0) {
|
||||||
|
const db = context.getGlobalDB()
|
||||||
|
const groups: UserGroup[] = []
|
||||||
|
for (let groupId of userGroups) {
|
||||||
|
try {
|
||||||
|
const group = await db.get<UserGroup>(groupId)
|
||||||
|
groups.push(group)
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.error !== "not_found") {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups.some(group =>
|
||||||
|
Object.values(group.roles || {}).includes(BUILTIN_ROLE_IDS.ADMIN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
export async function validateUniqueUser(email: string, tenantId: string) {
|
export async function validateUniqueUser(email: string, tenantId: string) {
|
||||||
// check budibase users in other tenants
|
// check budibase users in other tenants
|
||||||
if (env.MULTI_TENANCY) {
|
if (env.MULTI_TENANCY) {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
export let getOptionIcon = () => null
|
export let getOptionIcon = () => null
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = () => null
|
||||||
export let getOptionSubtitle = () => null
|
export let getOptionSubtitle = () => null
|
||||||
export let compare = (option, value) => option === value
|
export let compare = null
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
@ -33,13 +33,19 @@
|
||||||
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
||||||
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
||||||
|
|
||||||
|
function compareOptionAndValue(option, value) {
|
||||||
|
return typeof compare === "function"
|
||||||
|
? compare(option, value)
|
||||||
|
: option === value
|
||||||
|
}
|
||||||
|
|
||||||
const getFieldAttribute = (getAttribute, value, options) => {
|
const getFieldAttribute = (getAttribute, value, options) => {
|
||||||
// Wait for options to load if there is a value but no options
|
// Wait for options to load if there is a value but no options
|
||||||
if (!options?.length) {
|
if (!options?.length) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const index = options.findIndex((option, idx) =>
|
const index = options.findIndex((option, idx) =>
|
||||||
compare(getOptionValue(option, idx), value)
|
compareOptionAndValue(getOptionValue(option, idx), value)
|
||||||
)
|
)
|
||||||
return index !== -1 ? getAttribute(options[index], index) : null
|
return index !== -1 ? getAttribute(options[index], index) : null
|
||||||
}
|
}
|
||||||
|
@ -91,7 +97,7 @@
|
||||||
{tag}
|
{tag}
|
||||||
isPlaceholder={value == null || value === ""}
|
isPlaceholder={value == null || value === ""}
|
||||||
placeholderOption={placeholder === false ? null : placeholder}
|
placeholderOption={placeholder === false ? null : placeholder}
|
||||||
isOptionSelected={option => compare(option, value)}
|
isOptionSelected={option => compareOptionAndValue(option, value)}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
{loading}
|
{loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { createHistoryStore } from "builderStore/store/history"
|
import { createHistoryStore } from "builderStore/store/history"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { getHoverStore } from "./store/hover"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
|
@ -16,6 +17,7 @@ export const themeStore = getThemeStore()
|
||||||
export const temporalStore = getTemporalStore()
|
export const temporalStore = getTemporalStore()
|
||||||
export const userStore = getUserStore()
|
export const userStore = getUserStore()
|
||||||
export const deploymentStore = getDeploymentStore()
|
export const deploymentStore = getDeploymentStore()
|
||||||
|
export const hoverStore = getHoverStore()
|
||||||
|
|
||||||
// Setup history for screens
|
// Setup history for screens
|
||||||
export const screenHistoryStore = createHistoryStore({
|
export const screenHistoryStore = createHistoryStore({
|
||||||
|
|
|
@ -92,9 +92,6 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
// Onboarding
|
// Onboarding
|
||||||
onboarding: false,
|
onboarding: false,
|
||||||
tourNodes: null,
|
tourNodes: null,
|
||||||
|
|
||||||
// UI state
|
|
||||||
hoveredComponentId: null,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendStore = () => {
|
export const getFrontendStore = () => {
|
||||||
|
@ -1415,18 +1412,6 @@ export const getFrontendStore = () => {
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
hover: (componentId, notifyClient = true) => {
|
|
||||||
if (componentId === get(store).hoveredComponentId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
store.update(state => {
|
|
||||||
state.hoveredComponentId = componentId
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
if (notifyClient) {
|
|
||||||
store.actions.preview.sendEvent("hover-component", componentId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
links: {
|
links: {
|
||||||
save: async (url, title) => {
|
save: async (url, title) => {
|
||||||
|
|
27
packages/builder/src/builderStore/store/hover.js
Normal file
27
packages/builder/src/builderStore/store/hover.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { get, writable } from "svelte/store"
|
||||||
|
import { store as builder } from "builderStore"
|
||||||
|
|
||||||
|
export const getHoverStore = () => {
|
||||||
|
const initialValue = {
|
||||||
|
componentId: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = writable(initialValue)
|
||||||
|
|
||||||
|
const update = (componentId, notifyClient = true) => {
|
||||||
|
if (componentId === get(store).componentId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.update(state => {
|
||||||
|
state.componentId = componentId
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
if (notifyClient) {
|
||||||
|
builder.actions.preview.sendEvent("hover-component", componentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: { update },
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { getEventContextBindings } from "builderStore/dataBinding"
|
import { getEventContextBindings } from "builderStore/dataBinding"
|
||||||
|
import { cloneDeep, isEqual } from "lodash/fp"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let componentBindings
|
export let componentBindings
|
||||||
|
@ -17,8 +18,13 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let focusItem
|
let focusItem
|
||||||
|
let cachedValue
|
||||||
|
|
||||||
$: buttonList = sanitizeValue(value) || []
|
$: if (!isEqual(value, cachedValue)) {
|
||||||
|
cachedValue = cloneDeep(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
$: buttonList = sanitizeValue(cachedValue) || []
|
||||||
$: buttonCount = buttonList.length
|
$: buttonCount = buttonList.length
|
||||||
$: eventContextBindings = getEventContextBindings({
|
$: eventContextBindings = getEventContextBindings({
|
||||||
componentInstance,
|
componentInstance,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { store, selectedScreen, currentAsset } from "builderStore"
|
import { store, selectedScreen, currentAsset, hoverStore } from "builderStore"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import {
|
||||||
ProgressCircle,
|
ProgressCircle,
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
} else if (type === "select-component" && data.id) {
|
} else if (type === "select-component" && data.id) {
|
||||||
$store.selectedComponentId = data.id
|
$store.selectedComponentId = data.id
|
||||||
} else if (type === "hover-component") {
|
} else if (type === "hover-component") {
|
||||||
store.actions.components.hover(data.id, false)
|
hoverStore.actions.update(data.id, false)
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
await store.actions.components.updateSetting(data.prop, data.value)
|
await store.actions.components.updateSetting(data.prop, data.value)
|
||||||
} else if (type === "update-styles") {
|
} else if (type === "update-styles") {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
selectedComponentPath,
|
selectedComponentPath,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
|
hoverStore,
|
||||||
} from "builderStore"
|
} from "builderStore"
|
||||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
@ -90,7 +91,7 @@
|
||||||
return findComponentPath($selectedComponent, component._id)?.length > 0
|
return findComponentPath($selectedComponent, component._id)?.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const hover = store.actions.components.hover
|
const hover = hoverStore.actions.update
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -111,7 +112,7 @@
|
||||||
on:dragover={dragover(component, index)}
|
on:dragover={dragover(component, index)}
|
||||||
on:iconClick={() => toggleNodeOpen(component._id)}
|
on:iconClick={() => toggleNodeOpen(component._id)}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
hovering={$store.hoveredComponentId === component._id}
|
hovering={$hoverStore.componentId === component._id}
|
||||||
on:mouseenter={() => hover(component._id)}
|
on:mouseenter={() => hover(component._id)}
|
||||||
on:mouseleave={() => hover(null)}
|
on:mouseleave={() => hover(null)}
|
||||||
text={getComponentText(component)}
|
text={getComponentText(component)}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { notifications, Icon, Body } from "@budibase/bbui"
|
import { notifications, Icon, Body } from "@budibase/bbui"
|
||||||
import { isActive, goto } from "@roxi/routify"
|
import { isActive, goto } from "@roxi/routify"
|
||||||
import { store, selectedScreen, userSelectedResourceMap } from "builderStore"
|
import {
|
||||||
|
store,
|
||||||
|
selectedScreen,
|
||||||
|
userSelectedResourceMap,
|
||||||
|
hoverStore,
|
||||||
|
} from "builderStore"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import ComponentTree from "./ComponentTree.svelte"
|
import ComponentTree from "./ComponentTree.svelte"
|
||||||
import { dndStore, DropPosition } from "./dndStore.js"
|
import { dndStore, DropPosition } from "./dndStore.js"
|
||||||
|
@ -36,7 +41,7 @@
|
||||||
scrolling = e.target.scrollTop !== 0
|
scrolling = e.target.scrollTop !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const hover = store.actions.components.hover
|
const hover = hoverStore.actions.update
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="components">
|
<div class="components">
|
||||||
|
@ -60,7 +65,7 @@
|
||||||
icon="WebPage"
|
icon="WebPage"
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
on:click={() => ($store.selectedComponentId = screenComponentId)}
|
on:click={() => ($store.selectedComponentId = screenComponentId)}
|
||||||
hovering={$store.hoveredComponentId === screenComponentId}
|
hovering={$hoverStore.componentId === screenComponentId}
|
||||||
on:mouseenter={() => hover(screenComponentId)}
|
on:mouseenter={() => hover(screenComponentId)}
|
||||||
on:mouseleave={() => hover(null)}
|
on:mouseleave={() => hover(null)}
|
||||||
id="component-screen"
|
id="component-screen"
|
||||||
|
@ -79,7 +84,7 @@
|
||||||
: "VisibilityOff"}
|
: "VisibilityOff"}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
on:click={() => ($store.selectedComponentId = navComponentId)}
|
on:click={() => ($store.selectedComponentId = navComponentId)}
|
||||||
hovering={$store.hoveredComponentId === navComponentId}
|
hovering={$hoverStore.componentId === navComponentId}
|
||||||
on:mouseenter={() => hover(navComponentId)}
|
on:mouseenter={() => hover(navComponentId)}
|
||||||
on:mouseleave={() => hover(null)}
|
on:mouseleave={() => hover(null)}
|
||||||
id="component-nav"
|
id="component-nav"
|
||||||
|
|
|
@ -108,10 +108,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: forceFetchRows(filter)
|
$: forceFetchRows(filter, fieldApi)
|
||||||
$: debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
|
$: debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
|
||||||
|
|
||||||
const forceFetchRows = async () => {
|
const forceFetchRows = async () => {
|
||||||
|
if (!fieldApi) {
|
||||||
|
return
|
||||||
|
}
|
||||||
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
|
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
|
||||||
optionsObj = {}
|
optionsObj = {}
|
||||||
fieldApi.setValue([])
|
fieldApi.setValue([])
|
||||||
|
|
|
@ -34,7 +34,7 @@ const checkAuthorized = async (
|
||||||
const isCreatorApi = permType === PermissionType.CREATOR
|
const isCreatorApi = permType === PermissionType.CREATOR
|
||||||
const isBuilderApi = permType === PermissionType.BUILDER
|
const isBuilderApi = permType === PermissionType.BUILDER
|
||||||
const isGlobalBuilder = users.isGlobalBuilder(ctx.user)
|
const isGlobalBuilder = users.isGlobalBuilder(ctx.user)
|
||||||
const isCreator = users.isCreator(ctx.user)
|
const isCreator = await users.isCreator(ctx.user)
|
||||||
const isBuilder = appId
|
const isBuilder = appId
|
||||||
? users.isBuilder(ctx.user, appId)
|
? users.isBuilder(ctx.user, appId)
|
||||||
: users.hasBuilderPermissions(ctx.user)
|
: users.hasBuilderPermissions(ctx.user)
|
||||||
|
|
|
@ -84,7 +84,7 @@ describe("syncGlobalUsers", () => {
|
||||||
await syncGlobalUsers()
|
await syncGlobalUsers()
|
||||||
|
|
||||||
const metadata = await rawUserMetadata()
|
const metadata = await rawUserMetadata()
|
||||||
expect(metadata).toHaveLength(3)
|
expect(metadata).toHaveLength(2)
|
||||||
expect(metadata).toContainEqual(
|
expect(metadata).toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
_id: db.generateUserMetadataID(user1._id!),
|
_id: db.generateUserMetadataID(user1._id!),
|
||||||
|
@ -121,7 +121,7 @@ describe("syncGlobalUsers", () => {
|
||||||
await syncGlobalUsers()
|
await syncGlobalUsers()
|
||||||
|
|
||||||
const metadata = await rawUserMetadata()
|
const metadata = await rawUserMetadata()
|
||||||
expect(metadata).toHaveLength(0)
|
expect(metadata).toHaveLength(1) //ADMIN user created in test bootstrap still in the application
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function hasAppCreatorPermissions(user?: User | ContextUser): boolean {
|
||||||
return _.flow(
|
return _.flow(
|
||||||
_.get("roles"),
|
_.get("roles"),
|
||||||
_.values,
|
_.values,
|
||||||
_.find(x => x === "CREATOR"),
|
_.find(x => ["CREATOR", "ADMIN"].includes(x)),
|
||||||
x => !!x
|
x => !!x
|
||||||
)(user)
|
)(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,9 @@ export async function getSelf(ctx: any) {
|
||||||
id: userId,
|
id: userId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust creators quotas (prevents wrong creators count if user has changed the plan)
|
||||||
|
await groups.adjustGroupCreatorsQuotas()
|
||||||
|
|
||||||
// get the main body of the user
|
// get the main body of the user
|
||||||
const user = await userSdk.db.getUser(userId)
|
const user = await userSdk.db.getUser(userId)
|
||||||
ctx.body = await groups.enrichUserRolesFromGroups(user)
|
ctx.body = await groups.enrichUserRolesFromGroups(user)
|
||||||
|
|
Loading…
Reference in a new issue