diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 0a0596915f9..25793d026fa 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -49,7 +49,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d @@ -61,7 +60,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-pg-restTableOps-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-pg-restViews-run-cache: @@ -94,7 +93,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d @@ -106,7 +104,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-pg-restViews-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-pg-restRoles-run-cache: @@ -139,7 +137,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d @@ -151,7 +148,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-pg-restRoles-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-pg-restMisc-run-cache: @@ -184,7 +181,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d @@ -196,7 +192,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-pg-restMisc-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-restTableOps-run-cache: @@ -229,7 +225,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -241,7 +236,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-restTableOps-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-restViews-run-cache: @@ -274,7 +269,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -286,7 +280,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-restViews-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-restRoles-run-cache: @@ -319,7 +313,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -331,7 +324,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-restRoles-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-restMisc-run-cache: @@ -364,7 +357,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -376,7 +368,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-restMisc-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-xcdb-restTableOps-run-cache: @@ -409,7 +401,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:xcdb-api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -421,7 +412,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-xcdb-restTableOps-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-xcdb-restViews-run-cache: @@ -454,7 +445,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:xcdb-api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -466,7 +456,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-xcdb-restViews-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-xcdb-restRoles-run-cache: @@ -499,7 +489,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:xcdb-api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -511,7 +500,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-xcdb-restRoles-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 cypress-xcdb-restMisc-run-cache: @@ -544,7 +533,6 @@ jobs: uses: cypress-io/github-action@v2 with: start: | - npm run build:common npm run start:xcdb-api:cache npm run start:web docker-compose -f ./scripts/docker-compose-cypress.yml up -d @@ -556,7 +544,7 @@ jobs: if: always() uses: actions/upload-artifact@v2 with: - name: restTableOps-snapshots + name: cypress-xcdb-restMisc-run-cache-snapshots path: scripts/cypress/screenshots retention-days: 2 # docker: diff --git a/.github/workflows/release-executables.yml b/.github/workflows/release-executables.yml new file mode 100644 index 00000000000..686cdf0d573 --- /dev/null +++ b/.github/workflows/release-executables.yml @@ -0,0 +1,266 @@ +name: "Release : Executables" + +on: + # Triggered manually + workflow_dispatch: + inputs: + tag: + description: "Tag name" + required: true + # Triggered by release-nocodb.yml + workflow_call: + inputs: + tag: + description: "Tag name" + required: true + type: string + secrets: + NC_GITHUB_TOKEN: + required: true +jobs: + build-executables: + runs-on: ubuntu-latest + steps: + # Get the latest draft release for asset upload url + - uses: cardinalby/git-get-release-action@v1 + id: get_release + env: + GITHUB_TOKEN: ${{ secrets.NC_GITHUB_TOKEN }} + with: + latest: 1 + draft: true + - uses: actions/checkout@v3 + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Cache pkg modules + id: cache-pkg + uses: actions/cache@v3 + env: + cache-name: cache-pkg + with: + # pkg cache files are stored in `~/.pkg-cache` + path: ~/.pkg-cache + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + # for building images for all platforms these libraries are required in Linux + - name: Install QEMU and ldid + run: | + sudo apt update + # Install qemu + sudo apt install qemu binfmt-support qemu-user-static + # install ldid + git clone https://github.com/daeken/ldid.git + cd ./ldid + ./make.sh + sudo cp ./ldid /usr/local/bin + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name : Install nocodb, other dependencies and build executables + run: | + cd ./scripts/pkg-executable + + # Install nocodb version based on provided tag name + npm i -E nocodb@$TAG + + # install npm dependendencies + npm i + + # Copy sqlite binaries + rsync -rvzhP ./binaries/binding/ ./node_modules/sqlite3/lib/binding/ + + # clean up code to optimize size + npx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,sqlite3/**" --run + + # build executables + npm run build + + ls ./dist + + # Move macOS executables for signing + mkdir ./mac-dist + mv ./dist/Noco-macos-arm64 ./mac-dist/ + mv ./dist/Noco-macos-x64 ./mac-dist/ + + - name: Upload win-arm64 build to asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/dist/Noco-win-arm64.exe + asset_name: Noco-win-arm64 + asset_content_type: application/octet-stream + + - name: Upload win-x64 build to asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/dist/Noco-win-x64.exe + asset_name: Noco-win-x64 + asset_content_type: application/octet-stream + + - name: Upload linux-arm64 build to asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/dist/Noco-linux-arm64 + asset_name: Noco-linux-arm64 + asset_content_type: application/octet-stream + + - name: Upload linux-x64 build to asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/dist/Noco-linux-x64 + asset_name: Noco-linux-x64 + asset_content_type: application/octet-stream + + - uses: actions/upload-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: scripts/pkg-executable/mac-dist + retention-days: 1 + outputs: + upload_url: ${{ steps.get_release.outputs.upload_url }} + sign-mac-executables: + runs-on: macos-latest + needs: build-executables + steps: + + - uses: actions/download-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: scripts/pkg-executable/mac-dist + + - name: Sign macOS executables + run: | + /usr/bin/codesign --force -s - ./scripts/pkg-executable/mac-dist/Noco-macos-arm64 -v + /usr/bin/codesign --force -s - ./scripts/pkg-executable/mac-dist/Noco-macos-x64 -v + + - uses: actions/upload-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: scripts/pkg-executable/mac-dist + retention-days: 1 + + + publish-mac-executables-and-homebrew: + needs: [sign-mac-executables,build-executables] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: scripts/pkg-executable/mac-dist + + + + - uses: actions/checkout@v3 + with: + path: 'homebrew-nocodb' + token: ${{ secrets.NC_GITHUB_TOKEN }} + repository: 'nocodb/homebrew-nocodb' + fetch-depth: 0 + + - name: Compress files and calculate checksum + run: | + cd ./scripts/pkg-executable + cp ./mac-dist/Noco-macos-x64 ./mac-dist/nocodb + tar -czf ./mac-dist/nocodb.tar.gz ./mac-dist/nocodb + rm ./mac-dist/nocodb + echo "::set-output name=CHECKSUM::$(shasum -a 256 ./mac-dist/nocodb.tar.gz | awk '{print $1}')" + id: compress + + + - name: Upload macos-x64 build to asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.build-executables.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/mac-dist/Noco-macos-x64 + asset_name: Noco-macos-x64 + asset_content_type: application/octet-stream + + + + - name: Upload macos-arm64 build to asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.build-executables.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/mac-dist/Noco-macos-arm64 + asset_name: Noco-macos-arm64 + asset_content_type: application/octet-stream + + + + - name: Upload macos compressed build(for homebrew) to asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.build-executables.outputs.upload_url }} + asset_path: ./scripts/pkg-executable/mac-dist/nocodb.tar.gz + asset_name: nocodb.tar.gz + asset_content_type: application/octet-stream + + + - name: Generate Homebrew Formula class and push + run: | + FORMULA_CLASS_STR=$(cat << EOF + class Nocodb < Formula + desc "Get Human Readable file size information. - CLI" + homepage "https://github.com/nocodb/nocodb" + url "https://github.com/nocodb/nocodb/releases/download/${{ github.event.inputs.tag || inputs.tag }}/nocodb.tar.gz" + sha256 "${{ steps.compress.outputs.CHECKSUM }}" + license "MIT" + version "${{ github.event.inputs.tag || inputs.tag }}" + + def install + bin.install "nocodb" + end + end + EOF + ) + + cd ./homebrew-nocodb + + printf "$FORMULA_CLASS_STR" > ./Formula/nocodb.rb + + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + + git commit ./Formula/nocodb.rb -m "Automatic publish" + git push + + + + diff --git a/.github/workflows/release-nightly-dev.yml b/.github/workflows/release-nightly-dev.yml index af71aa58ff9..79b651cf8d1 100644 --- a/.github/workflows/release-nightly-dev.yml +++ b/.github/workflows/release-nightly-dev.yml @@ -37,7 +37,7 @@ jobs: outputs: nightly_build_tag: ${{ steps.tag-step.outputs.NIGHTLY_BUILD_TAG }} is_daily: ${{ steps.tag-step.outputs.IS_DAILY }} - current_version: ${{ steps.tag-step.outputs.CURRENT_VERSION }} + current_version: ${{ steps.tag-step.outputs.CURRENT_VERSION }} # Build frontend and backend and publish to npm release-npm: needs: set-tag @@ -48,6 +48,15 @@ jobs: secrets: NPM_TOKEN: "${{ secrets.NPM_TOKEN }}" + # Build executables and publish to GitHub + release-executables: + needs: [set-tag, release-npm] + uses: ./.github/workflows/release-timely-executables.yml + with: + tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.nightly_build_tag }} + secrets: + NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}" + # Build docker image and push to docker hub release-docker: needs: [set-tag, release-npm] diff --git a/.github/workflows/release-nocodb.yml b/.github/workflows/release-nocodb.yml index 0fe5ba1e030..cf09af5b837 100644 --- a/.github/workflows/release-nocodb.yml +++ b/.github/workflows/release-nocodb.yml @@ -44,14 +44,14 @@ jobs: # bump the version from PREV_TAG TARGET_TAG=$(echo ${PREV_TAG} | awk -F. -v OFS=. '{$NF += 1 ; print}') fi - + echo target version: ${TARGET_TAG} echo previous version: ${PREV_TAG} echo "::set-output name=target_tag::${TARGET_TAG}" echo "::set-output name=prev_tag::${PREV_TAG}" - name: Verify run : | - echo ${{ steps.process-input.outputs.target_tag }} + echo ${{ steps.process-input.outputs.target_tag }} # Merge develop to master pr-to-master: @@ -94,6 +94,15 @@ jobs: DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}" DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}" + # Build executables and publish to GitHub + release-executables: + needs: [release-draft-note, process-input] + uses: ./.github/workflows/release-executables.yml + with: + tag: ${{ needs.process-input.outputs.target_tag }} + secrets: + NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}" + # Close all issues with target tags 'Fixed' & 'Resolved' close-fixed-issues: needs: [release-docker, process-input] @@ -101,7 +110,7 @@ jobs: with: issue_label: 'Status: Fixed' version: ${{ needs.process-input.outputs.target_tag }} - + close-resolved-issues: needs: [close-fixed-issues, process-input] uses: ./.github/workflows/release-close-issue.yml diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 38cce2c3904..9d19da81a61 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -50,7 +50,7 @@ jobs: echo ${{ steps.tag-step.outputs.CURRENT_VERSION }} outputs: target_tag: ${{ steps.tag-step.outputs.TARGET_TAG }} - current_version: ${{ steps.tag-step.outputs.CURRENT_VERSION }} + current_version: ${{ steps.tag-step.outputs.CURRENT_VERSION }} # Build, install, publish frontend and backend to npm release-npm: @@ -77,13 +77,22 @@ jobs: DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}" DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}" + # Build executables and publish to GitHub + release-executables: + needs: [set-tag, release-npm] + uses: ./.github/workflows/release-timely-executables.yml + with: + tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }} + secrets: + NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}" + # leave-comment: # runs-on: 'ubuntu-latest' # needs: [release-docker, set-tag] # steps: # - run: | # echo docker run -d -p 8888:8080 nocodb/nocodb-timely:${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }} - + leave-comment: if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.draft == false }} runs-on: 'ubuntu-latest' @@ -96,7 +105,43 @@ jobs: ``` docker run -d -p 8888:8080 nocodb/nocodb-timely:${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }} ``` - + + leave-executable-comment: + if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.draft == false }} + runs-on: 'ubuntu-latest' + needs: [release-executables, set-tag] + steps: + - uses: peter-evans/commit-comment@v2 + with: + body: | + ### Run Executables + + #### MacOS + + ```bash + mkdir -p ./${{ needs.set-tag.outputs.current_version }}/${{ needs.set-tag.outputs.target_tag }} && cd "$_" \ + && curl http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-macos-arm64 -o noco -L \ + && chmod +x noco \ + && ./noco + ``` + #### Linux + + ```bash + mkdir -p ./${{ needs.set-tag.outputs.current_version }}/${{ needs.set-tag.outputs.target_tag }} && cd "$_" \ + && curl http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-linux-x64 -o noco -L \ + && chmod +x noco \ + && ./noco + ``` + #### Windows + + ```bash + iwp http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-win-arm64.exe + .\Noco-win-arm64.exe + ``` + + For executables visit [here](https://github.com/nocodb/nocodb-timely/releases/tag/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}) + + # if-merged: # if: github.event.pull_request.merged == true # runs-on: ubuntu-latest diff --git a/.github/workflows/release-timely-executables.yml b/.github/workflows/release-timely-executables.yml new file mode 100644 index 00000000000..9f8b0c7c1a3 --- /dev/null +++ b/.github/workflows/release-timely-executables.yml @@ -0,0 +1,158 @@ +name: "Release : Timely Executables" + +on: + # Triggered manually + workflow_dispatch: + inputs: + tag: + description: "Timely version" + required: true + # Triggered by release-nightly-dev.yml / release-pr.yml + workflow_call: + inputs: + tag: + description: "Timely version" + required: true + type: string + secrets: + NC_GITHUB_TOKEN: + required: true +jobs: + build-executables: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.NC_GITHUB_TOKEN }} + repository: 'nocodb/nocodb-timely' + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Cache pkg modules + id: cache-pkg + uses: actions/cache@v3 + env: + cache-name: cache-pkg + with: + # pkg cache files are stored in `~/.pkg-cache` + path: ~/.pkg-cache + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Install QEMU and ldid + run: | + sudo apt update + # Install qemu + sudo apt install qemu binfmt-support qemu-user-static + # install ldid + git clone https://github.com/daeken/ldid.git + cd ./ldid + ./make.sh + sudo cp ./ldid /usr/local/bin + + - name: Update nocodb-timely + env: + TAG: ${{ github.event.inputs.tag || inputs.tag }} + run: | + npm i -E nocodb-daily@$TAG + + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + + git commit package.json -m "Update to $TAG" + git tag $TAG + git push --tags + + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name : Install dependencies and build executables + run: | + # install npm dependendencies + npm i + + # Copy sqlite binaries + rsync -rvzhP ./binaries/binding/ ./node_modules/sqlite3/lib/binding/ + + # clean up code to optimize size + npx modclean --patterns="default:*" --ignore="nc-lib-gui-daily/**,dayjs/**,express-status-monitor/**,sqlite3/**" --run + + # build executables + npm run build + + mkdir ./mac-dist + mv ./dist/Noco-macos-arm64 ./mac-dist/ + mv ./dist/Noco-macos-x64 ./mac-dist/ + + - name: Upload executables(except mac executables) to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.NC_GITHUB_TOKEN }} + file: dist/** + tag: ${{ github.event.inputs.tag || inputs.tag }} + overwrite: true + file_glob: true + repo_name: nocodb/nocodb-timely + + - uses: actions/upload-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: mac-dist + retention-days: 1 + + sign-mac-executables: + runs-on: macos-latest + needs: build-executables + steps: + + - uses: actions/download-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: mac-dist + + - name: Sign macOS executables + run: | + /usr/bin/codesign --force -s - ./mac-dist/Noco-macos-arm64 -v + /usr/bin/codesign --force -s - ./mac-dist/Noco-macos-x64 -v + + - uses: actions/upload-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: mac-dist + retention-days: 1 + + + publish-mac-executables: + needs: sign-mac-executables + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@master + with: + name: ${{ github.event.inputs.tag || inputs.tag }} + path: mac-dist + + - name: Upload mac executables to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.NC_GITHUB_TOKEN }} + file: mac-dist/** + tag: ${{ github.event.inputs.tag || inputs.tag }} + overwrite: true + file_glob: true + repo_name: nocodb/nocodb-timely + + diff --git a/package.json b/package.json index 559c8d471b4..602192d780b 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,12 @@ "scripts": { "build:common": "cd ./packages/nocodb-sdk; npm install; npm run build", "install:common": "cd ./packages/nocodb; npm install ../nocodb-sdk; cd ../nc-gui; npm install ../nocodb-sdk", - "start:api": "cd ./packages/nocodb; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress", - "start:xcdb-api": "cd ./packages/nocodb; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress", - "start:api:cache": "cd ./packages/nocodb; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress", - "start:xcdb-api:cache": "cd ./packages/nocodb; npm install; NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress", - "start:web": "cd ./packages/nc-gui; npm install; npm run dev", + "start:api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress", + "start:xcdb-api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress", + "start:api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_DISABLE_TELE=true npm run watch:run:cypress", + "start:api:cache:pg": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg", + "start:xcdb-api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress", + "start:web": "npm run build:common ; cd ./packages/nc-gui; npm install ../nocodb-sdk; npm install; npm run dev", "cypress:run": "cypress run --config-file ./scripts/cypress/cypress.json", "cypress:open": "cypress open --config-file ./scripts/cypress/cypress.json", "cypress:clear": "cypress cache clear", diff --git a/packages/nc-gui/components/CreateOrEditProject.vue b/packages/nc-gui/components/CreateOrEditProject.vue index 831b4fa4899..12db72c65a6 100644 --- a/packages/nc-gui/components/CreateOrEditProject.vue +++ b/packages/nc-gui/components/CreateOrEditProject.vue @@ -89,7 +89,7 @@ ref="name" v-model="project.title" v-ge="['project', 'name']" - :rules="form.titleRequiredRule" + :rules="form.titleValidationRule" :height="20" :label="$t('placeholder.projName')" autofocus @@ -453,14 +453,14 @@ " /> - + /^\d+$/.test(v) || 'Not a valid port'], - titleRequiredRule: [v => !!v || 'Title is required'], + titleValidationRule: [ + v => !!v || 'Title is required', + v => v.length <= 50 || 'Project name exceeds 50 characters', + ], requiredRule: [v => !!v || 'Field is required'], folderRequiredRule: [v => !!v || 'Folder path is required'] }, @@ -1539,9 +1541,7 @@ export default { this.projectReloading = true const con = projectJson.envs._noco.db[0] - if (con.client === 'pg' || con.client === 'mssql') { - con.searchPath = [this.schema] - } else if ('searchPath' in con) { + if (con.client !== 'pg' && con.client !== 'mssql' && 'searchPath' in con) { delete con.searchPath } @@ -1892,9 +1892,9 @@ export default { }, onDatabaseTypeChanged(client, db1, index, env) { if (this.databaseNames[client] === 'mssql') { - this.schema = 'dbo' + this.project.envs[env].db[index].searchPath[0] = 'dbo' } else if (this.databaseNames[client] === 'pg') { - this.schema = 'public' + this.project.envs[env].db[index].searchPath[0] = 'public' } for (const env in this.project.envs) { diff --git a/packages/nc-gui/components/ProjectTabs.vue b/packages/nc-gui/components/ProjectTabs.vue index a3fd6392ece..0a69cd33600 100644 --- a/packages/nc-gui/components/ProjectTabs.vue +++ b/packages/nc-gui/components/ProjectTabs.vue @@ -315,6 +315,21 @@ + + + + mdi-code-json + + + + JSON file + + + + + + @@ -404,9 +426,11 @@ import GlobalAcl from '~/components/GlobalAcl' import AuditTab from '~/components/project/AuditTab' import QuickImport from '~/components/import/QuickImport' import ImportFromAirtable from '~/components/import/ImportFromAirtable' +import JsonImport from '~/components/import/JSONImport' export default { components: { + JsonImport, ImportFromAirtable, SwaggerClient, // Screensaver, @@ -447,7 +471,8 @@ export default { showScreensaver: false, quickImportModal: false, quickImportType: '', - airtableImportModal: false + airtableImportModal: false, + jsonImportModal: false } }, methods: { diff --git a/packages/nc-gui/components/import/ImportFromAirtable.vue b/packages/nc-gui/components/import/ImportFromAirtable.vue index d22031d35fb..a3264182a37 100644 --- a/packages/nc-gui/components/import/ImportFromAirtable.vue +++ b/packages/nc-gui/components/import/ImportFromAirtable.vue @@ -8,7 +8,6 @@
🚀
@@ -86,6 +85,13 @@ hide-details dense /> + +
+ + + + + + mdi-file-upload-outline + + Upload + + + + + + mdi-link-variant + + String + + + +
+
+ + mdi-file-plus-outline + +

