Browse Source

feat: assign view owner

pull/9807/head
Pranav C 2 weeks ago
parent
commit
d2b005709d
  1. 151
      packages/nc-gui/components/dlg/ReAssign.vue
  2. 28
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  3. 2
      packages/nc-gui/lang/en.json
  4. 1
      packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts
  5. 4
      packages/nocodb/src/schema/swagger-v2.json
  6. 8
      packages/nocodb/src/schema/swagger.json
  7. 26
      packages/nocodb/src/services/views.service.ts

151
packages/nc-gui/components/dlg/ReAssign.vue

@ -0,0 +1,151 @@
<script lang="ts" setup>
const { loadUsers, users } = useManageUsers()
interface Props {
modelValue: boolean
view?: Record<string, any>
}
interface Emits {
(event: 'update:modelValue', data: boolean): void
}
const props = defineProps<Props>()
const emits = defineEmits<Emits>()
const vModel = useVModel(props, 'modelValue', emits)
const { t } = useI18n()
const useForm = Form.useForm
const validators = computed(() => {
return {
emails: [
{
validator: (rule: any, value: string, callback: (errMsg?: string) => void) => {
if (!value || value.length === 0) {
callback(t('msg.error.signUpRules.emailRequired'))
return
}
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e))
if (invalidEmails.length > 0) {
callback(
`${
invalidEmails.length > 1 ? t('msg.error.signUpRules.invalidEmails') : t('msg.error.signUpRules.invalidEmail')
} ${invalidEmails.join(', ')} `,
)
} else {
callback()
}
},
},
],
}
})
const invitationUsersData = ref({})
const { validateInfos } = useForm(invitationUsersData, validators)
onMounted(async () => {
if (!users.value) {
await loadUsers()
}
})
watch(
() => validateInfos.emails.validateStatus,
() => {
invitationValid.value = validateInfos.emails.validateStatus === 'success' && invitationUsersData.emails?.length !== 0
},
)
const basesStore = useBases()
const baseStore = useBase()
const { basesUser } = storeToRefs(basesStore)
const { idUserMap } = storeToRefs(baseStore)
const baseUsers = computed(() => (props.view.base_id ? basesUser.value.get(props.view.base_id) || [] : []))
const {user} = useGlobal()
const searchQuery = ref('')
</script>
<template>
<NcModal v-model:visible="vModel"
wrap-class-name="nc-modal-re-assign"
width="448px"
>
<div class="mb-4">
<div class="flex text-base font-bold mb-2">Re-assign this view</div>
<div class="flex">Once reassigned, you will no longer be able to edit the view configuration.</div>
</div>
<div class="mb-4">
Current owner
<div class="flex flex-row items-center gap-x-2 h-12.5 p-2 bg-gray-100 rounded">
<GeneralUserIcon
size="auto"
:name="user.display_name?.trim() ? user.display_name?.trim() : ''"
:email="user.email"
class="!text-[0.65rem]"
/>
<div class="flex flex-col justify-center">
<div class="flex" :style="{ fontWeight: 500 }">{{ user.display_name }}</div>
<div class="flex text-xs">
{{ user.email }}
</div>
</div>
</div>
</div>
<div>
<span>
New owner
<div class="rounded border-1">
<div class="flex flex-row items-center gap-x-2 h-12.5 p-2">
<GeneralIcon icon="search" class="text-gray-500 ml-2"/>
<input v-model="searchQuery" class="border-0 px-2.5 outline-none " />
</div>
<div v-for="user of baseUsers" style="border-top: 1px solid; border-color: inherit">
<div class="flex flex-row items-center gap-x-2 h-12.5 p-2">
<GeneralUserIcon
size="auto"
:name="user.display_name?.trim() ? user.display_name?.trim() : ''"
:email="user.email"
class="!text-[0.65rem]"
/>
<div class="flex flex-col justify-center">
<div class="flex" :style="{ fontWeight: 500 }">{{ user.display_name }}</div>
<div class="flex text-xs">
{{ user.email }}
</div>
</div>
</div>
</div>
</div>
</span>
</div>
<div class="flex mt-8 justify-end">
<div class="flex gap-2">
<NcButton type="secondary" @click="dialogShow = false"> {{ $t('labels.cancel') }} </NcButton>
<NcButton
size="small"
type="primary"
class="nc-invite-btn"
>
{{ $t('activity.assignView') }}
</NcButton>
</div>
</div>
</NcModal>
</template>

