Browse Source

fix: better state and cache management

pull/7202/head
mertmit 10 months ago
parent
commit
179a7bd934
  1. 20
      packages/nc-gui/components/cell/User.vue
  2. 45
      packages/nc-gui/components/project/AccessSettings.vue
  3. 4
      packages/nc-gui/components/project/View.vue
  4. 4
      packages/nc-gui/store/base.ts
  5. 34
      packages/nc-gui/store/bases.ts
  6. 3
      packages/nc-gui/store/users.ts
  7. 2
      packages/nocodb/src/models/Base.ts
  8. 67
      packages/nocodb/src/models/BaseUser.ts
  9. 18
      packages/nocodb/src/services/base-users/base-users.service.ts

20
packages/nc-gui/components/cell/User.vue

@ -45,9 +45,11 @@ const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false)) const activeCell = inject(ActiveCellInj, ref(false))
const workspaceStore = useWorkspace() const basesStore = useBases()
const { activeWorkspace } = storeToRefs(workspaceStore) const { basesUser, activeProjectId } = storeToRefs(basesStore)
const baseUsers = computed(() => (activeProjectId.value ? basesUser.value.get(activeProjectId.value) || [] : []))
// use both ActiveCellInj or EditModeInj to determine the active state // use both ActiveCellInj or EditModeInj to determine the active state
// since active will be false in case of form view // since active will be false in case of form view
@ -75,7 +77,7 @@ const options = computed<{ id: string; email: string; display_name: string }[]>(
const collaborators: { id: string; email: string; display_name: string }[] = [] const collaborators: { id: string; email: string; display_name: string }[] = []
collaborators.push( collaborators.push(
...(activeWorkspace.value.collaborators?.map((user: any) => ({ ...(baseUsers.value?.map((user: any) => ({
id: user.id, id: user.id,
email: user.email, email: user.email,
display_name: user.display_name, display_name: user.display_name,
@ -151,6 +153,11 @@ watch(isOpen, (n, _o) => {
} }
}) })
// set isOpen to false when active cell is changed
watch(active, (n, _o) => {
if (!n) isOpen.value = false
})
useSelectedCellKeyupListener(activeCell, (e) => { useSelectedCellKeyupListener(activeCell, (e) => {
switch (e.key) { switch (e.key) {
case 'Escape': case 'Escape':
@ -240,9 +247,10 @@ useEventListener(document, 'click', handleClose, true)
// search with email // search with email
const filterOption = (input: string, option: any) => { const filterOption = (input: string, option: any) => {
const email = options.value.find((o) => o.id === option.value)?.email const opt = options.value.find((o) => o.id === option.value)
if (email) { const searchVal = opt?.display_name || opt?.email
return email.toLowerCase().includes(input.toLowerCase()) if (searchVal) {
return searchVal.toLowerCase().includes(input.toLowerCase())
} }
} }
</script> </script>

45
packages/nc-gui/components/project/AccessSettings.vue

@ -8,11 +8,11 @@ import {
timeAgo, timeAgo,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import type { WorkspaceUserRoles } from 'nocodb-sdk' import type { WorkspaceUserRoles } from 'nocodb-sdk'
import InfiniteLoading from 'v3-infinite-loading' import { OrderedProjectRoles, OrgUserRoles, ProjectRoles, WorkspaceRolesToProjectRoles, extractRolesObj } from 'nocodb-sdk'
import { isEeUI, storeToRefs } from '#imports' import { isEeUI, storeToRefs, timeAgo } from '#imports'
const basesStore = useBases() const basesStore = useBases()
const { getProjectUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore const { getBaseUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore
const { activeProjectId } = storeToRefs(basesStore) const { activeProjectId } = storeToRefs(basesStore)
const { orgRoles, baseRoles } = useRoles() const { orgRoles, baseRoles } = useRoles()
@ -30,7 +30,6 @@ interface Collaborators {
const collaborators = ref<Collaborators[]>([]) const collaborators = ref<Collaborators[]>([])
const totalCollaborators = ref(0) const totalCollaborators = ref(0)
const userSearchText = ref('') const userSearchText = ref('')
const currentPage = ref(0)
const isLoading = ref(false) const isLoading = ref(false)
const isSearching = ref(false) const isSearching = ref(false)
@ -38,18 +37,14 @@ const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>(
const loadCollaborators = async () => { const loadCollaborators = async () => {
try { try {
currentPage.value += 1 const { users, totalRows } = await getBaseUsers({
const { users, totalRows } = await getProjectUsers({
baseId: activeProjectId.value!, baseId: activeProjectId.value!,
page: currentPage.value,
...(!userSearchText.value ? {} : ({ searchText: userSearchText.value } as any)), ...(!userSearchText.value ? {} : ({ searchText: userSearchText.value } as any)),
limit: 20, force: true,
}) })
totalCollaborators.value = totalRows totalCollaborators.value = totalRows
collaborators.value = [ collaborators.value = [
...collaborators.value,
...users.map((user: any) => ({ ...users.map((user: any) => ({
...user, ...user,
base_roles: user.roles, base_roles: user.roles,
@ -66,24 +61,6 @@ const loadCollaborators = async () => {
} }
} }
const loadListData = async ($state: any) => {
const prevUsersCount = collaborators.value?.length || 0
if (collaborators.value?.length === totalCollaborators.value) {
$state.complete()
return
}
$state.loading()
// const oldPagesCount = currentPage.value || 0
await loadCollaborators()
if (prevUsersCount === collaborators.value?.length) {
$state.complete()
return
}
$state.loaded()
}
const updateCollaborator = async (collab: any, roles: ProjectRoles) => { const updateCollaborator = async (collab: any, roles: ProjectRoles) => {
try { try {
if ( if (
@ -120,7 +97,6 @@ watchDebounced(
async () => { async () => {
isSearching.value = true isSearching.value = true
currentPage.value = 0
totalCollaborators.value = 0 totalCollaborators.value = 0
collaborators.value = [] collaborators.value = []
@ -224,17 +200,6 @@ onMounted(async () => {
</div> </div>
</div> </div>
</div> </div>
<InfiniteLoading v-bind="$attrs" @infinite="loadListData">
<template #spinner>
<div class="flex flex-row w-full justify-center mt-2">
<GeneralLoader />
</div>
</template>
<template #complete>
<span></span>
</template>
</InfiniteLoading>
</div> </div>
</template> </template>
</div> </div>

4
packages/nc-gui/components/project/View.vue

@ -5,7 +5,7 @@ import { isEeUI } from '#imports'
const basesStore = useBases() const basesStore = useBases()
const { getProjectUsers } = basesStore const { getBaseUsers } = basesStore
const { openedProject, activeProjectId, baseUserCount } = storeToRefs(basesStore) const { openedProject, activeProjectId, baseUserCount } = storeToRefs(basesStore)
const { activeTables } = storeToRefs(useTablesStore()) const { activeTables } = storeToRefs(useTablesStore())
@ -38,7 +38,7 @@ const updateBaseUserCount = async () => {
if (!baseUserCount || !isUIAllowed('newUser')) return if (!baseUserCount || !isUIAllowed('newUser')) return
try { try {
const { totalRows } = await getProjectUsers({ const { totalRows } = await getBaseUsers({
baseId: activeProjectId.value!, baseId: activeProjectId.value!,
page: 1, page: 1,
searchText: undefined, searchText: undefined,

4
packages/nc-gui/store/base.ts

@ -172,6 +172,10 @@ export const useBase = defineStore('baseStore', () => {
await loadTables() await loadTables()
await basesStore.getBaseUsers({
baseId: base.value.id || baseId.value,
})
// if (withTheme) setTheme(baseMeta.value?.theme) // if (withTheme) setTheme(baseMeta.value?.theme)
return baseLoadedHook.trigger(base.value) return baseLoadedHook.trigger(base.value)

34
packages/nc-gui/store/bases.ts

@ -12,6 +12,8 @@ export const useBases = defineStore('basesStore', () => {
const bases = ref<Map<string, NcProject>>(new Map()) const bases = ref<Map<string, NcProject>>(new Map())
const basesList = computed<NcProject[]>(() => Array.from(bases.value.values()).sort((a, b) => a.updated_at - b.updated_at)) const basesList = computed<NcProject[]>(() => Array.from(bases.value.values()).sort((a, b) => a.updated_at - b.updated_at))
const basesUser = ref<Map<string, User[]>>(new Map())
const baseUserCount = ref<number | undefined>(undefined) const baseUserCount = ref<number | undefined>(undefined)
const router = useRouter() const router = useRouter()
@ -48,33 +50,35 @@ export const useBases = defineStore('basesStore', () => {
const isProjectsLoading = ref(false) const isProjectsLoading = ref(false)
async function getProjectUsers({ async function getBaseUsers({ baseId, searchText, force = false }: { baseId: string; searchText?: string; force?: boolean }) {
baseId, if (!force && !limit && !page && basesUser.value.has(baseId)) {
limit, const users = basesUser.value.get(baseId)
page, return {
searchText, users,
}: { totalRows: users?.length ?? 0,
baseId: string }
limit: number }
page: number
searchText: string | undefined
}) {
const response: any = await api.auth.baseUserList(baseId, { const response: any = await api.auth.baseUserList(baseId, {
query: { query: {
limit,
offset: (page - 1) * limit,
query: searchText, query: searchText,
}, },
} as RequestParams) } as RequestParams)
const totalRows = response.users.pageInfo.totalRows ?? 0 const totalRows = response.users.pageInfo.totalRows ?? 0
basesUser.value.set(baseId, response.users.list)
return { return {
users: response.users.list, users: response.users.list,
totalRows, totalRows,
} }
} }
const clearBasesUser = () => {
basesUser.value.clear()
}
const createProjectUser = async (baseId: string, user: User) => { const createProjectUser = async (baseId: string, user: User) => {
await api.auth.baseUserAdd(baseId, user as ProjectUserReqType) await api.auth.baseUserAdd(baseId, user as ProjectUserReqType)
} }
@ -312,13 +316,15 @@ export const useBases = defineStore('basesStore', () => {
activeProjectId, activeProjectId,
openedProject, openedProject,
openedProjectBasesMap, openedProjectBasesMap,
getProjectUsers, getBaseUsers,
createProjectUser, createProjectUser,
updateProjectUser, updateProjectUser,
navigateToProject, navigateToProject,
removeProjectUser, removeProjectUser,
navigateToFirstProjectOrHome, navigateToFirstProjectOrHome,
toggleStarred, toggleStarred,
basesUser,
clearBasesUser,
} }
}) })

3
packages/nc-gui/store/users.ts

@ -4,6 +4,7 @@ export const useUsers = defineStore('userStore', () => {
const { api } = useApi() const { api } = useApi()
const { user } = useGlobal() const { user } = useGlobal()
const { loadRoles } = useRoles() const { loadRoles } = useRoles()
const basesStore = useBases()
const updateUserProfile = async ({ const updateUserProfile = async ({
attrs, attrs,
@ -20,6 +21,8 @@ export const useUsers = defineStore('userStore', () => {
...user.value, ...user.value,
...attrs, ...attrs,
} }
basesStore.clearBasesUser()
} }
const loadCurrentUser = loadRoles const loadCurrentUser = loadRoles

2
packages/nocodb/src/models/Base.ts

@ -295,8 +295,6 @@ export default class Base implements BaseType {
let base = await this.get(baseId); let base = await this.get(baseId);
const users = await BaseUser.getUsersList({ const users = await BaseUser.getUsersList({
base_id: baseId, base_id: baseId,
offset: 0,
limit: 1000,
}); });
for (const user of users) { for (const user of users) {

67
packages/nocodb/src/models/BaseUser.ts

@ -3,7 +3,6 @@ import type { BaseType } from 'nocodb-sdk';
import User from '~/models/User'; import User from '~/models/User';
import Base from '~/models/Base'; import Base from '~/models/Base';
import { import {
CacheDelDirection,
// CacheDelDirection, // CacheDelDirection,
CacheGetType, CacheGetType,
CacheScope, CacheScope,
@ -67,27 +66,37 @@ export default class BaseUser {
`${CacheScope.BASE_USER}:${baseId}:${userId}`, `${CacheScope.BASE_USER}:${baseId}:${userId}`,
CacheGetType.TYPE_OBJECT, CacheGetType.TYPE_OBJECT,
)); ));
if (!baseUser) { if (!baseUser || !baseUser.roles) {
baseUser = await ncMeta.metaGet2(null, null, MetaTable.PROJECT_USERS, { const queryBuilder = ncMeta
fk_user_id: userId, .knex(MetaTable.USERS)
base_id: baseId, .select(
`${MetaTable.USERS}.id`,
`${MetaTable.USERS}.email`,
`${MetaTable.USERS}.display_name`,
`${MetaTable.USERS}.invite_token`,
`${MetaTable.USERS}.roles as main_roles`,
`${MetaTable.USERS}.created_at as created_at`,
`${MetaTable.PROJECT_USERS}.base_id`,
`${MetaTable.PROJECT_USERS}.roles as roles`,
);
queryBuilder.leftJoin(MetaTable.PROJECT_USERS, function () {
this.on(
`${MetaTable.PROJECT_USERS}.fk_user_id`,
'=',
`${MetaTable.USERS}.id`,
).andOn(
`${MetaTable.PROJECT_USERS}.base_id`,
'=',
ncMeta.knex.raw('?', [baseId]),
);
}); });
if (baseUser) {
const {
id,
email,
invite_token,
roles: main_roles,
} = await User.get(userId, ncMeta);
baseUser = {
...baseUser,
id,
email,
invite_token,
main_roles,
};
queryBuilder.where(`${MetaTable.USERS}.id`, userId);
baseUser = await queryBuilder.first();
if (baseUser) {
await NocoCache.set( await NocoCache.set(
`${CacheScope.BASE_USER}:${baseId}:${userId}`, `${CacheScope.BASE_USER}:${baseId}:${userId}`,
baseUser, baseUser,
@ -100,13 +109,9 @@ export default class BaseUser {
public static async getUsersList( public static async getUsersList(
{ {
base_id, base_id,
limit = 25,
offset = 0,
query, query,
}: { }: {
base_id: string; base_id: string;
limit?: number;
offset?: number;
query?: string; query?: string;
}, },
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
@ -120,6 +125,7 @@ export default class BaseUser {
.select( .select(
`${MetaTable.USERS}.id`, `${MetaTable.USERS}.id`,
`${MetaTable.USERS}.email`, `${MetaTable.USERS}.email`,
`${MetaTable.USERS}.display_name`,
`${MetaTable.USERS}.invite_token`, `${MetaTable.USERS}.invite_token`,
`${MetaTable.USERS}.roles as main_roles`, `${MetaTable.USERS}.roles as main_roles`,
`${MetaTable.USERS}.created_at as created_at`, `${MetaTable.USERS}.created_at as created_at`,
@ -127,10 +133,6 @@ export default class BaseUser {
`${MetaTable.PROJECT_USERS}.roles as roles`, `${MetaTable.PROJECT_USERS}.roles as roles`,
); );
if (limit) {
queryBuilder.offset(offset).limit(limit);
}
if (query) { if (query) {
queryBuilder.where('email', 'like', `%${query.toLowerCase?.()}%`); queryBuilder.where('email', 'like', `%${query.toLowerCase?.()}%`);
} }
@ -259,12 +261,9 @@ export default class BaseUser {
}, },
); );
// delete cache // delete list cache to refresh list
await NocoCache.deepDel( await NocoCache.del(`${CacheScope.BASE_USER}:${baseId}:${userId}`);
CacheScope.BASE_USER, await NocoCache.del(`${CacheScope.BASE_USER}:${baseId}:list`);
`${CacheScope.BASE_USER}:${baseId}:${userId}`,
CacheDelDirection.CHILD_TO_PARENT,
);
return response; return response;
} }

18
packages/nocodb/src/services/base-users/base-users.service.ts

@ -28,19 +28,15 @@ export class BaseUsersService {
constructor(protected appHooksService: AppHooksService) {} constructor(protected appHooksService: AppHooksService) {}
async userList(param: { baseId: string; query: any }) { async userList(param: { baseId: string; query: any }) {
return new PagedResponseImpl( const baseUsers = await BaseUser.getUsersList({
await BaseUser.getUsersList({
...param.query, ...param.query,
base_id: param.baseId, base_id: param.baseId,
}), });
{
...param.query, return new PagedResponseImpl(baseUsers, {
count: await BaseUser.getUsersCount({
base_id: param.baseId,
...param.query, ...param.query,
}), count: baseUsers.length,
}, });
);
} }
async userInvite(param: { async userInvite(param: {
@ -110,7 +106,7 @@ export class BaseUsersService {
return NcError.badRequest('Invalid base id'); return NcError.badRequest('Invalid base id');
} }
if (baseUser) { if (baseUser && baseUser.roles) {
NcError.badRequest( NcError.badRequest(
`${user.email} with role ${baseUser.roles} already exists in this base`, `${user.email} with role ${baseUser.roles} already exists in this base`,
); );

Loading…
Cancel
Save