+ + {{ $t('msg.info.upload') }} +

+

+ + {{ $t('msg.info.upload_sub') }} +

+ +

+ + {{ $t('msg.info.excelSupport') }} +

+
+
+
+ + +
+
+ +
+ + Format + + + + +
+ + + {{ $t('general.load') }} + +
+
+
+
+
+
+
+ +
+
+ + + {{ showMore ? $t('general.hideAll') : $t('general.showMore') }} + mdi-menu-{{ showMore ? 'up' : 'down' }} + +
+
+

+ + + + + + + + + +

+
+
+
+ + + + Create template from JSON + + + + + + + + + +
+ + + + + diff --git a/packages/nc-gui/components/import/QuickImport.vue b/packages/nc-gui/components/import/QuickImport.vue index 3ac14f1a685..49842bdf596 100644 --- a/packages/nc-gui/components/import/QuickImport.vue +++ b/packages/nc-gui/components/import/QuickImport.vue @@ -60,11 +60,18 @@ v-model="url" hide-details="auto" type="url" - :label="quickImportType == 'excel' ? $t('msg.info.excelURL') : $t('msg.info.csvURL') " + :label="quickImportType === 'excel' ? $t('msg.info.excelURL') : $t('msg.info.csvURL') " class="caption" outlined dense - :rules="[v => !!v || $t('general.required') ]" + :rules=" + [ + v => !!v || $t('general.required'), + v => !(/(10)(\.([2]([0-5][0-5]|[01234][6-9])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}|(172)\.(1[6-9]|2[0-9]|3[0-1])(\.(2[0-4][0-9]|25[0-5]|[1][0-9][0-9]|[1-9][0-9]|[0-9])){2}|(192)\.(168)(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){2}|(0.0.0.0)|localhost?/g).test(v) || errorMessages.ipBlockList, + v => quickImportType === 'excel' ? + (/.*\.(xls|xlsx|xlsm|ods|ots)/.test(v) || errorMessages.importExcel) : + (/.*\.(csv)/.test(v) || errorMessages.importCSV), + ]" /> @@ -213,7 +220,12 @@ export default { parserConfig: { maxRowsToParse: 500 }, - filename: '' + filename: '', + errorMessages: { + importExcel: "Target file is not an accepted file type. The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots!", + importCSV: "Target file is not an accepted file type. The accepted file type is .csv!", + ipBlockList: "IP Not allowed!" + } } }, computed: { @@ -288,7 +300,7 @@ export default { templateGenerator = new ExcelTemplateAdapter(name, val, this.parserConfig) break case 'url': - templateGenerator = new ExcelUrlTemplateAdapter(val, this.$store, this.parserConfig, this.$api) + templateGenerator = new ExcelUrlTemplateAdapter(val, this.$store, this.parserConfig, this.$api, this.quickImportType) break } await templateGenerator.init() @@ -322,11 +334,11 @@ export default { if (this.quickImportType === 'excel') { if (!/.*\.(xls|xlsx|xlsm|ods|ots)/.test(file.name)) { - return this.$toast.error('Dropped file is not an accepted file type. The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots!').goAway(3000) + return this.$toast.error(this.errorMessages.importExcel).goAway(3000) } } else if (this.quickImportType === 'csv') { if (!/.*\.(csv)/.test(file.name)) { - return this.$toast.error('Dropped file is not an accepted file type. The accepted file type is .csv!').goAway(3000) + return this.$toast.error(this.errorMessages.importCSV).goAway(3000) } } this._file(file) diff --git a/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js index 9a3be0d4de2..c608a79f850 100644 --- a/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js +++ b/packages/nc-gui/components/import/templateParsers/CSVTemplateAdapter.js @@ -1,6 +1,5 @@ import Papaparse from 'papaparse' import TemplateGenerator from '~/components/import/templateParsers/TemplateGenerator' - export default class CSVTemplateAdapter extends TemplateGenerator { constructor(name, data) { super() diff --git a/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js index 9202e4ab310..1bd0107d151 100644 --- a/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js +++ b/packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js @@ -27,7 +27,25 @@ export default class ExcelTemplateAdapter extends TemplateGenerator { } async init() { - this.wb = XLSX.read(new Uint8Array(this.excelData), { type: 'array', cellText: true, cellDates: true }) + const options = { + cellText: true, + cellDates: true + } + if (this.name.slice(-3) === 'csv') { + this.wb = XLSX.read( + (new TextDecoder).decode(new Uint8Array(this.excelData)), + { + type: "string", + ...options + }); + } else { + this.wb = XLSX.read( + new Uint8Array(this.excelData), + { + type: 'array', + ...options + }) + } } parse() { diff --git a/packages/nc-gui/components/import/templateParsers/ExcelUrlTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/ExcelUrlTemplateAdapter.js index a263ccc0fd9..d8a91ac3d2d 100644 --- a/packages/nc-gui/components/import/templateParsers/ExcelUrlTemplateAdapter.js +++ b/packages/nc-gui/components/import/templateParsers/ExcelUrlTemplateAdapter.js @@ -1,19 +1,19 @@ import ExcelTemplateAdapter from '~/components/import/templateParsers/ExcelTemplateAdapter' export default class ExcelUrlTemplateAdapter extends ExcelTemplateAdapter { - constructor(url, $store, parserConfig, $api) { + constructor(url, $store, parserConfig, $api, quickImportType) { const name = url.split('/').pop() super(name, null, parserConfig) this.url = url this.$api = $api this.$store = $store + this.quickImportType = quickImportType } async init() { const data = await this.$api.utils.axiosRequestMake({ apiMeta: { - url: this.url, - responseType: 'arraybuffer' + url: this.url } }) this.excelData = data.data diff --git a/packages/nc-gui/components/import/templateParsers/JSONTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/JSONTemplateAdapter.js new file mode 100644 index 00000000000..b53230b789e --- /dev/null +++ b/packages/nc-gui/components/import/templateParsers/JSONTemplateAdapter.js @@ -0,0 +1,150 @@ +import { TemplateGenerator, UITypes } from 'nocodb-sdk' +import { + extractMultiOrSingleSelectProps, + getCheckboxValue, + isCheckboxType, isDecimalType, isEmailType, + isMultiLineTextType, isUrlType +} from '~/components/import/templateParsers/parserHelpers' + +const jsonTypeToUidt = { + number: UITypes.Number, + string: UITypes.SingleLineText, + date: UITypes.DateTime, + boolean: UITypes.Checkbox, + object: UITypes.JSON +} + +const extractNestedData = (obj, path) => path.reduce((val, key) => val && val[key], obj) + +export default class JSONTemplateAdapter extends TemplateGenerator { + constructor(name = 'test', data, parserConfig = {}) { + super() + this.config = { + maxRowsToParse: 500, + ...parserConfig + } + this.name = name + this._jsonData = typeof data === 'string' ? JSON.parse(data) : data + this.project = { + title: this.name, + tables: [] + } + this.data = {} + } + + async init() { + } + + parseData() { + this.columns = this.csv.meta.fields + this.data = this.csv.data + } + + getColumns() { + return this.columns + } + + getData() { + return this.data + } + + get jsonData() { + return Array.isArray(this._jsonData) ? this._jsonData : [this._jsonData] + } + + parse() { + const jsonData = this.jsonData + const tn = 'table' + const table = { table_name: tn, ref_table_name: tn, columns: [] } + + this.data[tn] = [] + + for (const col of Object.keys(jsonData[0])) { + const columns = this._parseColumn([col], jsonData) + table.columns.push(...columns) + } + + if (this.config.importData) { this._parseTableData(table) } + + this.project.tables.push(table) + } + + getTemplate() { + return this.project + } + + _parseColumn(path = [], jsonData = this.jsonData, firstRowVal = path.reduce((val, k) => val && val[k], this.jsonData[0])) { + const columns = [] + // parse nested + if (firstRowVal && typeof firstRowVal === 'object' && !Array.isArray(firstRowVal) && this.config.normalizeNested) { + for (const key of Object.keys(firstRowVal)) { + const normalizedNestedColumns = this._parseColumn([...path, key], this.jsonData, firstRowVal[key]) + columns.push(...normalizedNestedColumns) + } + } else { + const cn = path.join('_').replace(/\W/g, '_').trim() + + const column = { + column_name: cn, + ref_column_name: cn, + path + } + + column.uidt = jsonTypeToUidt[typeof firstRowVal] || UITypes.SingleLineText + + const colData = jsonData.map(r => extractNestedData(r, path)) + Object.assign(column, this._getColumnUIDTAndMetas(colData, column.uidt)) + columns.push(column) + } + + return columns + } + + _getColumnUIDTAndMetas(colData, defaultType) { + const colProps = { uidt: defaultType } + // todo: optimize + if (colProps.uidt === UITypes.SingleLineText) { + // check for long text + if (isMultiLineTextType(colData)) { + colProps.uidt = UITypes.LongText + } if (isEmailType(colData)) { + colProps.uidt = UITypes.Email + } if (isUrlType(colData)) { + colProps.uidt = UITypes.URL + } else { + const checkboxType = isCheckboxType(colData) + if (checkboxType.length === 1) { + colProps.uidt = UITypes.Checkbox + } else { + Object.assign(colProps, extractMultiOrSingleSelectProps(colData)) + } + } + } else if (colProps.uidt === UITypes.Number) { + if (isDecimalType(colData)) { + colProps.uidt = UITypes.Decimal + } + } + return colProps + } + + _parseTableData(tableMeta) { + for (const row of this.jsonData) { + const rowData = {} + for (let i = 0; i < tableMeta.columns.length; i++) { + const value = extractNestedData(row, tableMeta.columns[i].path || []) + if (tableMeta.columns[i].uidt === UITypes.Checkbox) { + rowData[tableMeta.columns[i].ref_column_name] = getCheckboxValue(value) + } else if (tableMeta.columns[i].uidt === UITypes.SingleSelect || tableMeta.columns[i].uidt === UITypes.MultiSelect) { + rowData[tableMeta.columns[i].ref_column_name] = (value || '').toString().trim() || null + } else if (tableMeta.columns[i].uidt === UITypes.JSON) { + rowData[tableMeta.columns[i].ref_column_name] = JSON.stringify(value) + } else { + // toto: do parsing if necessary based on type + rowData[tableMeta.columns[i].column_name] = value + } + } + this.data[tableMeta.ref_table_name].push(rowData) + // rowIndex++ + } + } +} diff --git a/packages/nc-gui/components/import/templateParsers/JSONUrlTemplateAdapter.js b/packages/nc-gui/components/import/templateParsers/JSONUrlTemplateAdapter.js new file mode 100644 index 00000000000..34494e00fc5 --- /dev/null +++ b/packages/nc-gui/components/import/templateParsers/JSONUrlTemplateAdapter.js @@ -0,0 +1,21 @@ +import JSONTemplateAdapter from '~/components/import/templateParsers/JSONTemplateAdapter' + +export default class JSONUrlTemplateAdapter extends JSONTemplateAdapter { + constructor(url, $store, parserConfig, $api) { + const name = url.split('/').pop() + super(name, null, parserConfig) + this.url = url + this.$api = $api + this.$store = $store + } + + async init() { + const data = await this.$api.utils.axiosRequestMake({ + apiMeta: { + url: this.url + } + }) + this._jsonData = data + await super.init() + } +} diff --git a/packages/nc-gui/components/import/templateParsers/parserHelpers.js b/packages/nc-gui/components/import/templateParsers/parserHelpers.js index 75fd80ead70..43156d3bea5 100644 --- a/packages/nc-gui/components/import/templateParsers/parserHelpers.js +++ b/packages/nc-gui/components/import/templateParsers/parserHelpers.js @@ -1,3 +1,6 @@ +import { UITypes } from 'nocodb-sdk' +import { isEmail, isValidURL } from '~/helpers' + const booleanOptions = [ { checked: true, unchecked: false }, { x: true, '': false }, @@ -11,14 +14,24 @@ const booleanOptions = [ { '✔': true, '': false }, { enabled: true, disabled: false }, { on: true, off: false }, - { done: true, '': false } + { done: true, '': false }, + { true: true, false: false } ] const aggBooleanOptions = booleanOptions.reduce((obj, o) => ({ ...obj, ...o }), {}) -export const isCheckboxType = (values, col = '') => { + +const getColVal = (row, col = null) => { + return row && col ? row[col] : row +} + +export const isCheckboxType = (values, col = null) => { let options = booleanOptions for (let i = 0; i < values.length; i++) { - let val = col ? values[i][col] : values[i] - val = val === null || val === undefined ? '' : val + const val = getColVal(values[i], col) + + if (val === null || val === undefined || val.toString().trim() === '') { + continue + } + options = options.filter(v => val in v) if (!options.length) { return false @@ -29,3 +42,40 @@ export const isCheckboxType = (values, col = '') => { export const getCheckboxValue = (value) => { return value && aggBooleanOptions[value] } + +export const isMultiLineTextType = (values, col = null) => { + return values.some(r => + (getColVal(r, col) || '').toString().match(/[\r\n]/) || + (getColVal(r, col) || '').toString().length > 255) +} + +export const extractMultiOrSingleSelectProps = (colData) => { + const colProps = {} + if (colData.some(v => v && (v || '').toString().includes(','))) { + let flattenedVals = colData.flatMap(v => v ? v.toString().trim().split(/\s*,\s*/) : []) + const uniqueVals = flattenedVals = flattenedVals + .filter((v, i, arr) => i === arr.findIndex(v1 => v.toLowerCase() === v1.toLowerCase())) + if (flattenedVals.length > uniqueVals.length && uniqueVals.length <= Math.ceil(flattenedVals.length / 2)) { + colProps.uidt = UITypes.MultiSelect + colProps.dtxp = `'${uniqueVals.join("','")}'` + } + } else { + const uniqueVals = colData.map(v => (v || '').toString().trim()).filter((v, i, arr) => i === arr.findIndex(v1 => v.toLowerCase() === v1.toLowerCase())) + if (colData.length > uniqueVals.length && uniqueVals.length <= Math.ceil(colData.length / 2)) { + colProps.uidt = UITypes.SingleSelect + colProps.dtxp = `'${uniqueVals.join("','")}'` + } + } + return colProps +} + +export const isDecimalType = colData => colData.some((v) => { + return v && parseInt(+v) !== +v +}) + +export const isEmailType = colData => !colData.some((v) => { + return v && !isEmail(v) +}) +export const isUrlType = colData => !colData.some((v) => { + return v && !isValidURL(v) +}) diff --git a/packages/nc-gui/components/monaco/MonacoJsonEditor.js b/packages/nc-gui/components/monaco/MonacoJsonEditor.js index 1362ac4f1c2..aa05a997385 100644 --- a/packages/nc-gui/components/monaco/MonacoJsonEditor.js +++ b/packages/nc-gui/components/monaco/MonacoJsonEditor.js @@ -83,6 +83,9 @@ export default { }, methods: { + format() { + this.editor.getAction('editor.action.formatDocument').run() + }, resizeLayout() { this.editor.layout(); }, diff --git a/packages/nc-gui/components/project/spreadsheet/components/Cell.vue b/packages/nc-gui/components/project/spreadsheet/components/Cell.vue index 5dd090cedc9..76966531a8a 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/Cell.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/Cell.vue @@ -17,6 +17,7 @@ + @@ -37,10 +38,11 @@ import BooleanCell from '~/components/project/spreadsheet/components/cell/Boolea import EmailCell from '~/components/project/spreadsheet/components/cell/EmailCell' import RatingCell from '~/components/project/spreadsheet/components/editableCell/RatingCell' import CurrencyCell from '@/components/project/spreadsheet/components/cell/CurrencyCell' +import DurationCell from '@/components/project/spreadsheet/components/cell/DurationCell' export default { name: 'TableCell', - components: { RatingCell, EmailCell, TimeCell, DateTimeCell, DateCell, JsonCell, UrlCell, EditableAttachmentCell, EnumCell, SetListCell, BooleanCell, CurrencyCell }, + components: { RatingCell, EmailCell, TimeCell, DateTimeCell, DateCell, JsonCell, UrlCell, EditableAttachmentCell, EnumCell, SetListCell, BooleanCell, CurrencyCell, DurationCell }, mixins: [cell], props: ['value', 'dbAlias', 'isLocked', 'selected', 'column'], computed: { diff --git a/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue b/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue index 15d7c07ac70..03d75d602c0 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/EditColumn.vue @@ -172,7 +172,6 @@ :column="newColumn" :meta="meta" /> - - -
@@ -572,6 +580,7 @@ import { validateColumnName } from '~/helpers' import RatingOptions from '~/components/project/spreadsheet/components/editColumn/RatingOptions' import CheckboxOptions from '~/components/project/spreadsheet/components/editColumn/CheckboxOptions' import CurrencyOptions from '@/components/project/spreadsheet/components/editColumn/CurrencyOptions' +import DurationOptions from '@/components/project/spreadsheet/components/editColumn/DurationOptions' const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber] @@ -587,7 +596,8 @@ export default { DlgLabelSubmitCancel, RelationOptions, CustomSelectOptions, - CurrencyOptions + CurrencyOptions, + DurationOptions }, props: { nodes: Object, @@ -617,7 +627,8 @@ export default { UITypes.Lookup, UITypes.Rollup, UITypes.SpecificDBType, - UITypes.Formula + UITypes.Formula, + UITypes.Duration ].includes(this.newColumn && this.newColumn.uidt) }, uiTypes() { @@ -631,7 +642,7 @@ export default { ] }, isEditDisabled() { - return this.editColumn && this.sqlUi === SqliteUi + return this.editColumn && this.sqlUi === SqliteUi && this.column.uidt !== UITypes.Duration }, isSQLite() { return this.sqlUi === SqliteUi diff --git a/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue b/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue index 5ce6c7e4280..a1e8974d1cc 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/EditableCell.vue @@ -35,6 +35,16 @@ v-on="$listeners" /> + + + + + + + + + + diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn/DurationOptions.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn/DurationOptions.vue new file mode 100644 index 00000000000..4386131fd45 --- /dev/null +++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn/DurationOptions.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue b/packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue index a2cebccb06a..d0df405eed1 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue @@ -267,10 +267,7 @@ export default { // validate data type if (parsedTree.callee.type === jsep.IDENTIFIER) { const expectedType = formulas[parsedTree.callee.name].type - if ( - expectedType === formulaTypes.NUMERIC || - expectedType === formulaTypes.STRING - ) { + if (expectedType === formulaTypes.NUMERIC) { parsedTree.arguments.map(arg => this.validateAgainstType(arg, expectedType, null, typeErrors)) } else if (expectedType === formulaTypes.DATE) { if (parsedTree.callee.name === 'DATEADD') { diff --git a/packages/nc-gui/components/project/spreadsheet/components/editableCell/DurationCell.vue b/packages/nc-gui/components/project/spreadsheet/components/editableCell/DurationCell.vue new file mode 100644 index 00000000000..c19d2199b35 --- /dev/null +++ b/packages/nc-gui/components/project/spreadsheet/components/editableCell/DurationCell.vue @@ -0,0 +1,139 @@ + + + + + + + diff --git a/packages/nc-gui/components/project/spreadsheet/components/editableCell/TimePickerCell.vue b/packages/nc-gui/components/project/spreadsheet/components/editableCell/TimePickerCell.vue index cdaa9605c69..86b1f94b7c8 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/editableCell/TimePickerCell.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/editableCell/TimePickerCell.vue @@ -5,7 +5,7 @@
- + {{ $t('general.save') }} @@ -15,7 +15,6 @@