Browse Source

fix: add custom url update setup

Ramesh Mane 6 days ago
parent
commit
e00019b35c
  1. 70
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  2. 1
      packages/nocodb/src/models/Base.ts
  3. 45
      packages/nocodb/src/models/CustomUrl.ts
  4. 11
      packages/nocodb/src/models/View.ts
  5. 7
      packages/nocodb/src/schema/swagger.json
  6. 33
      packages/nocodb/src/services/views.service.ts

70
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -20,6 +20,7 @@ const isUpdating = ref({
public: false, public: false,
password: false, password: false,
download: false, download: false,
customUrl: false,
}) })
const activeView = computed<(ViewType & { meta: object & Record<string, any> }) | undefined>({ const activeView = computed<(ViewType & { meta: object & Record<string, any> }) | undefined>({
@ -100,6 +101,40 @@ const togglePasswordProtected = async () => {
} }
} }
const isOpenCustomUrlLocal = ref(false)
const isOpenCustomUrl = computed(() => {
return !!activeView.value?.password || isOpenCustomUrlLocal.value
})
const customUrl = computed({
get: () => (isOpenCustomUrl.value ? activeView.value?.custom_path ?? '' : ''),
set: async (value) => {
if (!activeView.value) return
activeView.value = { ...(activeView.value as any), custom_path: isOpenCustomUrl.value ? value : null }
},
})
const toggleCustomUrl = async () => {
isOpenCustomUrlLocal.value = !isOpenCustomUrl.value
if (!activeView.value) return
if (isUpdating.value.customUrl) return
isUpdating.value.password = true
try {
if (passwordProtected.value) {
activeView.value = { ...(activeView.value as any), custom_path: null }
} else {
activeView.value = { ...(activeView.value as any), custom_path: '' }
}
await updateSharedView()
} finally {
isUpdating.value.password = false
}
}
const withRTL = computed({ const withRTL = computed({
get: () => { get: () => {
if (!activeView.value?.meta) return false if (!activeView.value?.meta) return false
@ -282,6 +317,7 @@ async function updateSharedView() {
await $api.dbViewShare.update(activeView.value.id!, { await $api.dbViewShare.update(activeView.value.id!, {
meta, meta,
password: activeView.value.password, password: activeView.value.password,
...(activeView.value?.custom_path ? { custom_path: activeView.value?.custom_path } : {}),
}) })
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -290,6 +326,14 @@ async function updateSharedView() {
return true return true
} }
const updateSharedViewWithDebounce = useDebounceFn(
async () => {
await updateSharedView()
},
250,
{ maxWait: 2000 },
)
async function savePreFilledMode() { async function savePreFilledMode() {
await updateSharedView() await updateSharedView()
} }
@ -316,6 +360,32 @@ watchEffect(() => {})
<div class="mt-0.5 border-t-1 border-gray-100 pt-3"> <div class="mt-0.5 border-t-1 border-gray-100 pt-3">
<GeneralCopyUrl v-model:url="url" /> <GeneralCopyUrl v-model:url="url" />
</div> </div>
<div class="flex flex-col justify-between mt-1 py-2 px-3 bg-gray-50 rounded-md">
<div class="flex flex-row items-center justify-between">
<div class="flex text-black">Custom url</div>
<a-switch
v-e="['c:share:view:custom-url:toggle']"
:checked="isOpenCustomUrl"
:loading="isUpdating.customUrl"
class="share-custom-url-toggle !mt-0.25"
data-testid="share-custom-url-toggle"
size="small"
@click="toggleCustomUrl"
/>
</div>
<Transition mode="out-in" name="layout">
<div v-if="isOpenCustomUrl" class="flex gap-2 mt-2 w-2/3">
<a-input
v-model:value="customUrl"
placeholder="Enter custom url"
class="!rounded-lg !py-1 !bg-white"
data-testid="nc-modal-share-view__custom-url"
size="small"
@update:value="updateSharedViewWithDebounce"
/>
</div>
</Transition>
</div>
<div class="flex flex-col justify-between mt-1 py-2 px-3 bg-gray-50 rounded-md"> <div class="flex flex-col justify-between mt-1 py-2 px-3 bg-gray-50 rounded-md">
<div class="flex flex-row items-center justify-between"> <div class="flex flex-row items-center justify-between">
<div class="flex text-black">{{ $t('activity.restrictAccessWithPassword') }}</div> <div class="flex text-black">{{ $t('activity.restrictAccessWithPassword') }}</div>

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

@ -38,6 +38,7 @@ export default class Base implements BaseType {
uuid?: string; uuid?: string;
password?: string; password?: string;
roles?: string; roles?: string;
fk_custom_url_id?: string;
constructor(base: Partial<Base>) { constructor(base: Partial<Base>) {
Object.assign(this, base); Object.assign(this, base);

45
packages/nocodb/src/models/CustomUrl.ts

@ -7,6 +7,7 @@ import {
RootScopes, RootScopes,
} from '~/utils/globals'; } from '~/utils/globals';
import NocoCache from '~/cache/NocoCache'; import NocoCache from '~/cache/NocoCache';
import { NcContext } from 'src/interface/config';
export default class CustomUrl { export default class CustomUrl {
id?: string; id?: string;
@ -22,24 +23,24 @@ export default class CustomUrl {
} }
public static async get( public static async get(
params: { _context: NcContext,
id?: string; params: Pick<CustomUrl, 'id' | 'view_id' | 'custom_path'>,
view_id?: string;
custom_path?: string;
},
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
) { ) {
const condition = extractProps(params, ['id', 'view_id', 'custom_path']); const condition = extractProps(params, ['id', 'view_id', 'custom_path']);
return await ncMeta.metaGet2( const customUrl = await ncMeta.metaGet2(
RootScopes.ROOT, RootScopes.ROOT,
RootScopes.ROOT, RootScopes.ROOT,
MetaTable.CUSTOM_URLS, MetaTable.CUSTOM_URLS,
condition, condition,
); );
return customUrl && new CustomUrl(customUrl);
} }
public static async getByCustomPath( public static async getByCustomPath(
_context: NcContext,
customPath: string, customPath: string,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
) { ) {
@ -67,6 +68,7 @@ export default class CustomUrl {
} }
public static async insert( public static async insert(
_context: NcContext,
customUrl: Partial<CustomUrl>, customUrl: Partial<CustomUrl>,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
) { ) {
@ -79,20 +81,19 @@ export default class CustomUrl {
'custom_path', 'custom_path',
]); ]);
return await ncMeta.metaInsert2( const insertedCustomUrl = await ncMeta.metaInsert2(
RootScopes.ROOT, RootScopes.ROOT,
RootScopes.ROOT, RootScopes.ROOT,
MetaTable.CUSTOM_URLS, MetaTable.CUSTOM_URLS,
insertData, insertData,
); );
return insertedCustomUrl && new CustomUrl(insertedCustomUrl);
} }
public static async list( public static async list(
params: { _context: NcContext,
fk_workspace_id?: string; params: Pick<CustomUrl, 'fk_workspace_id' | 'base_id' | 'fk_model_id'>,
base_id?: string;
fk_model_id?: string;
},
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
) { ) {
const condition = extractProps(params, [ const condition = extractProps(params, [
@ -110,10 +111,11 @@ export default class CustomUrl {
}, },
); );
return customUrlList; return customUrlList.map((customUrl) => new CustomUrl(customUrl));
} }
public static async update( public static async update(
_context: NcContext,
id: string, id: string,
customUrl: Partial<CustomUrl>, customUrl: Partial<CustomUrl>,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
@ -131,4 +133,21 @@ export default class CustomUrl {
id, id,
); );
} }
static async delete(
_context: NcContext,
customUrl: Pick<CustomUrl, 'id' | 'view_id'>,
ncMeta = Noco.ncMeta,
) {
const condition = extractProps(customUrl, ['id', 'view_id']);
const res = await ncMeta.metaDelete(
RootScopes.ROOT,
RootScopes.ROOT,
MetaTable.CUSTOM_URLS,
condition,
);
return res;
}
} }

11
packages/nocodb/src/models/View.ts

@ -41,6 +41,7 @@ import {
} from '~/utils/modelUtils'; } from '~/utils/modelUtils';
import { LinkToAnotherRecordColumn } from '~/models'; import { LinkToAnotherRecordColumn } from '~/models';
import { cleanCommandPaletteCache } from '~/helpers/commandPaletteHelpers'; import { cleanCommandPaletteCache } from '~/helpers/commandPaletteHelpers';
import CustomUrl from './CustomUrl';
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
@ -101,6 +102,7 @@ export default class View implements ViewType {
source_id?: string; source_id?: string;
show_system_fields?: boolean; show_system_fields?: boolean;
meta?: any; meta?: any;
fk_custom_url_id?: string;
constructor(data: View) { constructor(data: View) {
Object.assign(this, data); Object.assign(this, data);
@ -1247,6 +1249,8 @@ export default class View implements ViewType {
viewId, viewId,
); );
await CustomUrl.delete(context, { view_id: viewId });
await NocoCache.update(`${CacheScope.VIEW}:${viewId}`, { await NocoCache.update(`${CacheScope.VIEW}:${viewId}`, {
uuid: null, uuid: null,
}); });
@ -1265,6 +1269,7 @@ export default class View implements ViewType {
meta?: any; meta?: any;
owned_by?: string; owned_by?: string;
created_by?: string; created_by?: string;
fk_custom_url_id?: string;
}, },
includeCreatedByAndUpdateBy = false, includeCreatedByAndUpdateBy = false,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
@ -1278,6 +1283,7 @@ export default class View implements ViewType {
'password', 'password',
'meta', 'meta',
'uuid', 'uuid',
'fk_custom_url_id',
...(includeCreatedByAndUpdateBy ? ['owned_by', 'created_by'] : []), ...(includeCreatedByAndUpdateBy ? ['owned_by', 'created_by'] : []),
]); ]);
@ -1406,6 +1412,11 @@ export default class View implements ViewType {
}); });
} }
} }
if (view.fk_custom_url_id) {
await CustomUrl.delete(context, { id: view.fk_custom_url_id });
}
// on update, delete any optimised single query cache // on update, delete any optimised single query cache
await View.clearSingleQueryCache(context, view.fk_model_id, [view], ncMeta); await View.clearSingleQueryCache(context, view.fk_model_id, [view], ncMeta);

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

