diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index 4232733013..f91de6e9b7 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -8,17 +8,45 @@ x-uffizzi: services: postgres: image: postgres - restart: always environment: POSTGRES_PASSWORD: password POSTGRES_USER: postgres POSTGRES_DB: root_db + deploy: + resources: + limits: + memory: 500M + mssql: + image: "mcr.microsoft.com/mssql/server:2017-latest" + environment: + ACCEPT_EULA: "Y" + SA_PASSWORD: Password123. + deploy: + resources: + limits: + memory: 1000M + mysql: + environment: + MYSQL_DATABASE: root_db + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: password + MYSQL_USER: noco + image: "mysql:5.7" + deploy: + resources: + limits: + memory: 500M nocodb: image: "${NOCODB_IMAGE}" ports: - "8080:8080" - restart: always + entrypoint: /bin/sh + command: ["-c", "apk add wait4ports && wait4ports tcp://localhost:5432 && /usr/src/appEntry/start.sh"] environment: NC_DB: "pg://localhost:5432?u=postgres&p=password&d=root_db" NC_ADMIN_EMAIL: admin@nocodb.com NC_ADMIN_PASSWORD: password + deploy: + resources: + limits: + memory: 500M \ No newline at end of file diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index 2f19829129..adc17aacee 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -50,6 +50,10 @@ jobs: run: | DOCKER_REPOSITORY=nocodb DOCKER_BUILD_TAG=${{ github.event.inputs.tag || inputs.tag }} + DOCKER_BUILD_LATEST_TAG=latest + if [[ "$DOCKER_BUILD_TAG" =~ "-beta." ]]; then + DOCKER_BUILD_LATEST_TAG=$(echo $DOCKER_BUILD_TAG | awk -F '-beta.' '{print $1}')-beta.latest + fi if [[ ${{ github.event.inputs.targetEnv || inputs.targetEnv }} == 'DEV' ]]; then if [[ ${{ github.event.inputs.currentVersion || inputs.currentVersion || 'N/A' }} != 'N/A' ]]; then DOCKER_BUILD_TAG=${{ github.event.inputs.currentVersion || inputs.currentVersion }}-${{ github.event.inputs.tag || inputs.tag }} @@ -62,8 +66,10 @@ jobs: fi echo "DOCKER_REPOSITORY=${DOCKER_REPOSITORY}" >> $GITHUB_OUTPUT echo "DOCKER_BUILD_TAG=${DOCKER_BUILD_TAG}" >> $GITHUB_OUTPUT + echo "DOCKER_BUILD_LATEST_TAG=${DOCKER_BUILD_LATEST_TAG}" >> $GITHUB_OUTPUT echo DOCKER_REPOSITORY: ${DOCKER_REPOSITORY} echo DOCKER_BUILD_TAG: ${DOCKER_BUILD_TAG} + echo DOCKER_BUILD_LATEST_TAG: ${DOCKER_BUILD_LATEST_TAG} - name: Checkout uses: actions/checkout@v3 @@ -134,7 +140,7 @@ jobs: push: true tags: | nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:${{ steps.get-docker-repository.outputs.DOCKER_BUILD_TAG }} - nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:latest + nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:${{ steps.get-docker-repository.outputs.DOCKER_BUILD_LATEST_TAG }} # Temp fix # https://github.com/docker/build-push-action/issues/252 diff --git a/packages/nc-gui/components/general/ReleaseInfo.vue b/packages/nc-gui/components/general/ReleaseInfo.vue index 14d9a3a595..058262417f 100644 --- a/packages/nc-gui/components/general/ReleaseInfo.vue +++ b/packages/nc-gui/components/general/ReleaseInfo.vue @@ -7,6 +7,9 @@ const { currentVersion, latestRelease, hiddenRelease } = useGlobal() const releaseAlert = computed({ get() { + if (currentVersion.value?.includes('-beta.') || latestRelease.value?.includes('-beta.')) { + return false + } return ( currentVersion.value && latestRelease.value && @@ -22,7 +25,7 @@ const releaseAlert = computed({ async function fetchReleaseInfo() { try { const versionInfo = await $api.utils.appVersion() - if (versionInfo && versionInfo.releaseVersion && versionInfo.currentVersion && !/[^0-9.]/.test(versionInfo.currentVersion)) { + if (versionInfo && versionInfo.releaseVersion && versionInfo.currentVersion) { currentVersion.value = versionInfo.currentVersion latestRelease.value = versionInfo.releaseVersion } else { diff --git a/packages/nc-gui/lang/ru.json b/packages/nc-gui/lang/ru.json index 8449d73f9a..584b04c0ec 100644 --- a/packages/nc-gui/lang/ru.json +++ b/packages/nc-gui/lang/ru.json @@ -284,17 +284,17 @@ "requestDataSource": "Request a data source you need?", "apiKey": "API Key", "sharedBase": "Shared Base", - "importData": "Import Data", + "importData": "Импорт данных", "importSecondaryViews": "Import Secondary Views", "importRollupColumns": "Import Rollup Columns", "importLookupColumns": "Import Lookup Columns", "importAttachmentColumns": "Import Attachment Columns", "importFormulaColumns": "Import Formula Columns", - "noData": "No Data", - "goToDashboard": "Go to Dashboard", - "importing": "Importing", + "noData": "Нет данных", + "goToDashboard": "Перейти к панели управления", + "importing": "Импорт", "flattenNested": "Flatten Nested", - "downloadAllowed": "Download allowed", + "downloadAllowed": "Скачивание разрешено", "weAreHiring": "We are Hiring!", "primaryKey": "Primary key", "hasMany": "has many", @@ -302,13 +302,13 @@ "manyToMany": "have many to many relation", "extraConnectionParameters": "Extra connection parameters", "commentsOnly": "Comments only", - "documentation": "Documentation", - "subscribeNewsletter": "Subscribe to our weekly newsletter", - "signUpWithGoogle": "Sign up with Google", - "signInWithGoogle": "Sign in with Google", + "documentation": "Документация", + "subscribeNewsletter": "Подпишитесь на нашу еженедельную рассылку", + "signUpWithGoogle": "Зарегистрируйтесь с помощью Google", + "signInWithGoogle": "Войти при помощи Google", "agreeToTos": "By signing up, you agree to the Terms of Service", - "welcomeToNc": "Welcome to NocoDB!", - "inviteOnlySignup": "Allow signup only using invite url" + "welcomeToNc": "Добро пожаловать в NocoDB!", + "inviteOnlySignup": "Разрешить регистрацию только по ссылке" }, "activity": { "createProject": "Создать проект", @@ -353,14 +353,14 @@ "invite": "Пригласить", "inviteMore": "Пригласить еще", "inviteTeam": "Пригласить команду", - "inviteUser": "Invite User", + "inviteUser": "Пригласить пользователя", "inviteToken": "Токен приглашения", "newUser": "Новый пользователь", "editUser": "Редактировать пользователя", "deleteUser": "Удалить пользователя из проекта", "resendInvite": "Переотправить приглашение e-mail", "copyInviteURL": "Скопировать URL-адрес приглашения", - "copyPasswordResetURL": "Copy password reset URL", + "copyPasswordResetURL": "Скопировать URL для сброса пароля", "newRole": "Новая роль", "reloadRoles": "Перезагрузить роли", "nextPage": "Следущая страница", @@ -376,13 +376,13 @@ "setPrimary": "Установить в качестве основного значения", "addRow": "Добавить новую строку", "saveRow": "Сохранить строку", - "saveAndExit": "Save & Exit", - "saveAndStay": "Save & Stay", + "saveAndExit": "Сохранить и выйти", + "saveAndStay": "Сохранить и остаться", "insertRow": "Вставить новый строк", "deleteRow": "Удалить строку", "deleteSelectedRow": "Удалить выбранные строки", "importExcel": "Импорт из Excel", - "importCSV": "Import CSV", + "importCSV": "Импорт CSV", "downloadCSV": "Скачать как CSV.", "downloadExcel": "Скачать как XLSX", "uploadCSV": "Загрузить CSV.", @@ -421,12 +421,12 @@ "editConnJson": "Редактировать соединение JSON", "sponsorUs": "Спонсируйте нас", "sendEmail": "Отправить письмо", - "addUserToProject": "Add user to project", + "addUserToProject": "Добавить пользователя в проект", "getApiSnippet": "Get API Snippet", - "clearCell": "Clear cell", - "addFilterGroup": "Add Filter Group", + "clearCell": "Очистить ячейку", + "addFilterGroup": "Добавить группу фильтров", "linkRecord": "Link record", - "addNewRecord": "Add new record", + "addNewRecord": "Добавить новую запись", "useConnectionUrl": "Use Connection URL", "toggleCommentsDraw": "Toggle comments draw", "expandRecord": "Развернуть запись", @@ -647,44 +647,44 @@ }, "invalidURL": "Неверный URL", "internalError": "Some internal error occurred", - "templateGeneratorNotFound": "Template Generator cannot be found!", - "fileUploadFailed": "Failed to upload file", + "templateGeneratorNotFound": "Генератор шаблонов не найден!", + "fileUploadFailed": "Не удалось загрузить файл", "primaryColumnUpdateFailed": "Failed to update primary column", "formDescriptionTooLong": "Data too long for Form Description", "columnsRequired": "Following columns are required", - "selectAtleastOneColumn": "At least one column has to be selected", + "selectAtleastOneColumn": "Должен быть выбран как минимум один столбец", "columnDescriptionNotFound": "Cannot find the destination column for", "duplicateMappingFound": "Duplicate mapping found, please remove one of the mapping", "nullValueViolatesNotNull": "Null value violates not-null constraint", - "sourceHasInvalidNumbers": "Source data contains some invalid numbers", + "sourceHasInvalidNumbers": "Исходные данные содержат недопустимые числа", "sourceHasInvalidBoolean": "Source data contains some invalid boolean values", "invalidForm": "Invalid Form", - "formValidationFailed": "Form validation failed", - "youHaveBeenSignedOut": "You have been signed out", - "failedToLoadList": "Failed to load list", + "formValidationFailed": "Ошибка проверки формы", + "youHaveBeenSignedOut": "Вы вышли из системы", + "failedToLoadList": "Не удалось загрузить список", "failedToLoadChildrenList": "Failed to load children list", - "deleteFailed": "Delete failed", - "unlinkFailed": "Unlink failed", + "deleteFailed": "Не удалось удалить", + "unlinkFailed": "Не удалось отменить связь", "rowUpdateFailed": "Row update failed", - "deleteRowFailed": "Failed to delete row", - "setFormDataFailed": "Failed to set form data", + "deleteRowFailed": "Не удалось удалить строку", + "setFormDataFailed": "Не удалось задать данные формы", "formViewUpdateFailed": "Failed to update form view", - "tableNameRequired": "Table name is required", + "tableNameRequired": "Требуется имя таблицы", "nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _", - "followingCharactersAreNotAllowed": "Following characters are not allowed", - "columnNameRequired": "Column name is required", - "projectNameExceeds50Characters": "Project name exceeds 50 characters", - "projectNameCannotStartWithSpace": "Project name cannot start with space", - "requiredField": "Required field", + "followingCharactersAreNotAllowed": "Нельзя использовать следующие символы", + "columnNameRequired": "Требуется название столбца", + "projectNameExceeds50Characters": "Название проекта превышает 50 символов", + "projectNameCannotStartWithSpace": "Название проекта не может начинаться с пробела", + "requiredField": "Обязательное поле", "ipNotAllowed": "IP not allowed", "targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type", - "theAcceptedFileTypeIsCsv": "The accepted file type is .csv", - "theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots", + "theAcceptedFileTypeIsCsv": "Допустимый тип файла: .csv", + "theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "Допустимые типы файлов: .xls, .xlsx, .xlsm, .ods, .ots", "parameterKeyCannotBeEmpty": "Parameter key cannot be empty", "duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed", - "fieldRequired": "{value} cannot be empty.", - "projectNotAccessible": "Project not accessible", - "copyToClipboardError": "Failed to copy to clipboard" + "fieldRequired": "{value} не может быть пустым.", + "projectNotAccessible": "Проект недоступен", + "copyToClipboardError": "Не удалось скопировать в буфер обмена" }, "toast": { "exportMetadata": "Метаданные проекта успешно экспортированы", @@ -704,18 +704,18 @@ "futureRelease": "Скоро!" }, "success": { - "columnDuplicated": "Column duplicated successfully", + "columnDuplicated": "Столбец успешно скопирован", "updatedUIACL": "Updated UI ACL for tables successfully", - "pluginUninstalled": "Plugin uninstalled successfully", - "pluginSettingsSaved": "Plugin settings saved successfully", + "pluginUninstalled": "Плагин успешно удален", + "pluginSettingsSaved": "Настройки плагина сохранены", "pluginTested": "Successfully tested plugin settings", - "tableRenamed": "Table renamed successfully", - "viewDeleted": "View deleted successfully", + "tableRenamed": "Таблица успешно переименована", + "viewDeleted": "Представление успешно удалено", "primaryColumnUpdated": "Successfully updated as primary column", - "tableDataExported": "Successfully exported all table data", - "updated": "Successfully updated", + "tableDataExported": "Все данные таблицы успешно экспортированы", + "updated": "Успешно обновлено", "sharedViewDeleted": "Deleted shared view successfully", - "userDeleted": "User deleted successfully", + "userDeleted": "Пользователь успешно удален", "viewRenamed": "View renamed successfully", "tokenGenerated": "Token generated successfully", "tokenDeleted": "Token deleted successfully", diff --git a/packages/nc-gui/pages/[projectType]/[projectId]/index/index/index.vue b/packages/nc-gui/pages/[projectType]/[projectId]/index/index/index.vue index 3c793a71ba..aa95232f38 100644 --- a/packages/nc-gui/pages/[projectType]/[projectId]/index/index/index.vue +++ b/packages/nc-gui/pages/[projectType]/[projectId]/index/index/index.vue @@ -19,7 +19,7 @@ const { isOverDropZone } = useDropZone(dropZone, onDrop) const { files, open, reset } = useFileDialog() -const { isSharedBase } = useProject() +const { bases, isSharedBase } = useProject() const { isUIAllowed } = useUIPermission() @@ -128,6 +128,7 @@ function openCreateTable() { const { close } = useDialog(resolveComponent('DlgTableCreate'), { 'modelValue': isOpen, 'onUpdate:modelValue': closeDialog, + 'baseId': bases.value[0].id, }) function closeDialog() { diff --git a/packages/noco-docs/content/en/engineering/builds-and-releases.md b/packages/noco-docs/content/en/engineering/builds-and-releases.md index 93273cd0e8..c5d362afe6 100644 --- a/packages/noco-docs/content/en/engineering/builds-and-releases.md +++ b/packages/noco-docs/content/en/engineering/builds-and-releases.md @@ -6,7 +6,9 @@ category: "Engineering" menuTitle: "Releases & Builds" --- ## Builds of NocoDB + There are 3 kinds of docker builds in NocoDB + - Release builds [nocodb/nocodb](https://hub.docker.com/r/nocodb/nocodb) : built during NocoDB release. - Daily builds [nocodb/nocodb-daily](https://hub.docker.com/r/nocodb/nocodb-daily) : built every 6 hours from Develop branch. - Daily builds [nocodb/nocodb-timely](https://hub.docker.com/r/nocodb/nocodb-timely): built for every PR. @@ -26,12 +28,24 @@ Below is an overview of how to make these builds and what happens behind the sce ![image](https://user-images.githubusercontent.com/35857179/167240383-dda05f76-8323-4f4a-b3e7-9db886dbd68d.png) - Then there would be two cases - you can either leave target tag and pervious tag blank or manually input some values -> Target Tag means the target deployment version, while Previous Tag means the latest version as of now. Previous Tag is used for Release Note only - showing the file / commit differences between two tags. +- Target Tag means the target deployment version, while Previous Tag means the latest version as of now. Previous Tag is used for Release Note only - showing the file / commit differences between two tags. + +### Tagging + +The naming convention would be following given the actual release tag is `0.100.0` + +- `0.100.0-beta.1` (first version of pre-release) +- `0.100.0-beta.2` (include bug fix changes on top of the previous version) +- `0.100.0-beta.3`(include bug fix changes on top of the previous version) +- and so on ... +- `0.100.0` (actual release) +- `0.100.1` (minor bug fix release) +- `0.100.2` (minor bug fix release) ### Case 1: Leaving inputs blank - If Previous Tag is blank, then the value will be fetched from [latest](https://github.com/nocodb/nocodb/releases/latest) -- If Target Tag is blank, then the value will be Previous Tag plus one. Example: 0.90.11 (Previous Tag) + 1 = 0.90.12 (Target Tag) +- If Target Tag is blank, then the value will be Previous Tag plus one. Example: 0.90.11 (Previous Tag) + 0.0.1 = 0.90.12 (Target Tag) ### Case 2: Manually Input diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json index 6efa3fe845..3b8d492b72 100644 --- a/packages/nocodb/package-lock.json +++ b/packages/nocodb/package-lock.json @@ -23,6 +23,7 @@ "bullmq": "^1.81.1", "clear": "^0.1.0", "colors": "1.4.0", + "compare-versions": "^5.0.1", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cron": "^1.8.2", @@ -3810,6 +3811,11 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/compare-versions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", + "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==" + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -20757,6 +20763,11 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "compare-versions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", + "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==" + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index a421d41fe6..e6883c00df 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -63,6 +63,7 @@ "bullmq": "^1.81.1", "clear": "^0.1.0", "colors": "1.4.0", + "compare-versions": "^5.0.1", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "cron": "^1.8.2", diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts index 5ce2ade250..ecfd94e6ac 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts @@ -8,6 +8,7 @@ import { XKnex } from '../../../index'; import LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecordColumn'; import LookupColumn from '../../../../../models/LookupColumn'; import { jsepCurlyHook, UITypes } from 'nocodb-sdk'; +import { validateDateWithUnknownFormat } from '../helpers/formulaFnHelper'; // todo: switch function based on database @@ -55,8 +56,11 @@ export default async function formulaQueryBuilderv2( jsep.plugins.register(jsepCurlyHook); const tree = jsep(_tree); + const columnIdToUidt = {}; + // todo: improve - implement a common solution for filter, sort, formula, etc for (const col of await model.getColumns()) { + columnIdToUidt[col.id] = col.uidt; if (col.id in aliasToColumn) continue; switch (col.uidt) { case UITypes.Formula: @@ -659,6 +663,47 @@ export default async function formulaQueryBuilderv2( const right = fn(pt.right, null, pt.operator).toQuery(); let sql = `${left} ${pt.operator} ${right}${colAlias}`; + // comparing a date with empty string would throw + // `ERROR: zero-length delimited identifier` in Postgres + if ( + knex.clientType() === 'pg' && + columnIdToUidt[pt.left.name] === UITypes.Date + ) { + // The correct way to compare with Date should be using + // `IS_AFTER`, `IS_BEFORE`, or `IS_SAME` + // This is to prevent empty data returned to UI due to incorrect SQL + if (pt.right.value === '') { + if (pt.operator === '=') { + sql = `${left} IS NULL ${colAlias}`; + } else { + sql = `${left} IS NOT NULL ${colAlias}`; + } + } else if (!validateDateWithUnknownFormat(pt.right.value)) { + // left tree value is date but right tree value is not date + // return true if left tree value is not null, else false + sql = `${left} IS NOT NULL ${colAlias}`; + } + } + if ( + knex.clientType() === 'pg' && + columnIdToUidt[pt.right.name] === UITypes.Date + ) { + // The correct way to compare with Date should be using + // `IS_AFTER`, `IS_BEFORE`, or `IS_SAME` + // This is to prevent empty data returned to UI due to incorrect SQL + if (pt.left.value === '') { + if (pt.operator === '=') { + sql = `${right} IS NULL ${colAlias}`; + } else { + sql = `${right} IS NOT NULL ${colAlias}`; + } + } else if (!validateDateWithUnknownFormat(pt.left.value)) { + // right tree value is date but left tree value is not date + // return true if right tree value is not null, else false + sql = `${right} IS NOT NULL ${colAlias}`; + } + } + // handle NULL values when calling CONCAT for sqlite3 if (pt.left.fnName === 'CONCAT' && knex.clientType() === 'sqlite3') { sql = `COALESCE(${left}, '') ${pt.operator} COALESCE(${right},'')${colAlias}`; diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts index f8057a2473..e3268d7406 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/helpers/formulaFnHelper.ts @@ -1,3 +1,7 @@ +import dayjs, { extend } from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat.js'; +extend(customParseFormat); + export function getWeekdayByText(v: string) { return { monday: 0, @@ -21,3 +25,28 @@ export function getWeekdayByIndex(idx: number): string { 6: 'sunday', }[idx || 0]; } + +export function validateDateWithUnknownFormat(v: string) { + const dateFormats = [ + 'DD-MM-YYYY', + 'MM-DD-YYYY', + 'YYYY-MM-DD', + 'DD/MM/YYYY', + 'MM/DD/YYYY', + 'YYYY/MM/DD', + 'DD MM YYYY', + 'MM DD YYYY', + 'YYYY MM DD', + ]; + for (const format of dateFormats) { + if (dayjs(v, format, true).isValid() as any) { + return true; + } + for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) { + if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) { + return true; + } + } + } + return false; +} diff --git a/packages/nocodb/src/lib/meta/api/tableApis.ts b/packages/nocodb/src/lib/meta/api/tableApis.ts index e9aa9442d3..e18886cbeb 100644 --- a/packages/nocodb/src/lib/meta/api/tableApis.ts +++ b/packages/nocodb/src/lib/meta/api/tableApis.ts @@ -230,7 +230,7 @@ export async function tableUpdate(req: Request, res) { const project = await Project.getWithInfo(req.body.project_id); const base = project.bases.find((b) => b.id === model.base_id); - + if (!req.body.table_name) { NcError.badRequest( 'Missing table name `table_name` property in request body' diff --git a/packages/nocodb/src/lib/meta/api/utilApis.ts b/packages/nocodb/src/lib/meta/api/utilApis.ts index cdd9f0a321..16cee5adcb 100644 --- a/packages/nocodb/src/lib/meta/api/utilApis.ts +++ b/packages/nocodb/src/lib/meta/api/utilApis.ts @@ -1,5 +1,6 @@ // // Project CRUD import { Request, Response } from 'express'; +import { compareVersions, validate } from 'compare-versions'; import { ViewTypes } from 'nocodb-sdk'; import Project from '../../models/Project'; @@ -65,17 +66,22 @@ export async function versionInfo(_req: Request, res: Response) { (versionCache.lastFetched && versionCache.lastFetched < Date.now() - 1000 * 60 * 60) ) { - versionCache.releaseVersion = await axios - .get('https://github.com/nocodb/nocodb/releases/latest', { + const nonBetaTags = await axios + .get('https://api.github.com/repos/nocodb/nocodb/tags', { timeout: 5000, }) - .then((response) => - response.request.res.responseUrl.replace( - 'https://github.com/nocodb/nocodb/releases/tag/', - '' - ) - ) + .then((response) => { + return response.data + .map((x) => x.name) + .filter( + (v) => validate(v) && !v.includes('finn') && !v.includes('beta') + ) + .sort((x, y) => compareVersions(y, x)); + }) .catch(() => null); + if (nonBetaTags && nonBetaTags.length > 0) { + versionCache.releaseVersion = nonBetaTags[0]; + } versionCache.lastFetched = Date.now(); }