28
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk'
import { resolveComponent } from '@vue/runtime-core'
import { LockType } from '#imports'
const props = withDefaults(
@ -129,6 +130,16 @@ const onDelete = async () => {
emits('delete')
}
const openReAssignDlg = () => {
const { close } = useDialog(resolveComponent('DlgReAssign'), {
'modelValue': ref(true),
'onUpdate:modelValue': () => {
close()
},
view,
})
}
/**
* ## Known Issue and Fix
* - **Issue**: When conditionally rendering `NcMenuItem` using `v-if` without a corresponding `v-else` fallback,
@ -314,6 +325,23 @@ const onDelete = async () => {
<LazySmartsheetToolbarLockType :type="LockType.Locked" @click="changeLockType(LockType.Locked)" />
</a-menu-item>
</NcSubMenu>
<NcMenuItem @click="openReAssignDlg">
<div
v-e="[
'c:navdraw:preview-as',
{
sidebar: props.inSidebar,
},
]"
class="flex flex-row items-center gap-x-3"
>
<div>
{{ $t('labels.reAssignView') }}
</div>
<div class="flex flex-grow"></div>
</div>
</NcMenuItem>
</template>
<template v-if="!view.is_default && isUIAllowed('viewCreateOrEdit')">

2
packages/nc-gui/lang/en.json

@ -799,6 +799,7 @@
"clickToCopyTableID": "Click to copy Table ID",
"clickToCopyViewID": "Click to copy View ID",
"viewMode": "View mode",
"reAssignView": "Re-Assign View",
"searchUsers": "Search Users",
"superAdmin": "Super Admin",
"allTables": "All Tables",
@ -1008,6 +1009,7 @@
"redirectToUrl": "Redirect to URL"
},
"activity": {
"assignView": "Assign view",
"webhookDetails": "Webhook Details",
"hideWeekends": "Hide weekends",
"renameBase": "Rename Base",

1
packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts

@ -687,6 +687,7 @@ export class AtImportProcessor {
base_roles: {
owner: true,
},
id: syncDB.user.id
},
});
recordPerfStats(_perfStart, 'dbView.list');

4
packages/nocodb/src/schema/swagger-v2.json

@ -21438,6 +21438,10 @@
"show_system_fields": {
"$ref": "#/components/schemas/Bool",
"description": "Should this view show system fields?"
},
"owned_by": {
"$ref": "#/components/schemas/Id",
"description": "ID of view owner user"
}
}
},

8
packages/nocodb/src/schema/swagger.json

@ -26546,6 +26546,10 @@
}
],
"description": "Associated View Model"
},
"owned_by": {
"$ref": "#/components/schemas/Id",
"description": "ID of view owner user"
}
},
"required": [
@ -26848,6 +26852,10 @@
"show_system_fields": {
"$ref": "#/components/schemas/Bool",
"description": "Should this view show system fields?"
},
"owned_by": {
"$ref": "#/components/schemas/Id",
"description": "ID of view owner user"
}
}
},

26
packages/nocodb/src/services/views.service.ts

@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AppEvents, ProjectRoles } from 'nocodb-sdk';
import { AppEvents, ProjectRoles, ViewLockType } from 'nocodb-sdk';
import type {
SharedViewReqType,
UserType,
@ -9,8 +9,7 @@ import type { NcContext, NcRequest } from '~/interface/config';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { validatePayload } from '~/helpers';
import { NcError } from '~/helpers/catchError';
import { Model, ModelRoleVisibility, View } from '~/models';
import {WorkspaceUser} from "~/ee/models";
import { BaseUser, Model, ModelRoleVisibility, View } from '~/models';
// todo: move
async function xcVisibilityMetaGet(
@ -78,6 +77,7 @@ export class ViewsService {
user: {
roles?: Record<string, boolean> | string;
base_roles?: Record<string, boolean>;
id: string;
};
},
) {
@ -95,6 +95,14 @@ export class ViewsService {
// todo: user roles
//await View.list(param.tableId)
const filteredViewList = viewList.filter((view: any) => {
if (
view.lock_type === ViewLockType.Personal &&
view.owned_by !== param.user.id &&
!(!view.owned_by && !param.user.base_roles?.[ProjectRoles.OWNER])
) {
return false;
}
return Object.values(ProjectRoles).some(
(role) => param?.user?.['base_roles']?.[role] && !view.disabled[role],
);
@ -172,14 +180,18 @@ export class ViewsService {
}
}
if(ownedBy && param.view.owned_by && param.user.id === ownedBy) {
ownedBy = param.view.owned_by
if (ownedBy && param.view.owned_by && param.user.id === ownedBy) {
ownedBy = param.view.owned_by;
// verify if the new owned_by is a valid user who have access to the base/workspace
// if not then throw error
const baseUser = await BaseUser.get(context,param.view.owned_by, context.base_id);
const baseUser = await BaseUser.get(
context,
param.view.owned_by,
context.base_id,
);
if(!baseUser){
if (!baseUser) {
NcError.badRequest('Invalid user');
}

Loading…
Cancel
Save