@ -24957,7 +24957,8 @@
"examples": [ "examples": [
{ {
"meta": {}, "meta": {},
"password": "123456789" "password": "123456789",
"custom_path": "feedback-form"
} }
], ],
"title": "Shared View Request Model", "title": "Shared View Request Model",
@ -24970,6 +24971,10 @@
"password": { "password": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/StringOrNull",
"description": "Password to restrict access" "description": "Password to restrict access"
},
"custom_path": {
"$ref": "#/components/schemas/StringOrNull",
"description": "Custom url"
} }
}, },
"x-stoplight": { "x-stoplight": {

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

@ -10,6 +10,7 @@ import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { validatePayload } from '~/helpers'; import { validatePayload } from '~/helpers';
import { NcError } from '~/helpers/catchError'; import { NcError } from '~/helpers/catchError';
import { BaseUser, Model, ModelRoleVisibility, View } from '~/models'; import { BaseUser, Model, ModelRoleVisibility, View } from '~/models';
import CustomUrl from 'src/models/CustomUrl';
// todo: move // todo: move
async function xcVisibilityMetaGet( async function xcVisibilityMetaGet(
@ -272,7 +273,36 @@ export class ViewsService {
NcError.viewNotFound(param.viewId); NcError.viewNotFound(param.viewId);
} }
const result = await View.update(context, param.viewId, param.sharedView); let customUrl: CustomUrl | undefined = await CustomUrl.get(context, {
view_id: view.id,
id: view.fk_custom_url_id,
});
if (customUrl?.id) {
if (param.sharedView.custom_path) {
await CustomUrl.update(context, view.fk_custom_url_id, {
custom_path: param.sharedView.custom_path,
});
} else {
await CustomUrl.delete(context, { id: view.fk_custom_url_id });
customUrl = undefined;
}
} else {
customUrl = await CustomUrl.insert(context, {
fk_workspace_id: view.fk_workspace_id,
base_id: view.base_id,
fk_model_id: view.fk_model_id,
view_id: view.id,
// Todo: add original path
original_path: '',
custom_path: param.sharedView.custom_path,
});
}
const result = await View.update(context, param.viewId, {
...param.sharedView,
fk_custom_url_id: customUrl?.id ?? null,
});
this.appHooksService.emit(AppEvents.SHARED_VIEW_UPDATE, { this.appHooksService.emit(AppEvents.SHARED_VIEW_UPDATE, {
user: param.user, user: param.user,
@ -296,6 +326,7 @@ export class ViewsService {
if (!view) { if (!view) {
NcError.viewNotFound(param.viewId); NcError.viewNotFound(param.viewId);
} }
await View.sharedViewDelete(context, param.viewId); await View.sharedViewDelete(context, param.viewId);
this.appHooksService.emit(AppEvents.SHARED_VIEW_DELETE, { this.appHooksService.emit(AppEvents.SHARED_VIEW_DELETE, {

Loading…
Cancel
Save