Browse Source

Merge commit '78efdfa0286f390ca276c7fe1713d3109f19b822' into NCDBOSS-39

pull/5419/head
gitstart 1 year ago
parent
commit
0636ea46f3
  1. 16
      packages/nc-gui/components/template/Editor.vue
  2. 3
      packages/nc-gui/components/template/utils.ts
  3. 10
      packages/nc-gui/composables/useGlobal/actions.ts
  4. 18
      packages/nc-gui/lang/zh-Hans.json
  5. 12
      packages/nocodb/package-lock.json
  6. 10
      packages/nocodb/src/lib/controllers/user/user.ctl.ts
  7. 13
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  8. 40
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts
  9. 6
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts
  10. 23
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/convertDateFormat.ts
  11. 45
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts
  12. 4
      packages/nocodb/src/lib/plugins/vultr/Vultr.ts
  13. 16
      packages/nocodb/src/lib/plugins/vultr/index.ts
  14. 15
      packages/nocodb/src/lib/services/user/index.ts

16
packages/nc-gui/components/template/Editor.vue

@ -3,6 +3,7 @@ import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface'
import { srcDestMappingColumns, tableColumns } from './utils'
import {
Empty,
@ -87,6 +88,8 @@ const isImporting = ref(false)
const importingTips = ref<Record<string, string>>({})
const checkAllRecord = ref<boolean[]>([])
const uiTypeOptions = ref<Option[]>(
(Object.keys(UITypes) as (keyof typeof UITypes)[])
.filter(
@ -615,6 +618,13 @@ function handleEditableTnChange(idx: number) {
function isSelectDisabled(uidt: string, disableSelect = false) {
return (uidt === UITypes.SingleSelect || uidt === UITypes.MultiSelect) && disableSelect
}
function handleCheckAllRecord(event: CheckboxChangeEvent, tableName: string) {
const isChecked = event.target.checked
for (const record of srcDestMapping.value[tableName]) {
record.enabled = isChecked
}
}
</script>
<template>
@ -671,6 +681,12 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<span v-if="column.key === 'source_column' || column.key === 'destination_column'">
{{ column.name }}
</span>
<span v-if="column.key === 'action'">
<a-checkbox
v-model:checked="checkAllRecord[table.table_name]"
@change="handleCheckAllRecord($event, table.table_name)"
/>
</span>
</template>
<template #bodyCell="{ column, record }">

3
packages/nc-gui/components/template/utils.ts

@ -40,6 +40,7 @@ export const srcDestMappingColumns: (Omit<ColumnGroupType<any>, 'children'> & {
{
name: 'Action',
key: 'action',
align: 'right',
align: 'center',
width: 50,
},
]

10
packages/nc-gui/composables/useGlobal/actions.ts

@ -46,11 +46,13 @@ export function useGlobalActions(state: State): Actions {
signIn(response.data.token)
}
})
.catch(async (err) => {
message.error(err.message || t('msg.error.youHaveBeenSignedOut'))
await signOut()
.catch(async () => {
if (state.token.value && state.user.value) {
await signOut()
message.error(t('msg.error.youHaveBeenSignedOut'))
}
})
.finally(() => resolve())
.finally(() => resolve(true))
})
}

18
packages/nc-gui/lang/zh-Hans.json

@ -221,7 +221,7 @@
"viewName": "查看名称",
"viewLink": "查看链接",
"columnName": "列名",
"columnToScanFor": "Column to scan",
"columnToScanFor": "要扫描的列",
"columnType": "列类型",
"roleName": "权限组",
"roleDescription": "权限描述",
@ -412,8 +412,8 @@
"changePwd": "更改密码",
"createView": "创建视图",
"shareView": "分享视图",
"findRowByCodeScan": "Find row by scan",
"fillByCodeScan": "Fill by scan",
"findRowByCodeScan": "通过扫描查找行",
"fillByCodeScan": "通过扫描填充",
"listSharedView": "共享视图列表",
"ListView": "视图列表",
"copyView": "复制视图",
@ -430,7 +430,7 @@
"iFrame": "复制可嵌入的 HTML 代码",
"addWebhook": "添加新的 Webhook",
"enableWebhook": "启用 Webhook",
"testWebhook": "Test Webhook",
"testWebhook": "测试Webhook",
"copyWebhook": "复制 Webhook",
"deleteWebhook": "删除 Webhook",
"newToken": "添加新 Token",
@ -470,7 +470,7 @@
"addOrEditStack": "添加/编辑分类标签"
},
"map": {
"mappedBy": "Mapped By",
"mappedBy": "映射字段",
"chooseMappingField": "选择映射字段",
"openInGoogleMaps": "谷歌地图",
"openInOpenStreetMap": "OSM"
@ -542,15 +542,15 @@
"orgViewer": "游客不能创建新项目,仅允许访问受邀项目。"
},
"codeScanner": {
"loadingScanner": "Loading the scanner...",
"selectColumn": "Select a column (QR code or Barcode) that you want to use for finding a row by scanning.",
"moreThanOneRowFoundForCode": "More than one row found for this code. Currently only unique codes are supported.",
"loadingScanner": "正在加载扫描仪...",
"selectColumn": "选择要用于扫描查找行的列(二维码或条形码)。",
"moreThanOneRowFoundForCode": "找到了多行此代码。目前只支持唯一的代码。",
"noRowFoundForCode": "所选列没有找到此代码行"
},
"map": {
"overLimit": "你已经超出了限制。",
"closeLimit": "您已接近上限。",
"limitNumber": "The limit of markers shown in a Map View is 1000 records."
"limitNumber": "在地图视图中显示的标记的限制是1000条记录。"
},
"footerInfo": "每页行驶",
"upload": "选择文件以上传",

12
packages/nocodb/package-lock.json generated

@ -17364,9 +17364,9 @@
"dev": true
},
"node_modules/vm2": {
"version": "3.9.15",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.15.tgz",
"integrity": "sha512-XqNqknHGw2avJo13gbIwLNZUumvrSHc9mLqoadFZTpo3KaNEJoe1I0lqTFhRXmXD7WkLyG01aaraXdXT0pa4ag==",
"version": "3.9.16",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.16.tgz",
"integrity": "sha512-3T9LscojNTxdOyG+e8gFeyBXkMlOBYDoF6dqZbj+MPVHi9x10UfiTAJIobuchRCp3QvC+inybTbMJIUrLsig0w==",
"dependencies": {
"acorn": "^8.7.0",
"acorn-walk": "^8.2.0"
@ -32786,9 +32786,9 @@
"dev": true
},
"vm2": {
"version": "3.9.15",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.15.tgz",
"integrity": "sha512-XqNqknHGw2avJo13gbIwLNZUumvrSHc9mLqoadFZTpo3KaNEJoe1I0lqTFhRXmXD7WkLyG01aaraXdXT0pa4ag==",
"version": "3.9.16",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.16.tgz",
"integrity": "sha512-3T9LscojNTxdOyG+e8gFeyBXkMlOBYDoF6dqZbj+MPVHi9x10UfiTAJIobuchRCp3QvC+inybTbMJIUrLsig0w==",
"requires": {
"acorn": "^8.7.0",
"acorn-walk": "^8.2.0"

10
packages/nocodb/src/lib/controllers/user/user.ctl.ts

@ -98,6 +98,15 @@ async function signin(req, res, next) {
)(req, res, next);
}
async function signout(req: Request<any, any>, res): Promise<any> {
res.json(
await userService.signout({
req,
res,
})
);
}
async function googleSignin(req, res, next) {
passport.authenticate(
'google',
@ -246,6 +255,7 @@ const mapRoutes = (router) => {
// new API
router.post('/api/v1/auth/user/signup', catchError(signup));
router.post('/api/v1/auth/user/signin', catchError(signin));
router.post('/api/v1/auth/user/signout', catchError(signout));
router.get(
'/api/v1/auth/user/me',
extractProjectIdAndAuthenticate,

13
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -3242,17 +3242,24 @@ function extractCondition(nestedArrayConditions, aliasColObjMap) {
// eslint-disable-next-line prefer-const
let [logicOp, alias, op, value] =
str.match(/(?:~(and|or|not))?\((.*?),(\w+),(.*)\)/)?.slice(1) || [];
if (!alias && !op && !value) {
// try match with blank filter format
[logicOp, alias, op, value] =
str.match(/(?:~(and|or|not))?\((.*?),(\w+)\)/)?.slice(1) || [];
}
let sub_op = null;
if (aliasColObjMap[alias]) {
if (
[UITypes.Date, UITypes.DateTime].includes(aliasColObjMap[alias].uidt)
) {
value = value.split(',');
value = value?.split(',');
// the first element would be sub_op
sub_op = value[0];
sub_op = value?.[0];
// remove the first element which is sub_op
value.shift();
value?.shift();
value = value?.[0];
} else if (op === 'in') {
value = value.split(',');
}

40
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts

@ -3,11 +3,14 @@ import { jsepCurlyHook, UITypes } from 'nocodb-sdk';
import mapFunctionName from '../mapFunctionName';
import genRollupSelectv2 from '../genRollupSelectv2';
import FormulaColumn from '../../../../../models/FormulaColumn';
import { validateDateWithUnknownFormat } from '../helpers/formulaFnHelper';
import {
convertDateFormatForConcat,
validateDateWithUnknownFormat,
} from '../helpers/formulaFnHelper';
import { CacheGetType, CacheScope } from '../../../../../utils/globals';
import NocoCache from '../../../../../cache/NocoCache';
import type Model from '../../../../../models/Model';
import type Column from '../../../../../models/Column';
import type Model from '../../../../../models/Model';
import type RollupColumn from '../../../../../models/RollupColumn';
import type { XKnex } from '../../../index';
import type LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecordColumn';
@ -633,8 +636,19 @@ async function _formulaQueryBuilder(
`${pt.callee.name}(${(
await Promise.all(
pt.arguments.map(async (arg) => {
const query = (await fn(arg)).builder.toQuery();
let query = (await fn(arg)).builder.toQuery();
if (pt.callee.name === 'CONCAT') {
if (knex.clientType() !== 'sqlite3') {
query = await convertDateFormatForConcat(
arg,
columnIdToUidt,
query,
knex.clientType()
);
} else {
// sqlite3: special handling - See BinaryExpression
}
if (knex.clientType() === 'mysql2') {
// mysql2: CONCAT() returns NULL if any argument is NULL.
// adding IFNULL to convert NULL values to empty strings
@ -679,8 +693,8 @@ async function _formulaQueryBuilder(
pt.left.fnName = pt.left.fnName || 'ARITH';
pt.right.fnName = pt.right.fnName || 'ARITH';
const left = (await fn(pt.left, null, pt.operator)).builder.toQuery();
const right = (await fn(pt.right, null, pt.operator)).builder.toQuery();
let left = (await fn(pt.left, null, pt.operator)).builder.toQuery();
let right = (await fn(pt.right, null, pt.operator)).builder.toQuery();
let sql = `${left} ${pt.operator} ${right}${colAlias}`;
// comparing a date with empty string would throw
@ -724,8 +738,22 @@ async function _formulaQueryBuilder(
}
}
// handle NULL values when calling CONCAT for sqlite3
if (pt.left.fnName === 'CONCAT' && knex.clientType() === 'sqlite3') {
// handle date format
left = await convertDateFormatForConcat(
pt.left?.arguments?.[0],
columnIdToUidt,
left,
knex.clientType()
);
right = await convertDateFormatForConcat(
pt.right?.arguments?.[0],
columnIdToUidt,
right,
knex.clientType()
);
// handle NULL values when calling CONCAT for sqlite3
sql = `COALESCE(${left}, '') ${pt.operator} COALESCE(${right},'')${colAlias}`;
}

6
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts

@ -17,9 +17,9 @@ const pg = {
builder: args.knex.raw(
`POSITION(${args.knex.raw(
(await args.fn(args.pt.arguments[1])).builder.toQuery()
)} in ${args.knex
.raw((await args.fn(args.pt.arguments[0])).builder)
.toQuery()})${args.colAlias}`
)} in ${args.knex.raw(
(await args.fn(args.pt.arguments[0])).builder.toQuery()
)})${args.colAlias}`
),
};
},

23
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/convertDateFormat.ts

@ -0,0 +1,23 @@
export function convertDateFormat(date_format: string, type: string) {
if (date_format === 'YYYY-MM-DD') {
if (type === 'mysql2' || type === 'sqlite3') return '%Y-%m-%d';
} else if (date_format === 'YYYY/MM/DD') {
if (type === 'mysql2' || type === 'sqlite3') return '%Y/%m/%d';
} else if (date_format === 'DD-MM-YYYY') {
if (type === 'mysql2' || type === 'sqlite3') return '%d/%m/%Y';
} else if (date_format === 'MM-DD-YYYY') {
if (type === 'mysql2' || type === 'sqlite3') return '%d-%m-%Y';
} else if (date_format === 'DD/MM/YYYY') {
if (type === 'mysql2' || type === 'sqlite3') return '%d/%m/%Y';
} else if (date_format === 'MM/DD/YYYY') {
if (type === 'mysql2' || type === 'sqlite3') return '%m-%d-%Y';
} else if (date_format === 'DD MM YYYY') {
if (type === 'mysql2' || type === 'sqlite3') return '%d %m %Y';
} else if (date_format === 'MM DD YYYY') {
if (type === 'mysql2' || type === 'sqlite3') return '%m %d %Y';
} else if (date_format === 'YYYY MM DD') {
if (type === 'mysql2' || type === 'sqlite3') return '%Y %m %d';
}
// pg / mssql
return date_format;
}

45
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts

@ -1,5 +1,8 @@
import dayjs, { extend } from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
import { UITypes } from 'nocodb-sdk';
import Column from '../../../../../models/Column';
import { convertDateFormat } from './convertDateFormat';
extend(customParseFormat);
export function getWeekdayByText(v: string) {
@ -50,3 +53,45 @@ export function validateDateWithUnknownFormat(v: string) {
}
return false;
}
export async function convertDateFormatForConcat(
o,
columnIdToUidt,
query,
clientType
) {
if (
o?.type === 'Identifier' &&
o?.name in columnIdToUidt &&
columnIdToUidt[o.name] === UITypes.Date
) {
const meta = (
await Column.get({
colId: o.name,
})
).meta;
if (clientType === 'mysql2') {
query = `DATE_FORMAT(${query}, '${convertDateFormat(
meta.date_format,
clientType
)}')`;
} else if (clientType === 'pg') {
query = `TO_CHAR(${query}, '${convertDateFormat(
meta.date_format,
clientType
)}')`;
} else if (clientType === 'sqlite3') {
query = `strftime('${convertDateFormat(
meta.date_format,
clientType
)}', ${query})`;
} else if (clientType === 'mssql') {
query = `FORMAT(${query}, '${convertDateFormat(
meta.date_format,
clientType
)}')`;
}
}
return query;
}

4
packages/nocodb/src/lib/plugins/vultr/Vultr.ts

@ -105,9 +105,7 @@ export default class Vultr implements IStorageAdapterV2 {
s3Options.accessKeyId = this.input.access_key;
s3Options.secretAccessKey = this.input.access_secret;
s3Options.endpoint = new AWS.Endpoint(
`s3.${this.input.region}.cloud.ovh.net`
);
s3Options.endpoint = new AWS.Endpoint(this.input.hostname);
this.s3Client = new AWS.S3(s3Options);
}

16
packages/nocodb/src/lib/plugins/vultr/index.ts

@ -5,7 +5,7 @@ import type { XcPluginConfig } from 'nc-plugin';
const config: XcPluginConfig = {
builder: VultrPlugin,
title: 'Vultr Object Storage',
version: '0.0.1',
version: '0.0.2',
logo: 'plugins/vultr.png',
description:
'Using Vultr Object Storage can give flexibility and cloud storage that allows applications greater flexibility and access worldwide.',
@ -20,13 +20,13 @@ const config: XcPluginConfig = {
type: XcType.SingleLineText,
required: true,
},
// {
// key: 'region',
// label: 'Region',
// placeholder: 'Region',
// type: XcType.SingleLineText,
// required: true
// },
{
key: 'hostname',
label: 'Host Name',
placeholder: 'e.g.: ewr1.vultrobjects.com',
type: XcType.SingleLineText,
required: true
},
{
key: 'access_key',
label: 'Access Key',

15
packages/nocodb/src/lib/services/user/index.ts

@ -458,5 +458,20 @@ export async function signup(param: {
} as any;
}
export async function signout(param: { req: any; res: any }): Promise<any> {
try {
param.res.clearCookie('refresh_token');
const user = (param.req as any).user;
if (user) {
await User.update(user.id, {
refresh_token: null,
});
}
return { msg: 'Signed out successfully' };
} catch (e) {
NcError.badRequest(e.message);
}
}
export * from './helpers';
export { default as initAdminFromEnv } from './initAdminFromEnv';

Loading…
Cancel
Save