mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 12:35:02 +02:00
UBERF-9457: Region move fixes + tests (#7986)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
+90
-51
@@ -40,6 +40,8 @@ env:
|
||||
rush.json
|
||||
.prettierrc
|
||||
tools
|
||||
workers
|
||||
ws-tests
|
||||
PublishTempFolder: publish_artifacts
|
||||
INIT_SCRIPTS_BRANCH: 'unified-init-scripts'
|
||||
|
||||
@@ -81,13 +83,13 @@ jobs:
|
||||
run: node common/scripts/install-run-rush.js model-version
|
||||
|
||||
- name: Building...
|
||||
run: node common/scripts/install-run-rush.js build -p 20
|
||||
run: node common/scripts/install-run-rush.js build
|
||||
|
||||
- name: Bundle...
|
||||
run: node common/scripts/install-run-rush.js bundle -p 20
|
||||
run: node common/scripts/install-run-rush.js bundle
|
||||
|
||||
- name: Validate...
|
||||
run: node common/scripts/install-run-rush.js validate -p 20
|
||||
run: node common/scripts/install-run-rush.js validate
|
||||
|
||||
- name: Cache build results
|
||||
uses: actions/cache@v4
|
||||
@@ -154,7 +156,7 @@ jobs:
|
||||
[ -z "$(git diff --name-only '*.js' '*.ts' '*.svelte' '*.json' '*.yaml' | cat)" ]
|
||||
echo '================================================================'
|
||||
- name: Formatting...
|
||||
run: node common/scripts/install-run-rush.js fast-format
|
||||
run: node common/scripts/install-run-rush.js fast-format --branch ${{ github.base_ref }}
|
||||
- name: Check files formatting
|
||||
run: |
|
||||
echo '================================================================'
|
||||
@@ -223,7 +225,7 @@ jobs:
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
|
||||
- name: Docker Build
|
||||
run: node common/scripts/install-run-rush.js docker:build -p 20
|
||||
run: node common/scripts/install-run-rush.js docker
|
||||
env:
|
||||
DOCKER_CLI_HINTS: false
|
||||
- name: Prepare server
|
||||
@@ -260,10 +262,8 @@ jobs:
|
||||
run: |
|
||||
cd ./tests/sanity
|
||||
mkdir logs
|
||||
docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log
|
||||
docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log
|
||||
docker logs $(docker ps | grep front | cut -f 1 -d ' ') > logs/front.log
|
||||
docker logs $(docker ps | grep collaborator | cut -f 1 -d ' ') > logs/collaborator.log
|
||||
cd logs
|
||||
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -340,7 +340,7 @@ jobs:
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
|
||||
- name: Docker Build
|
||||
run: node common/scripts/install-run-rush.js docker:build -p 20
|
||||
run: node common/scripts/install-run-rush.js docker
|
||||
env:
|
||||
DOCKER_CLI_HINTS: false
|
||||
- name: Prepare server
|
||||
@@ -361,9 +361,8 @@ jobs:
|
||||
run: |
|
||||
cd ./tests/sanity
|
||||
mkdir logs
|
||||
docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log
|
||||
docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log
|
||||
docker logs $(docker ps | grep front | cut -f 1 -d ' ') > logs/front.log
|
||||
cd logs
|
||||
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -405,12 +404,12 @@ jobs:
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
|
||||
- name: Docker Build
|
||||
run: node common/scripts/install-run-rush.js docker:build -p 20
|
||||
run: node common/scripts/install-run-rush.js docker
|
||||
env:
|
||||
DOCKER_CLI_HINTS: false
|
||||
- name: Configure /etc/hosts
|
||||
run: |
|
||||
sudo echo "127.0.0.1 host.docker.internal" | sudo tee -a /etc/hosts
|
||||
sudo echo "127.0.0.1 host.docker.internal" | sudo tee -a /etc/hosts
|
||||
- name: Prepare server
|
||||
run: |
|
||||
cd ./qms-tests
|
||||
@@ -429,50 +428,90 @@ jobs:
|
||||
run: |
|
||||
cd ./qms-tests/sanity
|
||||
mkdir logs
|
||||
docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log
|
||||
docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log
|
||||
docker logs $(docker ps | grep front | cut -f 1 -d ' ') > logs/front.log
|
||||
cd logs
|
||||
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-results-qms
|
||||
path: ./qms-tests/sanity/playwright-report/
|
||||
# - name: Get Allure history
|
||||
# uses: actions/checkout@v4
|
||||
# if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
# continue-on-error: true
|
||||
# with:
|
||||
# ref: gh-pages
|
||||
# path: gh-pages
|
||||
# - name: Generates Allure Report
|
||||
# uses: simple-elf/allure-report-action@master
|
||||
# if: always()
|
||||
# id: allure-report
|
||||
# with:
|
||||
# allure_results: ./qms-tests/sanity/allure-results/
|
||||
# gh_pages: gh-pages
|
||||
# allure_report: allure-report
|
||||
# allure_history: allure-history
|
||||
# - name: Upload allure test results
|
||||
# if: always()
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: allure-report-qms
|
||||
# path: ./allure-report/
|
||||
# - name: Deploy report to Github Pages
|
||||
# if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
# uses: peaceiris/actions-gh-pages@v4
|
||||
# with:
|
||||
# PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# PUBLISH_BRANCH: gh-pages
|
||||
# PUBLISH_DIR: allure-history
|
||||
- name: Upload Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-logs-qms
|
||||
path: ./qms-tests/sanity/logs
|
||||
uitest-workspaces:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-node-platform
|
||||
with:
|
||||
path: |
|
||||
common/temp
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Checking for mis-matching dependencies...
|
||||
run: node common/scripts/install-run-rush.js check
|
||||
|
||||
- name: Installing...
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
|
||||
- name: Build sanity-tests suite
|
||||
run: node common/scripts/install-run-rush.js build -t @hcengineering/tests-sanity
|
||||
|
||||
- name: Docker Build
|
||||
run: node common/scripts/install-run-rush.js docker
|
||||
env:
|
||||
DOCKER_CLI_HINTS: false
|
||||
- name: Configure /etc/hosts
|
||||
run: |
|
||||
sudo echo "127.0.0.1 host.docker.internal" | sudo tee -a /etc/hosts
|
||||
- name: Prepare server
|
||||
run: |
|
||||
cd ./ws-tests
|
||||
export DO_CLEAN=true
|
||||
./prepare.sh
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd ./ws-tests/sanity
|
||||
node ../../common/scripts/install-run-rushx.js ci
|
||||
- name: Run UI tests
|
||||
run: |
|
||||
cd ./ws-tests/sanity
|
||||
node ../../common/scripts/install-run-rushx.js uitest
|
||||
- name: 'Store docker logs'
|
||||
if: always()
|
||||
run: |
|
||||
cd ./ws-tests/sanity
|
||||
mkdir logs
|
||||
cd logs
|
||||
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-results-ws
|
||||
path: ./ws-tests/sanity/playwright-report/
|
||||
- name: Upload Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-logs-ws
|
||||
path: ./ws-tests/sanity/logs
|
||||
docker-build:
|
||||
needs: [build, test, svelte-check, uitest]
|
||||
runs-on: ubuntu-latest
|
||||
@@ -531,9 +570,9 @@ jobs:
|
||||
DOCKER_EXTRA: --platform=linux/amd64,linux/arm64
|
||||
- name: Docker build love-agent
|
||||
run: |
|
||||
cd ./services/ai-bot/love-agent
|
||||
pnpm install && pnpm build
|
||||
pnpm docker:build -v
|
||||
cd ./services/ai-bot/love-agent
|
||||
pnpm install && pnpm build
|
||||
pnpm docker:build -v
|
||||
env:
|
||||
DOCKER_CLI_HINTS: false
|
||||
DOCKER_EXTRA: --platform=linux/amd64,linux/arm64
|
||||
@@ -590,7 +629,7 @@ jobs:
|
||||
- name: Model version from git tags
|
||||
run: node common/scripts/install-run-rush.js model-version
|
||||
- name: Package
|
||||
run: node common/scripts/install-run-rush.js package --to desktop -p 20 -v
|
||||
run: node common/scripts/install-run-rush.js package --to desktop -v
|
||||
- name: Package JSON
|
||||
run: |
|
||||
cd desktop-package
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ temp/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
tests/sanity/screenshots
|
||||
*tests/sanity/screenshots
|
||||
|
||||
# Runtime data
|
||||
*.pid
|
||||
|
||||
Vendored
+65
@@ -70,6 +70,38 @@
|
||||
"cwd": "${workspaceRoot}/pods/server",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"name": "Debug server(Test)",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/__start.ts"],
|
||||
"env": {
|
||||
"FULLTEXT_URL": "http://localhost:4710",
|
||||
// "DB_URL": "mongodb://localhost:27018",
|
||||
// "DB_URL": "postgresql://postgres:example@localhost:5432",
|
||||
"DB_URL": "postgresql://root@host.docker.internal:26258/defaultdb?sslmode=disable",
|
||||
// "GREEN_URL": "http://host.docker.internal:6767?token=secret",
|
||||
"SERVER_PORT": "3335",
|
||||
"METRICS_CONSOLE": "false",
|
||||
"METRICS_FILE": "${workspaceRoot}/metrics.txt", // Show metrics in console evert 30 seconds.,
|
||||
"STORAGE_CONFIG": "minio|localhost?accessKey=minioadmin&secretKey=minioadmin",
|
||||
"SERVER_SECRET": "secret",
|
||||
"ENABLE_CONSOLE": "true",
|
||||
"COLLABORATOR_URL": "ws://localhost:3079",
|
||||
"FRONT_URL": "http://localhost:8083",
|
||||
"ACCOUNTS_URL": "http://localhost:3003",
|
||||
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
|
||||
"MODEL_VERSION": "0.6.435",
|
||||
"STATS_URL": "http://host.docker.internal:4901"
|
||||
},
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"runtimeVersion": "20",
|
||||
"showAsyncStacks": true,
|
||||
"outputCapture": "std",
|
||||
"sourceMaps": true,
|
||||
"cwd": "${workspaceRoot}/pods/server",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"name": "Debug Fulltext",
|
||||
"type": "node",
|
||||
@@ -131,6 +163,39 @@
|
||||
"cwd": "${workspaceRoot}/pods/account",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"name": "Debug Account(Staging)",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/__start.ts"],
|
||||
"env": {
|
||||
"MONGO_URL": "mongodb://localhost:27018",
|
||||
"DB_URL": "mongodb://localhost:27018",
|
||||
// "DB_URL": "postgresql://postgres:example@localhost:5432",
|
||||
"SERVER_SECRET": "secret",
|
||||
"REGION_INFO":"|Mongo;pg|Postgres;cockroach|CockroachDB",
|
||||
"TRANSACTOR_URL": "ws://host.docker.internal:3334;;,ws://host.docker.internal:3335;;europe",
|
||||
"ACCOUNTS_URL": "http://localhost:3003",
|
||||
"ACCOUNT_PORT": "3003",
|
||||
"FRONT_URL": "http://localhost:8083",
|
||||
"STATS_URL": "http://host.docker.internal:4901",
|
||||
"SES_URL": "",
|
||||
// "DB_NS": "account-2",
|
||||
// "WS_LIVENESS_DAYS": "1",
|
||||
"MINIO_ACCESS_KEY": "minioadmin",
|
||||
"MINIO_SECRET_KEY": "minioadmin",
|
||||
"MINIO_ENDPOINT": "localhost"
|
||||
// "DISABLE_SIGNUP": "true",
|
||||
// "INIT_SCRIPT_URL": "https://raw.githubusercontent.com/hcengineering/init/main/script.yaml",
|
||||
// "INIT_WORKSPACE": "onboarding",
|
||||
},
|
||||
"runtimeVersion": "20",
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"sourceMaps": true,
|
||||
"outputCapture": "std",
|
||||
"cwd": "${workspaceRoot}/pods/account",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"name": "Debug Stats",
|
||||
"type": "node",
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
"summary": "Format changed projects",
|
||||
"description": "Format and autofix linting issues in changed projects",
|
||||
"safeForSimultaneousRushProcesses": true,
|
||||
"shellCommand": "./common/scripts/each-diff.sh rushx format --force"
|
||||
"shellCommand": "./common/scripts/fast-format.sh"
|
||||
},
|
||||
{
|
||||
"commandKind": "global",
|
||||
@@ -354,6 +354,16 @@
|
||||
"shortName": "-f",
|
||||
"description": "Force formatting",
|
||||
"associatedCommands": ["format"]
|
||||
},
|
||||
{
|
||||
"parameterKind": "string",
|
||||
"argumentName": "BRANCH",
|
||||
"required": false,
|
||||
"associatedPhases": [],
|
||||
"shortName": "-b",
|
||||
"longName": "--branch",
|
||||
"description": "Force formatting of branch",
|
||||
"associatedCommands": ["fast-format"]
|
||||
}
|
||||
// {
|
||||
// /**
|
||||
|
||||
Generated
+29
@@ -1084,6 +1084,9 @@ importers:
|
||||
'@rush-temp/tests-sanity':
|
||||
specifier: file:./projects/tests-sanity.tgz
|
||||
version: file:projects/tests-sanity.tgz
|
||||
'@rush-temp/tests-ws-sanity':
|
||||
specifier: file:./projects/tests-ws-sanity.tgz
|
||||
version: file:projects/tests-ws-sanity.tgz
|
||||
'@rush-temp/text':
|
||||
specifier: file:./projects/text.tgz
|
||||
version: file:projects/text.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@20.11.19)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(esbuild@0.24.2)(prosemirror-inputrules@1.4.0)(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))(utf-8-validate@6.0.4)
|
||||
@@ -5156,6 +5159,10 @@ packages:
|
||||
resolution: {integrity: sha512-noV3nlBP0OvM6i4C5YUevkhltHxG/2bct4pusP3O3AEQ7Za+A4qwzpsE08pHfA9f9pedEG677GE4EmG9x61rVQ==, tarball: file:projects/tests-sanity.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/tests-ws-sanity@file:projects/tests-ws-sanity.tgz':
|
||||
resolution: {integrity: sha512-2y+gKxse3ozzKeoi1TF7Qfv7Y1fWy9Z6HiqSsEeQgGPpv396cH7ZfpSJ/qwsxO66c6iq8LUuBtzZ4AlquHfG3w==, tarball: file:projects/tests-ws-sanity.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/text-core@file:projects/text-core.tgz':
|
||||
resolution: {integrity: sha512-sQ1C/LTDJ6RoC7YHYOCrRGsOHerkeumxp9lRjRaTmjc65mAFtiizz4eYFlkbSVSQvahSdD5KMT1G+wvtmLq7Gg==, tarball: file:projects/text-core.tgz}
|
||||
version: 0.0.0
|
||||
@@ -24844,6 +24851,28 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rush-temp/tests-ws-sanity@file:projects/tests-ws-sanity.tgz':
|
||||
dependencies:
|
||||
'@faker-js/faker': 8.4.1
|
||||
'@playwright/test': 1.49.1
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 20.11.19
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.7.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.7.3)
|
||||
allure-js-commons: 3.0.7(allure-playwright@3.0.7(@playwright/test@1.49.1))
|
||||
allure-playwright: 3.0.7(@playwright/test@1.49.1)
|
||||
cross-env: 7.0.3
|
||||
dotenv: 16.0.3
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint-plugin-n@15.7.0(eslint@8.56.0))(eslint-plugin-promise@6.1.1(eslint@8.56.0))(eslint@8.56.0)(typescript@5.7.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
prettier: 3.2.5
|
||||
typescript: 5.7.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rush-temp/text-core@file:projects/text-core.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@20.11.19)(babel-jest@29.7.0(@babel/core@7.23.9))(bufferutil@4.0.8)(esbuild@0.24.2)(ts-node@10.9.2(@types/node@20.11.19)(typescript@5.3.3))(utf-8-validate@6.0.4)':
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
FILES=$(git diff origin/develop --name-only --diff-filter=ACMR | sed 's| |\\ |g')
|
||||
branch=${BASE_BRANCH:-develop}
|
||||
|
||||
FILES=$(git diff origin/$branch --name-only --diff-filter=ACMR | sed 's| |\\ |g')
|
||||
[ -z "$FILES" ] && exit 0
|
||||
|
||||
roots=$(rush list -p --json | grep "path" | cut -f 2 -d ':' | cut -f 2 -d '"')
|
||||
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
echo $@
|
||||
BASE_BRANCH=${1:-develop}
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
--branch) BASE_BRANCH="$2"; shift ;;
|
||||
*) ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
export BASE_BRANCH
|
||||
./common/scripts/each-diff.sh rushx format --force
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hcengineering/desktop",
|
||||
"version": "0.6.271",
|
||||
"version": "0.6.435",
|
||||
"main": "dist/main/electron.js",
|
||||
"template": "@hcengineering/webpack-package",
|
||||
"scripts": {
|
||||
|
||||
@@ -42,8 +42,6 @@ async function doBackup (dirName: string, token: string, endpoint: string, works
|
||||
ctx.info('do backup', { workspace, endpoint })
|
||||
await backup(ctx, endpoint, wsid, storage, {
|
||||
force: true,
|
||||
freshBackup: false,
|
||||
clean: false,
|
||||
skipDomains: [],
|
||||
timeout: 0,
|
||||
connectTimeout: 60 * 1000,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"analyze": "rm -rf ./dist && cross-env NODE_ENV=production webpack --profile --json > stats.json",
|
||||
"show": "webpack-bundle-analyzer stats.json dist",
|
||||
"dev-server": "cross-env USE_CACHE=false CLIENT_TYPE=dev-server webpack serve",
|
||||
"dev-server-test": "cross-env USE_CACHE=false CLIENT_TYPE=dev-server-test webpack serve",
|
||||
"dev-worker": "cross-env USE_CACHE=false CLIENT_TYPE=dev-worker webpack serve",
|
||||
"dev-worker-local": "cross-env USE_CACHE=false CLIENT_TYPE=dev-worker-local webpack serve",
|
||||
"dev-server-cache": "cross-env USE_CACHE=true CLIENT_TYPE=dev-server webpack serve",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"ACCOUNTS_URL": "/account",
|
||||
"COLLABORATOR_URL": "ws://localhost:3079",
|
||||
"UPLOAD_URL": "/files",
|
||||
"TELEGRAM_URL": "http://localhost:8088",
|
||||
"GMAIL_URL": "http://localhost:8089",
|
||||
"CALENDAR_URL": "http://localhost:8096",
|
||||
"REKONI_URL": "/rekoni",
|
||||
"GITHUB_APP": "uberflow-dev",
|
||||
"GITHUB_CLIENTID": "Iv1.43f9cac43bd68617",
|
||||
"GITHUB_URL": "http://localhost:3501",
|
||||
"LAST_NAME_FIRST": "true",
|
||||
"PRINT_URL": "http://localhost:4006",
|
||||
"SIGN_URL": "http://localhost:4006",
|
||||
"ANALYTICS_COLLECTOR_URL": "http://localhost:4018",
|
||||
"AI_URL": "http://localhost:4011",
|
||||
"BRANDING_URL": "/branding.json",
|
||||
"VERSION": null,
|
||||
"MODEL_VERSION": null,
|
||||
"STATS_URL": "http://localhost:4901",
|
||||
"PASSWORD_STRICTNESS": "none"
|
||||
}
|
||||
@@ -196,6 +196,7 @@ const configs: Record<string, string> = {
|
||||
'dev-huly': '/config-huly.json',
|
||||
'dev-bold': '/config.json',
|
||||
'dev-server': '/config.json',
|
||||
'dev-server-test': '/config-test.json',
|
||||
'dev-worker': '/config-worker.json',
|
||||
'dev-worker-local': '/config-worker-local.json'
|
||||
}
|
||||
|
||||
@@ -24,13 +24,14 @@ const mode = process.env.NODE_ENV || 'development'
|
||||
const prod = mode === 'production'
|
||||
const clientType = process.env.CLIENT_TYPE ?? ''
|
||||
const devServer = clientType === 'dev-server'
|
||||
const devServerTest = clientType === 'dev-server-test'
|
||||
const devServerWorker = clientType === 'dev-worker'
|
||||
const devServerWorkerLocal = clientType === 'dev-worker-local'
|
||||
const devProduction = clientType === 'dev-production'
|
||||
const devProductionHuly = clientType === 'dev-huly'
|
||||
const devProductionBold = clientType === 'dev-bold'
|
||||
const dev =
|
||||
(process.env.CLIENT_TYPE ?? '') === 'dev' || devServer || devProduction || devProductionHuly || devProductionBold || devServerWorker || devServerWorkerLocal
|
||||
(process.env.CLIENT_TYPE ?? '') === 'dev' || devServer || devProduction || devProductionHuly || devProductionBold || devServerWorker || devServerWorkerLocal || devServerTest
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
|
||||
|
||||
const { EsbuildPlugin } = require('esbuild-loader')
|
||||
@@ -71,6 +72,36 @@ const devProxy = {
|
||||
}
|
||||
}
|
||||
|
||||
const devProxyTest = {
|
||||
'/account': {
|
||||
target: 'http://localhost:3003',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/account': '' },
|
||||
logLevel: 'debug'
|
||||
},
|
||||
'/files': {
|
||||
target: 'http://localhost:8083',
|
||||
changeOrigin: true,
|
||||
logLevel: 'debug'
|
||||
},
|
||||
'/api/v1': {
|
||||
target: 'http://localhost:8083',
|
||||
changeOrigin: true,
|
||||
logLevel: 'debug'
|
||||
},
|
||||
'/import': {
|
||||
target: 'http://localhost:8083',
|
||||
changeOrigin: true,
|
||||
logLevel: 'debug'
|
||||
},
|
||||
'/rekoni/recognize': {
|
||||
target: 'http://localhost:4004',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^/rekoni/recognize': '/recognize' },
|
||||
logLevel: 'debug'
|
||||
}
|
||||
}
|
||||
|
||||
const devHulyProxy = {
|
||||
'/account': {
|
||||
target: 'https://account.huly.app/',
|
||||
@@ -148,6 +179,7 @@ const proxy = {
|
||||
'dev-worker': devProxy,
|
||||
'dev-worker-local': devProxy,
|
||||
'dev-server': devProxy,
|
||||
'dev-server-test': devProxyTest,
|
||||
'dev-production': devFrontProxy,
|
||||
'dev-bold': devBoldProxy,
|
||||
'dev-huly': devHulyProxy
|
||||
|
||||
@@ -662,8 +662,6 @@ export function devTool (
|
||||
})
|
||||
},
|
||||
cmd.region,
|
||||
true,
|
||||
true,
|
||||
5000, // 5 gigabytes per blob
|
||||
sharedPipelineContextVars,
|
||||
async (storage, workspaceStorage) => {
|
||||
@@ -734,8 +732,6 @@ export function devTool (
|
||||
})
|
||||
},
|
||||
cmd.region,
|
||||
false,
|
||||
false,
|
||||
100,
|
||||
sharedPipelineContextVars
|
||||
)
|
||||
@@ -932,8 +928,6 @@ export function devTool (
|
||||
)
|
||||
.option('-bl, --blobLimit <blobLimit>', 'A blob size limit in megabytes (default 15mb)', '15')
|
||||
.option('-f, --force', 'Force backup', false)
|
||||
.option('-f, --fresh', 'Force fresh backup', false)
|
||||
.option('-c, --clean', 'Force clean of old backup files, only with fresh backup option', false)
|
||||
.option('-t, --timeout <timeout>', 'Connect timeout in seconds', '30')
|
||||
.action(
|
||||
async (
|
||||
@@ -942,8 +936,6 @@ export function devTool (
|
||||
cmd: {
|
||||
skip: string
|
||||
force: boolean
|
||||
fresh: boolean
|
||||
clean: boolean
|
||||
timeout: string
|
||||
include: string
|
||||
blobLimit: string
|
||||
@@ -955,8 +947,6 @@ export function devTool (
|
||||
const endpoint = await getTransactorEndpoint(generateToken(systemAccountEmail, wsid), 'external')
|
||||
await backup(toolCtx, endpoint, wsid, storage, {
|
||||
force: cmd.force,
|
||||
freshBackup: cmd.fresh,
|
||||
clean: cmd.clean,
|
||||
include: cmd.include === '*' ? undefined : new Set(cmd.include.split(';').map((it) => it.trim())),
|
||||
skipDomains: (cmd.skip ?? '').split(';').map((it) => it.trim()),
|
||||
timeout: 0,
|
||||
|
||||
@@ -150,6 +150,7 @@ export default plugin(platformId, {
|
||||
AccountNotConfirmed: '' as StatusCode<{ account: string }>,
|
||||
WorkspaceNotFound: '' as StatusCode<{ workspace: string }>,
|
||||
WorkspaceArchived: '' as StatusCode<{ workspace: string }>,
|
||||
WorkspaceMigration: '' as StatusCode<{ workspace: string }>,
|
||||
InvalidPassword: '' as StatusCode<{ account: string }>,
|
||||
AccountAlreadyExists: '' as StatusCode<{ account: string }>,
|
||||
AccountAlreadyConfirmed: '' as StatusCode<{ account: string }>,
|
||||
|
||||
@@ -281,6 +281,9 @@ class Connection implements ClientConnection {
|
||||
if (resp.error?.code === platform.status.WorkspaceArchived) {
|
||||
this.opt?.onArchived?.()
|
||||
}
|
||||
if (resp.error?.code === platform.status.WorkspaceMigration) {
|
||||
this.opt?.onMigration?.()
|
||||
}
|
||||
}
|
||||
|
||||
if (resp.id !== undefined) {
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface ClientFactoryOptions {
|
||||
onUpgrade?: () => void
|
||||
onUnauthorized?: () => void
|
||||
onArchived?: () => void
|
||||
onMigration?: () => void
|
||||
onConnect?: (event: ClientConnectEvent, lastTx: string | undefined, data: any) => Promise<void>
|
||||
ctx?: MeasureContext
|
||||
onDialTimeout?: () => void | Promise<void>
|
||||
|
||||
@@ -192,13 +192,19 @@
|
||||
|
||||
let selectedRegionId: string = ''
|
||||
void getRegionInfo().then((_regionInfo) => {
|
||||
regionInfo = _regionInfo?.filter((it) => it.name !== '') ?? []
|
||||
regionInfo = _regionInfo ?? []
|
||||
if (selectedRegionId === '' && regionInfo.length > 0) {
|
||||
selectedRegionId = regionInfo[0].region
|
||||
}
|
||||
})
|
||||
|
||||
$: selectedRegionName = regionInfo.find((it) => it.region === selectedRegionId)?.name
|
||||
$: selectedRegionRef = regionInfo.find((it) => it.region === selectedRegionId)
|
||||
$: selectedRegionName =
|
||||
selectedRegionRef !== undefined
|
||||
? selectedRegionRef.name.length > 0
|
||||
? selectedRegionRef.name
|
||||
: selectedRegionRef.region
|
||||
: ''
|
||||
|
||||
$: byVersion = groupByArray(
|
||||
workspaces.filter((it) => {
|
||||
@@ -277,8 +283,11 @@
|
||||
<ButtonMenu
|
||||
selected={selectedRegionId}
|
||||
autoSelectionIfOne
|
||||
title={regionInfo.find((it) => it.region === selectedRegionId)?.name}
|
||||
items={regionInfo.map((it) => ({ id: it.region === '' ? '#' : it.region, label: getEmbeddedLabel(it.name) }))}
|
||||
title={selectedRegionName}
|
||||
items={regionInfo.map((it) => ({
|
||||
id: it.region === '' ? '#' : it.region,
|
||||
label: getEmbeddedLabel(it.name.length > 0 ? it.name : it.region + ' (hidden)')
|
||||
}))}
|
||||
on:selected={(it) => {
|
||||
selectedRegionId = it.detail === '#' ? '' : it.detail
|
||||
}}
|
||||
@@ -290,12 +299,13 @@
|
||||
{#each Object.keys(dayRanges) as k}
|
||||
{@const v = groupped.get(k) ?? []}
|
||||
{@const hasMore = (groupped.get(k) ?? []).length > limit}
|
||||
{@const activeV = v.filter((it) => it.mode === 'active' && (it.region ?? '') !== selectedRegionId)}
|
||||
{@const archivedV = v.filter((it) => it.mode === 'archived')}
|
||||
{@const deletedV = v.filter((it) => it.mode === 'deleted')}
|
||||
{@const av = v.length - archivedV.length - deletedV.length}
|
||||
{@const activeV = v.filter((it) => isActiveMode(it.mode) && it.region !== selectedRegionId)}
|
||||
{@const activeAll = v.filter((it) => isActiveMode(it.mode))}
|
||||
{@const archivedV = v.filter((it) => isArchivingMode(it.mode))}
|
||||
{@const deletedV = v.filter((it) => isDeletingMode(it.mode))}
|
||||
{@const maintenance = v.length - activeAll.length - archivedV.length - deletedV.length}
|
||||
{#if v.length > 0}
|
||||
<Expandable expandable={true} bordered={true}>
|
||||
<Expandable expandable={true} bordered={true} expanded={search.trim().length > 0}>
|
||||
<svelte:fragment slot="title">
|
||||
<span class="fs-title focused-button flex-row-center">
|
||||
{k} -
|
||||
@@ -304,8 +314,8 @@
|
||||
{:else}
|
||||
{v.length}
|
||||
{/if}
|
||||
{#if av > 0}
|
||||
- maitenance: {av}
|
||||
{#if maintenance > 0}
|
||||
- maitenance: {maintenance}
|
||||
{/if}
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
@@ -323,18 +333,18 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="tools">
|
||||
{#if archivedV.length > 0}
|
||||
{#if activeAll.length > 0}
|
||||
<Button
|
||||
icon={IconStop}
|
||||
label={getEmbeddedLabel(`Mass Archive ${archivedV.length}`)}
|
||||
label={getEmbeddedLabel(`Mass Archive ${activeAll.length}`)}
|
||||
kind={'ghost'}
|
||||
on:click={() => {
|
||||
showPopup(MessageBox, {
|
||||
label: getEmbeddedLabel(`Mass Archive ${archivedV.length}`),
|
||||
message: getEmbeddedLabel(`Please confirm archive ${archivedV.length} workspaces`),
|
||||
label: getEmbeddedLabel(`Mass Archive ${activeAll.length}`),
|
||||
message: getEmbeddedLabel(`Please confirm archive ${activeAll.length} workspaces`),
|
||||
action: async () => {
|
||||
void performWorkspaceOperation(
|
||||
archivedV.map((it) => it.workspace),
|
||||
activeAll.map((it) => it.workspace),
|
||||
'archive'
|
||||
)
|
||||
}
|
||||
@@ -350,8 +360,8 @@
|
||||
label={getEmbeddedLabel(`Mass Migrate ${activeV.length} to ${selectedRegionName ?? ''}`)}
|
||||
on:click={() => {
|
||||
showPopup(MessageBox, {
|
||||
label: getEmbeddedLabel(`Mass Migrate ${archivedV.length}`),
|
||||
message: getEmbeddedLabel(`Please confirm migrate ${archivedV.length} workspaces`),
|
||||
label: getEmbeddedLabel(`Mass Migrate ${activeV.length}`),
|
||||
message: getEmbeddedLabel(`Please confirm migrate ${activeV.length} workspaces`),
|
||||
action: async () => {
|
||||
await performWorkspaceOperation(
|
||||
activeV.map((it) => it.workspace),
|
||||
@@ -370,7 +380,7 @@
|
||||
{@const bIdx = backupIdx.get(workspace.workspace)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="flex fs-title cursor-pointer focused-button bordered">
|
||||
<div class="flex fs-title cursor-pointer focused-button bordered" id={`${workspace.workspace}`}>
|
||||
<div class="flex p-2">
|
||||
<span class="label overflow-label flex-row-center" style:width={'12rem'}>
|
||||
{wsName}
|
||||
@@ -400,9 +410,6 @@
|
||||
{workspace.attempts}
|
||||
</span>
|
||||
|
||||
<!-- <span class="flex flex-between select-text overflow-label" style:width={'25rem'}>
|
||||
{workspace.workspace}
|
||||
</span> -->
|
||||
<span class="flex flex-between" style:width={'5rem'}>
|
||||
{#if workspace.progress !== 100 && workspace.progress !== 0}
|
||||
({workspace.progress}%)
|
||||
@@ -474,13 +481,12 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if regionInfo.length > 0 && workspace.mode === 'active' && (workspace.region ?? '') !== selectedRegionId}
|
||||
<Button
|
||||
icon={IconArrowRight}
|
||||
size={'small'}
|
||||
kind={'positive'}
|
||||
label={getEmbeddedLabel('Migrate ' + (selectedRegionName ?? ''))}
|
||||
label={getEmbeddedLabel('Migrate')}
|
||||
on:click={() => {
|
||||
showPopup(MessageBox, {
|
||||
label: getEmbeddedLabel(`Migrate ${workspace.workspaceUrl}`),
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "Otevřít v postranním panelu na nové kartě",
|
||||
"ConfigureWidgets": "Nastavit widgety",
|
||||
"Tab": "Karta",
|
||||
"WorkspaceIsArchived": "Pracovní prostor je archivován kvůli nečinnosti. Kontaktujte nás prosím pro obnovení..."
|
||||
"WorkspaceIsArchived": "Pracovní prostor je archivován kvůli nečinnosti. Kontaktujte nás prosím pro obnovení...",
|
||||
"WorkspaceIsMigrating": "Pracovní prostor je aktualizován. Prosím čekejte..."
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "In neuer Seitenleiste öffnen",
|
||||
"ConfigureWidgets": "Widgets konfigurieren",
|
||||
"Tab": "Tab",
|
||||
"WorkspaceIsArchived": "Workspace wurde wegen Inaktivität archiviert. Bitte kontaktieren Sie uns zur Wiederherstellung..."
|
||||
"WorkspaceIsArchived": "Workspace wurde wegen Inaktivität archiviert. Bitte kontaktieren Sie uns zur Wiederherstellung...",
|
||||
"WorkspaceIsMigrating": "Workspace wird aktualisiert. Bitte warten..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "Open in sidebar new tab",
|
||||
"ConfigureWidgets": "Configure widgets",
|
||||
"Tab": "Tab",
|
||||
"WorkspaceIsArchived": "Workspace is archived because of being unused, Please contact us to restore..."
|
||||
"WorkspaceIsArchived": "Workspace is archived because of being unused, Please contact us to restore...",
|
||||
"WorkspaceIsMigrating": "Workspace is being updated. Please wait..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "Abrir en una nueva pestaña de la barra lateral",
|
||||
"ConfigureWidgets": "Configurar widgets",
|
||||
"Tab": "Pestaña",
|
||||
"WorkspaceIsArchived": "El espacio de trabajo está archivado por no estar en uso, por favor contáctenos para restaurarlo..."
|
||||
"WorkspaceIsArchived": "El espacio de trabajo está archivado por no estar en uso, por favor contáctenos para restaurarlo...",
|
||||
"WorkspaceIsMigrating": "El espacio de trabajo se está actualizando. Por favor, espere..."
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "Ouvrir dans un nouvel onglet de la barre latérale",
|
||||
"ConfigureWidgets": "Configurer les widgets",
|
||||
"Tab": "Onglet",
|
||||
"WorkspaceIsArchived": "L'espace de travail est archivé en raison de son inactivité, veuillez nous contacter pour le restaurer..."
|
||||
"WorkspaceIsArchived": "L'espace de travail est archivé en raison de son inactivité, veuillez nous contacter pour le restaurer...",
|
||||
"WorkspaceIsMigrating": "L'espace de travail est en cours de mise à jour. Veuillez patienter..."
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
"MobileNotSupported": "Spiacenti, il supporto per i dispositivi mobili arriverà presto. Nel frattempo, si prega di utilizzare un computer desktop",
|
||||
"LogInAnyway": "Accedi comunque",
|
||||
"WorkspaceCreating": "Creazione in corso...",
|
||||
"AccessDenied": "L'oggetto non esiste o non hai autorizzazione per accedervi."
|
||||
"AccessDenied": "L'oggetto non esiste o non hai autorizzazione per accedervi.",
|
||||
"WorkspaceIsMigrating": "Il workspace è in fase di aggiornamento. Attendi..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "Abrir em uma nova aba da barra lateral",
|
||||
"ConfigureWidgets": "Configurar widgets",
|
||||
"Tab": "Aba",
|
||||
"WorkspaceIsArchived": "O espaço de trabalho está arquivado por estar inativo, por favor, entre em contato conosco para restaurá-lo..."
|
||||
"WorkspaceIsArchived": "O espaço de trabalho está arquivado por estar inativo, por favor, entre em contato conosco para restaurá-lo...",
|
||||
"WorkspaceIsMigrating": "O espaço de trabalho está sendo atualizado. Por favor, aguarde..."
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "Открыть в новой вкладке боковой панели",
|
||||
"ConfigureWidgets": "Настроить виджеты",
|
||||
"Tab": "Вкладка",
|
||||
"WorkspaceIsArchived": "Рабочее пространство архивировано из-за неиспользования, пожалуйста, свяжитесь с нами для восстановления..."
|
||||
"WorkspaceIsArchived": "Рабочее пространство архивировано из-за неиспользования, пожалуйста, свяжитесь с нами для восстановления...",
|
||||
"WorkspaceIsMigrating": "Рабочее пространство обновляется. Пожалуйста, подождите..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"OpenInSidebarNewTab": "在侧边栏新标签页中打开",
|
||||
"ConfigureWidgets": "配置小部件",
|
||||
"Tab": "选项卡",
|
||||
"WorkspaceIsArchived": "工作区因未使用而归档,请与我们联系以恢复..."
|
||||
"WorkspaceIsArchived": "工作区因未使用而归档,请与我们联系以恢复...",
|
||||
"WorkspaceIsMigrating": "工作区正在更新。请稍候..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,9 +213,9 @@
|
||||
<div class="p-3">2.</div>
|
||||
<Button
|
||||
icon={IconArrowRight}
|
||||
label={getEmbeddedLabel('Reboot server')}
|
||||
label={getEmbeddedLabel('Reboot workspace')}
|
||||
on:click={() => {
|
||||
void fetch(endpoint + `/api/v1/manage?token=${token}&operation=reboot`, {
|
||||
void fetch(endpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||
method: 'PUT'
|
||||
})
|
||||
}}
|
||||
|
||||
@@ -209,6 +209,17 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
onArchived: () => {
|
||||
translateCB(plugin.string.WorkspaceIsArchived, {}, get(themeStore).language, (r) => {
|
||||
versionError.set(r)
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 5000)
|
||||
})
|
||||
},
|
||||
onMigration: () => {
|
||||
translateCB(plugin.string.WorkspaceIsMigrating, {}, get(themeStore).language, (r) => {
|
||||
versionError.set(r)
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 5000)
|
||||
})
|
||||
},
|
||||
// We need to refresh all active live queries and clear old queries.
|
||||
|
||||
@@ -255,7 +255,8 @@ export default plugin(workbenchId, {
|
||||
OpenInSidebar: '' as IntlString,
|
||||
OpenInSidebarNewTab: '' as IntlString,
|
||||
ConfigureWidgets: '' as IntlString,
|
||||
WorkspaceIsArchived: '' as IntlString
|
||||
WorkspaceIsArchived: '' as IntlString,
|
||||
WorkspaceIsMigrating: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Search: '' as Asset
|
||||
|
||||
@@ -1135,6 +1135,11 @@
|
||||
"projectFolder": "tests/sanity",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/tests-ws-sanity",
|
||||
"projectFolder": "ws-tests/sanity",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/qms-tests-sanity",
|
||||
"projectFolder": "qms-tests/sanity",
|
||||
|
||||
@@ -32,7 +32,6 @@ import core, {
|
||||
getWorkspaceId,
|
||||
isActiveMode,
|
||||
isArchivingMode,
|
||||
isMigrationMode,
|
||||
isWorkspaceCreating,
|
||||
MeasureContext,
|
||||
RateLimiter,
|
||||
@@ -503,18 +502,6 @@ export async function selectWorkspace (
|
||||
}
|
||||
|
||||
if (workspaceInfo !== null) {
|
||||
if (isArchivingMode(workspaceInfo.mode) || isMigrationMode(workspaceInfo.mode)) {
|
||||
const result: WorkspaceLoginInfo = {
|
||||
endpoint: '',
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId(workspaceInfo.workspace), getExtra(accountInfo)),
|
||||
workspace: workspaceUrl,
|
||||
workspaceId: workspaceInfo.workspace,
|
||||
mode: workspaceInfo.mode,
|
||||
progress: workspaceInfo.progress
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (workspaceInfo.disabled === true && isActiveMode(workspaceInfo.mode)) {
|
||||
ctx.error('workspace disabled', { workspaceUrl, email })
|
||||
throw new PlatformError(
|
||||
@@ -1594,7 +1581,9 @@ export async function createUserWorkspace (
|
||||
endpoint: getEndpoint(ctx, workspaceInfo, EndpointKind.External),
|
||||
email,
|
||||
token: generateToken(email, getWorkspaceId(workspaceInfo.workspace), getExtra(userAccount)),
|
||||
workspace: workspaceInfo.workspaceUrl
|
||||
workspace: workspaceInfo.workspaceUrl,
|
||||
workspaceId: workspaceInfo.workspace,
|
||||
workspaceName: workspaceInfo.workspaceName
|
||||
}
|
||||
ctx.info('Creating user side done', { workspaceName, email })
|
||||
return result
|
||||
@@ -2723,8 +2712,12 @@ export async function joinWithProvider (
|
||||
|
||||
const token = generateToken(email, getWorkspaceId(''), getExtra(account))
|
||||
const ws = await getWorkspaceById(db, workspace.name)
|
||||
|
||||
if (ws != null && ws.accounts.includes(account._id)) {
|
||||
if (ws == null) {
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspace.name })
|
||||
)
|
||||
}
|
||||
if (ws.accounts.includes(account._id)) {
|
||||
const result = {
|
||||
endpoint: getEndpoint(ctx, ws, EndpointKind.External),
|
||||
email,
|
||||
|
||||
@@ -93,8 +93,6 @@ export async function backupWorkspace (
|
||||
externalStorage: StorageAdapter
|
||||
) => DbConfiguration,
|
||||
region: string,
|
||||
freshBackup: boolean = false,
|
||||
clean: boolean = false,
|
||||
downloadLimit: number,
|
||||
contextVars: Record<string, any>,
|
||||
|
||||
@@ -130,8 +128,6 @@ export async function backupWorkspace (
|
||||
return getConfig(ctx, mainDbUrl, workspace, branding, externalStorage)
|
||||
},
|
||||
region,
|
||||
freshBackup,
|
||||
clean,
|
||||
downloadLimit,
|
||||
[],
|
||||
contextVars
|
||||
|
||||
@@ -33,7 +33,6 @@ import core, {
|
||||
SortingOrder,
|
||||
systemAccountEmail,
|
||||
toIdMap,
|
||||
toWorkspaceString,
|
||||
TxProcessor,
|
||||
WorkspaceId,
|
||||
type BackupStatus,
|
||||
@@ -671,8 +670,6 @@ export async function backup (
|
||||
include?: Set<string>
|
||||
skipDomains: string[]
|
||||
force: boolean
|
||||
freshBackup: boolean // If passed as true, will download all documents except blobs as new backup
|
||||
clean: boolean // If set will perform a clena of old backup files
|
||||
timeout: number
|
||||
connectTimeout: number
|
||||
skipBlobContentTypes: string[]
|
||||
@@ -686,8 +683,6 @@ export async function backup (
|
||||
token?: string
|
||||
} = {
|
||||
force: false,
|
||||
freshBackup: false,
|
||||
clean: false,
|
||||
timeout: 0,
|
||||
skipDomains: [],
|
||||
connectTimeout: 30000,
|
||||
@@ -704,7 +699,6 @@ export async function backup (
|
||||
ctx = ctx.newChild('backup', {
|
||||
workspaceId: workspaceId.name,
|
||||
force: options.force,
|
||||
recheck: options.freshBackup,
|
||||
timeout: options.timeout
|
||||
})
|
||||
|
||||
@@ -760,7 +754,7 @@ export async function backup (
|
||||
|
||||
let lastTxChecked = false
|
||||
// Skip backup if there is no transaction changes.
|
||||
if (options.getLastTx !== undefined && !options.freshBackup) {
|
||||
if (options.getLastTx !== undefined) {
|
||||
lastTx = await options.getLastTx()
|
||||
if (lastTx !== undefined) {
|
||||
if (lastTx._id === backupInfo.lastTxId && !options.force) {
|
||||
@@ -785,7 +779,7 @@ export async function backup (
|
||||
? await options.getConnection()
|
||||
: ((await createClient(transactorUrl, token, undefined, options.connectTimeout)) as CoreClient & BackupClient)
|
||||
|
||||
if (!lastTxChecked && !options.freshBackup) {
|
||||
if (!lastTxChecked) {
|
||||
lastTx = await connection.findOne(
|
||||
core.class.Tx,
|
||||
{ objectSpace: { $ne: core.space.Model } },
|
||||
@@ -924,7 +918,7 @@ export async function backup (
|
||||
if (digest.delete(id as Ref<Doc>)) {
|
||||
oldHash.set(id as Ref<Doc>, currentHash)
|
||||
}
|
||||
if (currentHash !== serverDocHash || (options.freshBackup && domain !== DOMAIN_BLOB)) {
|
||||
if (currentHash !== serverDocHash) {
|
||||
if (changes.updated.has(id as Ref<Doc>)) {
|
||||
removeFromNeedRetrieve(needRetrieve, id as Ref<Doc>)
|
||||
}
|
||||
@@ -1347,36 +1341,6 @@ export async function backup (
|
||||
if (!canceled()) {
|
||||
backupInfo.lastTxId = lastTx?._id ?? '0' // We could store last tx, since full backup is complete
|
||||
await storage.writeFile(infoFile, gzipSync(JSON.stringify(backupInfo, undefined, 2), { level: defaultLevel }))
|
||||
|
||||
if (options.freshBackup && options.clean) {
|
||||
// Preparing a list of files to clean
|
||||
|
||||
ctx.info('Cleaning old backup files...')
|
||||
for (const sn of backupInfo.snapshots.slice(0, backupInfo.snapshots.length - 1)) {
|
||||
const filesToDelete: string[] = []
|
||||
for (const [domain, dsn] of [...Object.entries(sn.domains)]) {
|
||||
if (domain === DOMAIN_BLOB) {
|
||||
continue
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete (sn.domains as any)[domain]
|
||||
|
||||
filesToDelete.push(...(dsn.snapshots ?? []))
|
||||
filesToDelete.push(...(dsn.storage ?? []))
|
||||
if (dsn.snapshot !== undefined) {
|
||||
filesToDelete.push(dsn.snapshot)
|
||||
}
|
||||
}
|
||||
for (const file of filesToDelete) {
|
||||
ctx.info('Removing file...', { file })
|
||||
await storage.delete(file)
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete sizeInfo[file]
|
||||
}
|
||||
}
|
||||
ctx.info('Cleaning complete...')
|
||||
await storage.writeFile(infoFile, gzipSync(JSON.stringify(backupInfo, undefined, 2), { level: defaultLevel }))
|
||||
}
|
||||
}
|
||||
|
||||
const addFileSize = async (file: string | undefined | null): Promise<void> => {
|
||||
@@ -1726,12 +1690,9 @@ export async function restore (
|
||||
try {
|
||||
let serverEndpoint = await getTransactorEndpoint(token, 'external')
|
||||
serverEndpoint = serverEndpoint.replaceAll('wss://', 'https://').replace('ws://', 'http://')
|
||||
await fetch(
|
||||
serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close&wsId=${toWorkspaceString(workspaceId)}`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
)
|
||||
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||
method: 'PUT'
|
||||
})
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@@ -72,8 +72,6 @@ class BackupWorker {
|
||||
) => DbConfiguration,
|
||||
readonly region: string,
|
||||
readonly contextVars: Record<string, any>,
|
||||
readonly freshWorkspace: boolean = false,
|
||||
readonly clean: boolean = false,
|
||||
readonly skipDomains: string[] = []
|
||||
) {}
|
||||
|
||||
@@ -280,8 +278,6 @@ class BackupWorker {
|
||||
backup(ctx, '', getWorkspaceId(ws.workspace), storage, {
|
||||
skipDomains: this.skipDomains,
|
||||
force: true,
|
||||
freshBackup: this.freshWorkspace,
|
||||
clean: this.clean,
|
||||
timeout: this.config.Timeout * 1000,
|
||||
connectTimeout: 5 * 60 * 1000, // 5 minutes to,
|
||||
blobDownloadLimit: this.downloadLimit,
|
||||
@@ -406,8 +402,6 @@ export async function doBackupWorkspace (
|
||||
externalStorage: StorageAdapter
|
||||
) => DbConfiguration,
|
||||
region: string,
|
||||
freshWorkspace: boolean,
|
||||
clean: boolean,
|
||||
downloadLimit: number,
|
||||
skipDomains: string[],
|
||||
contextVars: Record<string, any>,
|
||||
@@ -421,8 +415,6 @@ export async function doBackupWorkspace (
|
||||
getConfig,
|
||||
region,
|
||||
contextVars,
|
||||
freshWorkspace,
|
||||
clean,
|
||||
skipDomains
|
||||
)
|
||||
backupWorker.downloadLimit = downloadLimit
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import {
|
||||
type Account,
|
||||
type BaseWorkspaceInfo,
|
||||
type Branding,
|
||||
type Class,
|
||||
type Doc,
|
||||
@@ -637,10 +638,21 @@ export interface AddSessionActive {
|
||||
context: MeasureContext
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export interface WorkspaceLoginInfo extends Omit<BaseWorkspaceInfo, 'workspace'> {
|
||||
upgrade?: {
|
||||
toProcess: number
|
||||
total: number
|
||||
elapsed: number
|
||||
eta: number
|
||||
}
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export type AddSessionResponse =
|
||||
| AddSessionActive
|
||||
| { upgrade: true }
|
||||
| { error: any, terminate?: boolean, archived?: boolean }
|
||||
| { upgrade: true, upgradeInfo?: WorkspaceLoginInfo['upgrade'] }
|
||||
| { error: any, terminate?: boolean, specialError?: 'archived' | 'migration' }
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
||||
@@ -27,7 +27,6 @@ import core, {
|
||||
versionToString,
|
||||
withContext,
|
||||
WorkspaceEvent,
|
||||
type BaseWorkspaceInfo,
|
||||
type Branding,
|
||||
type BrandingMap,
|
||||
type MeasureContext,
|
||||
@@ -45,10 +44,12 @@ import {
|
||||
ServerFactory,
|
||||
SessionManager,
|
||||
StorageAdapter,
|
||||
type AddSessionResponse,
|
||||
type ClientSessionCtx,
|
||||
type ConnectionSocket,
|
||||
type Session,
|
||||
type Workspace
|
||||
type Workspace,
|
||||
type WorkspaceLoginInfo
|
||||
} from '@hcengineering/server-core'
|
||||
import { generateToken, type Token } from '@hcengineering/server-token'
|
||||
|
||||
@@ -57,16 +58,6 @@ import { sendResponse } from './utils'
|
||||
const ticksPerSecond = 20
|
||||
const workspaceSoftShutdownTicks = 15 * ticksPerSecond
|
||||
|
||||
interface WorkspaceLoginInfo extends Omit<BaseWorkspaceInfo, 'workspace'> {
|
||||
upgrade?: {
|
||||
toProcess: number
|
||||
total: number
|
||||
elapsed: number
|
||||
eta: number
|
||||
}
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
function timeoutPromise (time: number): { promise: Promise<void>, cancelHandle: () => void } {
|
||||
let timer: any
|
||||
return {
|
||||
@@ -330,11 +321,7 @@ class TSessionManager implements SessionManager {
|
||||
rawToken: string,
|
||||
pipelineFactory: PipelineFactory,
|
||||
sessionId: string | undefined
|
||||
): Promise<
|
||||
| { session: Session, context: MeasureContext, workspaceId: string }
|
||||
| { upgrade: true, upgradeInfo?: WorkspaceLoginInfo['upgrade'] }
|
||||
| { error: any, terminate?: boolean, archived?: boolean }
|
||||
> {
|
||||
): Promise<AddSessionResponse> {
|
||||
const wsString = toWorkspaceString(token.workspace)
|
||||
|
||||
let workspaceInfo: WorkspaceLoginInfo | undefined
|
||||
@@ -352,15 +339,15 @@ class TSessionManager implements SessionManager {
|
||||
|
||||
if (isArchivingMode(workspaceInfo.mode)) {
|
||||
// No access to disabled workspaces for regular users
|
||||
return { error: new Error('Workspace is archived'), terminate: true, archived: true }
|
||||
return { error: new Error('Workspace is archived'), terminate: true, specialError: 'archived' }
|
||||
}
|
||||
if (isMigrationMode(workspaceInfo.mode)) {
|
||||
// No access to disabled workspaces for regular users
|
||||
return { error: new Error('Workspace is in region migration'), terminate: true, archived: false }
|
||||
return { error: new Error('Workspace is in region migration'), terminate: true, specialError: 'migration' }
|
||||
}
|
||||
if (isRestoringMode(workspaceInfo.mode)) {
|
||||
// No access to disabled workspaces for regular users
|
||||
return { error: new Error('Workspace is in backup restore'), terminate: true, archived: false }
|
||||
return { error: new Error('Workspace is in backup restore'), terminate: true, specialError: 'migration' }
|
||||
}
|
||||
|
||||
if (workspaceInfo.disabled === true && token.email !== systemAccountEmail && token.extra?.admin !== 'true') {
|
||||
@@ -806,11 +793,14 @@ class TSessionManager implements SessionManager {
|
||||
async forceClose (wsId: string, ignoreSocket?: ConnectionSocket): Promise<void> {
|
||||
const ws = this.workspaces.get(wsId)
|
||||
if (ws !== undefined) {
|
||||
this.ctx.warn('force-close', { name: ws.workspaceName })
|
||||
ws.upgrade = true // We need to similare upgrade to refresh all clients.
|
||||
ws.closing = this.closeAll(wsId, ws, 99, 'force-close', ignoreSocket)
|
||||
this.workspaces.delete(wsId)
|
||||
await ws.closing
|
||||
ws.closing = undefined
|
||||
} else {
|
||||
this.ctx.warn('force-close-unknown', { wsId })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import core, {
|
||||
MigrationState,
|
||||
ModelDb,
|
||||
systemAccountEmail,
|
||||
toWorkspaceString,
|
||||
Tx,
|
||||
TxOperations,
|
||||
WorkspaceId,
|
||||
@@ -357,12 +356,9 @@ export async function upgradeModel (
|
||||
const serverEndpoint = transactorUrl.replaceAll('wss://', 'https://').replace('ws://', 'http://')
|
||||
const token = generateToken(systemAccountEmail, workspaceId, { admin: 'true' })
|
||||
try {
|
||||
await fetch(
|
||||
serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close&wsId=${toWorkspaceString(workspaceId)}`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
)
|
||||
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||
method: 'PUT'
|
||||
})
|
||||
} catch (err: any) {
|
||||
// Ignore error if transactor is not yet ready
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type MeasureContext,
|
||||
type Tx,
|
||||
type Version,
|
||||
type WorkspaceId,
|
||||
type WorkspaceUpdateEvent,
|
||||
getBranding,
|
||||
getWorkspaceId,
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
import { type MigrateOperation, type ModelLogger } from '@hcengineering/model'
|
||||
import {
|
||||
getPendingWorkspace,
|
||||
getTransactorEndpoint,
|
||||
updateWorkspaceInfo,
|
||||
withRetryConnUntilSuccess,
|
||||
withRetryConnUntilTimeout,
|
||||
@@ -399,6 +401,20 @@ export class WorkspaceWorker {
|
||||
}
|
||||
}
|
||||
|
||||
async sendTransactorMaitenance (token: string, ws: WorkspaceId): Promise<void> {
|
||||
try {
|
||||
let serverEndpoint = await getTransactorEndpoint(token)
|
||||
serverEndpoint = serverEndpoint.replaceAll('wss://', 'https://').replace('ws://', 'http://')
|
||||
console.log('sending event', serverEndpoint, ws.name)
|
||||
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||
method: 'PUT'
|
||||
})
|
||||
} catch (err: any) {
|
||||
console.log(err)
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
private async doWorkspaceOperation (
|
||||
ctx: MeasureContext,
|
||||
workspace: BaseWorkspaceInfo,
|
||||
@@ -427,6 +443,8 @@ export class WorkspaceWorker {
|
||||
case 'archiving-pending-backup':
|
||||
case 'archiving-backup': {
|
||||
await sendEvent('archiving-backup-started', 0)
|
||||
|
||||
await this.sendTransactorMaitenance(token, { name: workspace.workspace })
|
||||
if (await this.doBackup(ctx, workspace, opt, true)) {
|
||||
await sendEvent('archiving-backup-done', 100)
|
||||
}
|
||||
@@ -449,6 +467,7 @@ export class WorkspaceWorker {
|
||||
case 'deleting': {
|
||||
// We should remove DB, not storages.
|
||||
await sendEvent('delete-started', 0)
|
||||
await this.sendTransactorMaitenance(token, { name: workspace.workspace })
|
||||
try {
|
||||
await this.doCleanup(ctx, workspace, true)
|
||||
} catch (err: any) {
|
||||
@@ -462,6 +481,7 @@ export class WorkspaceWorker {
|
||||
case 'migration-pending-backup':
|
||||
case 'migration-backup':
|
||||
await sendEvent('migrate-backup-started', 0)
|
||||
await this.sendTransactorMaitenance(token, { name: workspace.workspace })
|
||||
if (await this.doBackup(ctx, workspace, opt, false)) {
|
||||
await sendEvent('migrate-backup-done', 100)
|
||||
}
|
||||
@@ -470,6 +490,7 @@ export class WorkspaceWorker {
|
||||
case 'migration-clean': {
|
||||
// We should remove DB, not storages.
|
||||
await sendEvent('migrate-clean-started', 0)
|
||||
await this.sendTransactorMaitenance(token, { name: workspace.workspace })
|
||||
try {
|
||||
await this.doCleanup(ctx, workspace, false)
|
||||
} catch (err: any) {
|
||||
@@ -556,8 +577,6 @@ export class WorkspaceWorker {
|
||||
})
|
||||
},
|
||||
this.region,
|
||||
archive,
|
||||
archive,
|
||||
50000,
|
||||
['blob'],
|
||||
sharedPipelineContextVars,
|
||||
@@ -570,7 +589,7 @@ export class WorkspaceWorker {
|
||||
}
|
||||
)
|
||||
if (result) {
|
||||
console.log('backup completed')
|
||||
ctx.info('backup completed')
|
||||
return true
|
||||
}
|
||||
} finally {
|
||||
@@ -633,7 +652,7 @@ export class WorkspaceWorker {
|
||||
}
|
||||
)
|
||||
if (result) {
|
||||
console.log('backup completed')
|
||||
ctx.info('restore completed')
|
||||
return true
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -185,7 +185,7 @@ export function startHttpServer (
|
||||
}
|
||||
case 'force-close': {
|
||||
const wsId = req.query.wsId as string
|
||||
void sessions.forceClose(wsId)
|
||||
void sessions.forceClose(wsId ?? payload.workspace.name)
|
||||
res.writeHead(200)
|
||||
res.end()
|
||||
return
|
||||
@@ -374,7 +374,7 @@ export function startHttpServer (
|
||||
if (webSocketData.session instanceof Promise) {
|
||||
void webSocketData.session.then((s) => {
|
||||
if ('error' in s) {
|
||||
if (s.archived === true) {
|
||||
if (s.specialError === 'archived') {
|
||||
cs.send(
|
||||
ctx,
|
||||
{
|
||||
@@ -387,6 +387,19 @@ export function startHttpServer (
|
||||
false,
|
||||
false
|
||||
)
|
||||
} else if (s.specialError === 'migration') {
|
||||
cs.send(
|
||||
ctx,
|
||||
{
|
||||
id: -1,
|
||||
error: new Status(Severity.ERROR, platform.status.WorkspaceMigration, {
|
||||
workspace: token.workspace.name
|
||||
}),
|
||||
terminate: s.terminate
|
||||
},
|
||||
false,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
cs.send(
|
||||
ctx,
|
||||
|
||||
@@ -41,7 +41,15 @@ export class ApiEndpoint {
|
||||
return token
|
||||
}
|
||||
|
||||
async createWorkspaceWithLogin (workspaceName: string, username: string, password: string): Promise<any> {
|
||||
async createWorkspaceWithLogin (
|
||||
workspaceName: string,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<{
|
||||
workspace: string
|
||||
workspaceId: string
|
||||
workspaceName: string
|
||||
}> {
|
||||
const token = await this.loginAndGetToken(username, password)
|
||||
const url = this.baseUrl
|
||||
const payload = {
|
||||
@@ -50,7 +58,7 @@ export class ApiEndpoint {
|
||||
}
|
||||
const headers = this.getDefaultHeaders(token)
|
||||
const response = await this.request.post(url, { data: payload, headers })
|
||||
return await response.json()
|
||||
return (await response.json()).result
|
||||
}
|
||||
|
||||
async createAccount (username: string, password: string, firstName: string, lastName: string): Promise<any> {
|
||||
|
||||
@@ -8,6 +8,8 @@ export * from './model/select-workspace-page'
|
||||
export * from './utils'
|
||||
export * from './model/tracker/issues-page'
|
||||
export * from './model/tracker/issues-details-page'
|
||||
export * from './model/tracker/types'
|
||||
export * from './model/tracker/common-tracker-page'
|
||||
export * from './model/tracker/tracker-navigation-menu-page'
|
||||
export * from './model/profile/user-profile-page'
|
||||
export * from './API/Api'
|
||||
|
||||
@@ -29,6 +29,11 @@ export class LoginPage {
|
||||
await (await this.page.goto(`${PlatformURI}/login/login`))?.finished()
|
||||
}
|
||||
|
||||
// ACTIONS
|
||||
async gotoAdmin (): Promise<void> {
|
||||
await (await this.page.goto(`${PlatformURI}/login/admin`))?.finished()
|
||||
}
|
||||
|
||||
async clickSignUp (): Promise<void> {
|
||||
await this.linkSignUp().click()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
STORAGE_CONFIG="minio|minio?accessKey=minioadmin&secretKey=minioadmin"
|
||||
BACKUP_STORAGE_CONFIG="minio|minio?accessKey=minioadmin&secretKey=minioadmin"
|
||||
BACKUP_BUCKET_NAME=ws-dev-backups
|
||||
DB_URL=mongodb://mongodb:27018
|
||||
DB_EU_URL=postgresql://root@cockroach:26257/defaultdb?sslmode=disable
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"localhost:8083": {
|
||||
"key": "huly",
|
||||
"title": "Platform",
|
||||
"protocol": "http",
|
||||
"languages": "en,ru,pt,es,zh,fr",
|
||||
"defaultLanguage": "en",
|
||||
"defaultApplication": "tracker",
|
||||
"defaultSpace": "tracker:project:DefaultProject",
|
||||
"defaultSpecial": "issues",
|
||||
"lastNameFirst": "true",
|
||||
"links": [
|
||||
{
|
||||
"rel": "manifest",
|
||||
"href": "/platform/site.webmanifest"
|
||||
},
|
||||
{
|
||||
"rel": "icon",
|
||||
"href": "/platform/favicon.svg",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"rel": "shortcut icon",
|
||||
"href": "/platform/favicon.ico",
|
||||
"sizes": "any"
|
||||
},
|
||||
{
|
||||
"rel": "apple-touch-icon",
|
||||
"href": "/platform/icon-192.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Restore workspace contents in mongo/elastic
|
||||
rush build
|
||||
rush bundle
|
||||
rush docker:build
|
||||
|
||||
# Re-assign user to workspace.
|
||||
docker compose -p sanity up $1 -d --force-recreate
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
./tool-local.sh create-workspace sanity-ws
|
||||
|
||||
./tool-local.sh create-account user1 -f John -l Appleseed -p 1234
|
||||
./tool-local.sh create-account user2 -f Kainin -l Dirak -p 1234
|
||||
@@ -0,0 +1,264 @@
|
||||
services:
|
||||
mongodb:
|
||||
image: 'mongo:7-jammy'
|
||||
command: mongod --port 27018
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
ports:
|
||||
- 27018:27018
|
||||
restart: unless-stopped
|
||||
cockroach:
|
||||
image: cockroachdb/cockroach:latest-v24.2
|
||||
ports:
|
||||
- '26258:26257'
|
||||
command: start-single-node --insecure
|
||||
restart: unless-stopped
|
||||
minio:
|
||||
image: 'minio/minio'
|
||||
command: server /data --address ":9000" --console-address ":9001"
|
||||
expose:
|
||||
- 9000
|
||||
- 9001
|
||||
ports:
|
||||
- 9002:9000
|
||||
- 9003:9001
|
||||
elastic:
|
||||
image: 'elasticsearch:7.14.2'
|
||||
expose:
|
||||
- 9200
|
||||
ports:
|
||||
- 9201:9200
|
||||
environment:
|
||||
- ELASTICSEARCH_PORT_NUMBER=9200
|
||||
- BITNAMI_DEBUG=true
|
||||
- discovery.type=single-node
|
||||
- ES_JAVA_OPTS=-Xms1024m -Xmx1024m
|
||||
healthcheck:
|
||||
interval: 20s
|
||||
retries: 10
|
||||
test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
|
||||
account:
|
||||
image: hardcoreeng/account
|
||||
pull_policy: never
|
||||
links:
|
||||
- mongodb
|
||||
- minio
|
||||
- cockroach
|
||||
ports:
|
||||
- 3003:3003
|
||||
volumes:
|
||||
- ./branding-test.json:/var/cfg/branding.json
|
||||
environment:
|
||||
- REGION_INFO=|America;europe| # Europe without name will not be available for creation of new workspaces.
|
||||
# - REGION_INFO=|America;europe|Europe
|
||||
- ADMIN_EMAILS=admin
|
||||
- ACCOUNT_PORT=3003
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=mongodb://mongodb:27018
|
||||
- TRANSACTOR_URL=ws://transactor:3334;ws://localhost:3334,ws://transactor-europe:3335;ws://localhost:3335;europe,
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- MODEL_ENABLED=*
|
||||
- BRANDING_PATH=/var/cfg/branding.json
|
||||
- STATS_URL=http://stats:4901
|
||||
workspace:
|
||||
image: hardcoreeng/workspace
|
||||
links:
|
||||
- mongodb
|
||||
- minio
|
||||
volumes:
|
||||
- ./branding-test.json:/var/cfg/branding.json
|
||||
environment:
|
||||
- WS_OPERATION=all+backup
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=${DB_URL}
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- MODEL_ENABLED=*
|
||||
- REGION=
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- BRANDING_PATH=/var/cfg/branding.json
|
||||
- STATS_URL=http://stats:4901
|
||||
- BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG}
|
||||
- BACKUP_BUCKET=${BACKUP_BUCKET_NAME}
|
||||
restart: unless-stopped
|
||||
workspace_europe:
|
||||
image: hardcoreeng/workspace
|
||||
links:
|
||||
- cockroach
|
||||
- minio
|
||||
volumes:
|
||||
- ./branding-test.json:/var/cfg/branding.json
|
||||
environment:
|
||||
- WS_OPERATION=all+backup
|
||||
- REGION=europe
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=${DB_EU_URL}
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- MODEL_ENABLED=*
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- BRANDING_PATH=/var/cfg/branding.json
|
||||
- STATS_URL=http://stats:4901
|
||||
- BACKUP_STORAGE=${BACKUP_STORAGE_CONFIG}
|
||||
- BACKUP_BUCKET=${BACKUP_BUCKET_NAME}
|
||||
restart: unless-stopped
|
||||
front:
|
||||
image: hardcoreeng/front
|
||||
pull_policy: never
|
||||
links:
|
||||
- account
|
||||
- mongodb
|
||||
- minio
|
||||
- elastic
|
||||
- collaborator
|
||||
- transactor
|
||||
ports:
|
||||
- 8083:8083
|
||||
volumes:
|
||||
- ./branding-test.json:/app/dist/branding-test.json
|
||||
environment:
|
||||
- SERVER_PORT=8083
|
||||
- SERVER_SECRET=secret
|
||||
- ACCOUNTS_URL_INTERNAL=http://account:3003
|
||||
- ACCOUNTS_URL=http://localhost:3003
|
||||
- UPLOAD_URL=/files
|
||||
- ELASTIC_URL=http://elastic:9200
|
||||
- GMAIL_URL=http://localhost:8088
|
||||
- CALENDAR_URL=http://localhost:8095
|
||||
- REKONI_URL=http://rekoni:4007
|
||||
- TELEGRAM_URL=http://localhost:8086
|
||||
- COLLABORATOR_URL=ws://localhost:3079
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- BRANDING_URL=http://localhost:8083/branding-test.json
|
||||
- STATS_URL=http://stats:4901
|
||||
- STATS_API=http://localhost:4901
|
||||
transactor:
|
||||
image: hardcoreeng/transactor
|
||||
pull_policy: never
|
||||
links:
|
||||
- mongodb
|
||||
- elastic
|
||||
- minio
|
||||
- rekoni
|
||||
- account
|
||||
ports:
|
||||
- 3334:3334
|
||||
volumes:
|
||||
- ./branding-test.json:/var/cfg/branding.json
|
||||
environment:
|
||||
- REGION=
|
||||
- SERVER_PORT=3334
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=${DB_URL}
|
||||
- METRICS_CONSOLE=false
|
||||
- METRICS_FILE=metrics.txt
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- FRONT_URL=http://localhost:8083
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- COLLABORATOR_URL=http://collaborator:3079
|
||||
- LAST_NAME_FIRST=true
|
||||
- BRANDING_PATH=/var/cfg/branding.json
|
||||
- FULLTEXT_URL=http://fulltext:4710
|
||||
- STATS_URL=http://stats:4901
|
||||
- ENABLE_COMPRESSION=true
|
||||
transactor-europe:
|
||||
image: hardcoreeng/transactor
|
||||
pull_policy: never
|
||||
links:
|
||||
- elastic
|
||||
- minio
|
||||
- rekoni
|
||||
- cockroach
|
||||
- account
|
||||
ports:
|
||||
- 3335:3335
|
||||
volumes:
|
||||
- ./branding-test.json:/var/cfg/branding.json
|
||||
environment:
|
||||
- SERVER_PORT=3335
|
||||
- REGION=europe
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=${DB_EU_URL}
|
||||
- METRICS_CONSOLE=false
|
||||
- METRICS_FILE=metrics.txt
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- FRONT_URL=http://localhost:8083
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- COLLABORATOR_URL=http://collaborator:3079
|
||||
- LAST_NAME_FIRST=true
|
||||
- BRANDING_PATH=/var/cfg/branding.json
|
||||
- FULLTEXT_URL=http://fulltext:4710
|
||||
- STATS_URL=http://stats:4901
|
||||
- ENABLE_COMPRESSION=true
|
||||
collaborator:
|
||||
image: hardcoreeng/collaborator
|
||||
links:
|
||||
- mongodb
|
||||
- minio
|
||||
- transactor
|
||||
ports:
|
||||
- 3079:3079
|
||||
environment:
|
||||
- COLLABORATOR_PORT=3079
|
||||
- SECRET=secret
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- FULLTEXT_URL=http://fulltext:4710
|
||||
- STATS_URL=http://stats:4901
|
||||
restart: unless-stopped
|
||||
rekoni:
|
||||
image: hardcoreeng/rekoni-service
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 4007:4004
|
||||
environment:
|
||||
- STATS_URL=http://stats:4901
|
||||
|
||||
fulltext:
|
||||
image: hardcoreeng/fulltext
|
||||
restart: unless-stopped
|
||||
links:
|
||||
- elastic
|
||||
- mongodb
|
||||
- cockroach
|
||||
- rekoni
|
||||
ports:
|
||||
- 4710:4710
|
||||
environment:
|
||||
- PORT=4710
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=${DB_URL}
|
||||
- FULLTEXT_DB_URL=http://elastic:9200
|
||||
- ELASTIC_INDEX_NAME=local_storage_index
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- REKONI_URL=http://rekoni:4007
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- STATS_URL=http://stats:4901
|
||||
fulltext-europe:
|
||||
image: hardcoreeng/fulltext
|
||||
restart: unless-stopped
|
||||
links:
|
||||
- elastic
|
||||
- mongodb
|
||||
- cockroach
|
||||
- rekoni
|
||||
ports:
|
||||
- 4711:4711
|
||||
environment:
|
||||
- PORT=4711
|
||||
- REGION=europe
|
||||
- SERVER_SECRET=secret
|
||||
- DB_URL=${DB_EU_URL}
|
||||
- FULLTEXT_DB_URL=http://elastic:9200
|
||||
- ELASTIC_INDEX_NAME=local_eu_storage_index # Use different one to be able to check migration is working
|
||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||
- REKONI_URL=http://rekoni:4007
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- STATS_URL=http://stats:4901
|
||||
stats:
|
||||
image: hardcoreeng/stats
|
||||
ports:
|
||||
- 4901:4901
|
||||
environment:
|
||||
- PORT=4901
|
||||
- SERVER_SECRET=secret
|
||||
restart: unless-stopped
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
docker compose -p sanity kill
|
||||
docker compose -p sanity down --volumes
|
||||
docker compose -p sanity up -d --force-recreate --renew-anon-volumes
|
||||
docker_exit=$?
|
||||
if [ ${docker_exit} -eq 0 ]; then
|
||||
echo "Container started successfully"
|
||||
else
|
||||
echo "Container started with errors"
|
||||
exit ${docker_exit}
|
||||
fi
|
||||
|
||||
if [ "x$DO_CLEAN" == 'xtrue' ]; then
|
||||
echo 'Do docker Clean'
|
||||
docker system prune -a -f
|
||||
fi
|
||||
|
||||
./wait-elastic.sh 9201
|
||||
|
||||
# Create user record in accounts
|
||||
./tool.sh create-account admin -f Super -l Admin -p 1234
|
||||
./tool.sh set-user-admin admin true
|
||||
|
||||
./tool.sh create-account user1 -f John -l Appleseed -p 1234
|
||||
./tool.sh create-account user2 -f Kainin -l Dirak -p 1234
|
||||
|
||||
./tool.sh confirm-email user1
|
||||
./tool.sh confirm-email user2
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
echo "Downloading profile"
|
||||
token=$(./tool.sh generate-token --admin anticrm@hc.engineering sanity-ws)
|
||||
current=$(date +%Y%m%d%H%M%S)
|
||||
mkdir -p ./profiles
|
||||
curl -X PUT "http://localhost:3334/api/v1/manage?token=${token}&operation=profile-stop" -o "./profiles/profile-${current}".cpuprofile
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# npm install -g cpupro
|
||||
for profile in $(ls ./profiles/*.cpuprofile); do
|
||||
name=${profile/\.cpuprofile/}
|
||||
if ! test -f $name.html; then
|
||||
cpupro $profile -f "$name.html" --no-open
|
||||
fi
|
||||
done
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
echo "Start profiling on server"
|
||||
token=$(./tool.sh generate-token --admin anticrm@hc.engineering sanity-ws)
|
||||
curl -X PUT "http://localhost:3334/api/v1/manage?token=${token}&operation=profile-start"
|
||||
@@ -0,0 +1,11 @@
|
||||
PLATFORM_URI='http://localhost:8083'
|
||||
PLATFORM_TRANSACTOR='ws://localhost:3334'
|
||||
STAGING_URL='https://front.hc.engineering'
|
||||
PLATFORM_USER='user1'
|
||||
PLATFORM_USER_SECOND='user2'
|
||||
PLATFORM_WS='sanity-ws'
|
||||
PLATFORM_TOKEN='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InVzZXIxIiwid29ya3NwYWNlIjoic2FuaXR5LXdzIn0.hfUCqePHO-WNps2by4B-CYGKIpDpLG0WVCUUtU-SVI4'
|
||||
LOCAL_URL='http://localhost:3003/'
|
||||
DEV_URL='http://localhost:8080/account'
|
||||
SETTING=.auth/storage.json
|
||||
SETTING_SECOND=.auth/storageSecond.json
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
playwright-report
|
||||
test-results/*
|
||||
allure-report
|
||||
allure-results
|
||||
.auth
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "default"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@hcengineering/tests-ws-sanity",
|
||||
"version": "0.6.1",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"template": "@hcengineering/default-package",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"_phase:build": "compile transpile tests",
|
||||
"_phase:test": "",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate",
|
||||
"lint:fix": "eslint --fix tests",
|
||||
"lint": "eslint tests",
|
||||
"format": "format tests",
|
||||
"ci": "playwright install --with-deps chromium",
|
||||
"test": "",
|
||||
"uitest": "cross-env LOCAL_URL=http://localhost:3003/ DEV_URL= playwright test -c ./tests/playwright.config.ts",
|
||||
"staging-uitest": "cross-env PLATFORM_URI=https://front.hc.engineering/ playwright test -c ./tests/playwright.config.ts --grep @staging",
|
||||
"dev-uitest": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 DEV_URL=http://localhost:8080/account playwright test -c ./tests/playwright.config.ts",
|
||||
"debug": "cross-env LOCAL_URL=http://localhost:3003/ DEV_URL= playwright test -c ./tests/playwright.config.ts --debug --headed",
|
||||
"dev-debug": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 playwright test -c ./tests/playwright.config.ts --debug --headed",
|
||||
"codegen": "playwright codegen --load-storage .auth/storage.json http://localhost:8083/login/selectWorkspace",
|
||||
"codegen-admin": "playwright codegen --load-storage .auth/adminStorage.json http://localhost:8083/login/admin",
|
||||
"dev-codegen": "cross-env playwright codegen --load-storage storage-dev.json http://localhost:8080/workbench/sanity-ws/",
|
||||
"allure:generate": "allure generate allure-results -o allure-report --clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "~20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"@playwright/test": "^1.48.2",
|
||||
"allure-playwright": "^3.0.6",
|
||||
"allure-js-commons": "^3.0.6",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@hcengineering/tests-sanity": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "~16.0.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/client-resources": "^0.6.27"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Page, test as setup } from '@playwright/test'
|
||||
import path from 'path'
|
||||
|
||||
import { LoginPage, PlatformURI, PlatformUser, PlatformUserSecond, setTestOptions } from '@hcengineering/tests-sanity'
|
||||
import { existsSync } from 'fs'
|
||||
|
||||
const authFile = path.join(__dirname, '../../.auth/storage.json')
|
||||
const authFileSecond = path.join(__dirname, '../../.auth/storageSecond.json')
|
||||
const adminFile = path.join(__dirname, '../../.auth/adminStorage.json')
|
||||
|
||||
async function authenticate (page: Page, user: string, password: string): Promise<void> {
|
||||
const loginPage = new LoginPage(page)
|
||||
await (await page.goto(`${PlatformURI}`))?.finished()
|
||||
await loginPage.login(user, password)
|
||||
}
|
||||
|
||||
if (!existsSync(authFile)) {
|
||||
setup('auth user1', async ({ page }) => {
|
||||
await authenticate(page, PlatformUser, '1234')
|
||||
await setTestOptions(page)
|
||||
|
||||
await page.context().storageState({ path: authFile })
|
||||
})
|
||||
}
|
||||
|
||||
if (!existsSync(authFileSecond)) {
|
||||
setup('auth user2', async ({ page }) => {
|
||||
await authenticate(page, PlatformUserSecond, '1234')
|
||||
await setTestOptions(page)
|
||||
|
||||
await page.context().storageState({ path: authFileSecond })
|
||||
})
|
||||
}
|
||||
|
||||
if (!existsSync(adminFile)) {
|
||||
setup('auth admin', async ({ page }) => {
|
||||
await authenticate(page, 'admin', '1234')
|
||||
await setTestOptions(page)
|
||||
|
||||
await page.context().storageState({ path: adminFile })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { PlatformURI } from '@hcengineering/tests-sanity'
|
||||
import { type Page } from '@playwright/test'
|
||||
|
||||
export class AdminPage {
|
||||
readonly page: Page
|
||||
|
||||
constructor (page: Page) {
|
||||
this.page = page
|
||||
}
|
||||
|
||||
// ACTIONS
|
||||
async gotoAdmin (): Promise<void> {
|
||||
await (await this.page.goto(`${PlatformURI}/login/admin`))?.finished()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { devices, PlaywrightTestConfig } from '@playwright/test'
|
||||
import { config as dotenvConfig } from 'dotenv'
|
||||
dotenvConfig()
|
||||
|
||||
let maxFailures: number | undefined
|
||||
if (process.env.TESTS_MAX_FAILURES !== undefined) {
|
||||
maxFailures = parseInt(process.env.TESTS_MAX_FAILURES)
|
||||
}
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
projects: [
|
||||
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
|
||||
{
|
||||
name: 'Platform',
|
||||
use: {
|
||||
testIdAttribute: 'data-id',
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
...devices['Desktop Chrome'],
|
||||
screenshot: 'only-on-failure',
|
||||
viewport: {
|
||||
width: 1440,
|
||||
height: 900
|
||||
},
|
||||
trace: {
|
||||
mode: 'retain-on-failure',
|
||||
snapshots: true,
|
||||
screenshots: true,
|
||||
sources: true
|
||||
},
|
||||
contextOptions: {
|
||||
reducedMotion: 'reduce'
|
||||
}
|
||||
},
|
||||
fullyParallel: false,
|
||||
dependencies: ['setup']
|
||||
}
|
||||
],
|
||||
retries: 2,
|
||||
timeout: 60000,
|
||||
maxFailures,
|
||||
expect: {
|
||||
timeout: 15000
|
||||
},
|
||||
reporter: [
|
||||
['list'],
|
||||
['html'],
|
||||
[
|
||||
'allure-playwright',
|
||||
{
|
||||
detail: true,
|
||||
suiteTitle: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
export default config
|
||||
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
ApiEndpoint,
|
||||
generateId,
|
||||
getSecondPage,
|
||||
IssuesDetailsPage,
|
||||
IssuesPage,
|
||||
LoginPage,
|
||||
NewIssue,
|
||||
SelectWorkspacePage,
|
||||
TrackerNavigationMenuPage
|
||||
} from '@hcengineering/tests-sanity'
|
||||
import { test } from '@playwright/test'
|
||||
import { AdminPage } from '../model/admin.page'
|
||||
|
||||
test.describe('Workspace Archive tests', () => {
|
||||
let loginPage: LoginPage
|
||||
let selectWorkspacePage: SelectWorkspacePage
|
||||
let trackerNavigationMenuPage: TrackerNavigationMenuPage
|
||||
let issuesPage: IssuesPage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page)
|
||||
selectWorkspacePage = new SelectWorkspacePage(page)
|
||||
trackerNavigationMenuPage = new TrackerNavigationMenuPage(page)
|
||||
issuesPage = new IssuesPage(page)
|
||||
})
|
||||
|
||||
test('New workspace with date, archive, unarchive', async ({ page, browser, request }) => {
|
||||
const api: ApiEndpoint = new ApiEndpoint(request)
|
||||
const workspaceInfo = await api.createWorkspaceWithLogin(generateId(), 'user1', '1234')
|
||||
|
||||
const newIssue: NewIssue = {
|
||||
title: `Issue with all parameters and attachments-${generateId()}`,
|
||||
description: 'Created issue with all parameters and attachments description',
|
||||
status: 'In Progress',
|
||||
priority: 'Urgent',
|
||||
createLabel: true,
|
||||
labels: `CREATE-ISSUE-${generateId()}`,
|
||||
component: 'No component',
|
||||
estimation: '2',
|
||||
milestone: 'No Milestone',
|
||||
duedate: 'today'
|
||||
}
|
||||
await test.step('create new workspace', async () => {
|
||||
await loginPage.goto()
|
||||
await loginPage.login('user1', '1234')
|
||||
|
||||
await selectWorkspacePage.selectWorkspace(workspaceInfo.workspaceName)
|
||||
|
||||
await trackerNavigationMenuPage.openIssuesForProject('Default')
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
await issuesPage.createNewIssue(newIssue)
|
||||
await issuesPage.openIssueByName(newIssue.title)
|
||||
|
||||
const issuesDetailsPage = new IssuesDetailsPage(page)
|
||||
await issuesDetailsPage.checkIssue(newIssue)
|
||||
})
|
||||
|
||||
using adminSecondPage = await getSecondPage(browser)
|
||||
const page2 = adminSecondPage.page
|
||||
|
||||
await test.step('Archive workspace', async () => {
|
||||
// login as admin
|
||||
const loginPage2 = new LoginPage(adminSecondPage.page)
|
||||
await loginPage2.goto()
|
||||
await loginPage2.login('admin', '1234')
|
||||
|
||||
const adminPage = new AdminPage(page2)
|
||||
await adminPage.gotoAdmin()
|
||||
|
||||
await page2.getByText('Today -').click()
|
||||
await page2.locator('div:nth-child(3) > .checkbox-container > .checkSVG').click()
|
||||
await page2.locator('div:nth-child(4) > .checkbox-container > .checkSVG').click()
|
||||
|
||||
await page2.getByRole('button', { name: 'America', exact: true }).click()
|
||||
await page2.getByRole('button', { name: 'europe (hidden)' }).click()
|
||||
await page2.getByPlaceholder('Search').click()
|
||||
await page2.getByPlaceholder('Search').fill(workspaceInfo.workspaceId)
|
||||
await page2.locator(`[id="${workspaceInfo.workspaceId}"]`).getByRole('button', { name: 'Archive' }).click()
|
||||
|
||||
await page2.getByRole('button', { name: 'Ok' }).click()
|
||||
await page2.locator(`[id="${workspaceInfo.workspaceId}"]`).getByText('archived').waitFor()
|
||||
})
|
||||
await test.step('Check workspace is archived', async () => {
|
||||
await page.reload()
|
||||
await page.getByText('Workspace is archived').waitFor()
|
||||
})
|
||||
await test.step('Restore workspace', async () => {
|
||||
await page2.locator(`[id="${workspaceInfo.workspaceId}"]`).getByRole('button', { name: 'Unarchive' }).click()
|
||||
|
||||
await page2.getByRole('button', { name: 'Ok' }).click()
|
||||
await page2.locator(`[id="${workspaceInfo.workspaceId}"]`).getByText('active').waitFor()
|
||||
})
|
||||
await test.step('Check workspace is active again', async () => {
|
||||
await page.reload()
|
||||
|
||||
const issuesDetailsPage = new IssuesDetailsPage(page)
|
||||
await issuesDetailsPage.checkIssue(newIssue)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,227 @@
|
||||
import { faker } from '@faker-js/faker'
|
||||
import {
|
||||
generateId,
|
||||
IssuesDetailsPage,
|
||||
IssuesPage,
|
||||
LeftSideMenuPage,
|
||||
LoginPage,
|
||||
NewIssue,
|
||||
SelectWorkspacePage,
|
||||
SignInJoinPage,
|
||||
SignUpData,
|
||||
SignUpPage,
|
||||
TrackerNavigationMenuPage,
|
||||
UserProfilePage
|
||||
} from '@hcengineering/tests-sanity'
|
||||
import { test } from '@playwright/test'
|
||||
|
||||
test.describe('Workspace tests', () => {
|
||||
let loginPage: LoginPage
|
||||
let signUpPage: SignUpPage
|
||||
let selectWorkspacePage: SelectWorkspacePage
|
||||
let leftSideMenuPage: LeftSideMenuPage
|
||||
let trackerNavigationMenuPage: TrackerNavigationMenuPage
|
||||
let issuesPage: IssuesPage
|
||||
let userProfilePage: UserProfilePage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page)
|
||||
signUpPage = new SignUpPage(page)
|
||||
selectWorkspacePage = new SelectWorkspacePage(page)
|
||||
leftSideMenuPage = new LeftSideMenuPage(page)
|
||||
trackerNavigationMenuPage = new TrackerNavigationMenuPage(page)
|
||||
issuesPage = new IssuesPage(page)
|
||||
userProfilePage = new UserProfilePage(page)
|
||||
})
|
||||
|
||||
test('Create a workspace with a custom name', async () => {
|
||||
const newUser: SignUpData = {
|
||||
firstName: `FirstName-${generateId()}`,
|
||||
lastName: `LastName-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
const newWorkspaceName = `New Workspace Name - ${generateId(2)}`
|
||||
await loginPage.goto()
|
||||
await loginPage.clickSignUp()
|
||||
await signUpPage.signUp(newUser)
|
||||
await selectWorkspacePage.createWorkspace(newWorkspaceName)
|
||||
await leftSideMenuPage.clickTracker()
|
||||
})
|
||||
|
||||
test('Create a new issue in the workspace with a custom name', async ({ page }) => {
|
||||
const newUser: SignUpData = {
|
||||
firstName: `FirstName-${generateId()}`,
|
||||
lastName: `LastName-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
const newIssue: NewIssue = {
|
||||
title: `Issue with all parameters and attachments-${generateId()}`,
|
||||
description: 'Created issue with all parameters and attachments description',
|
||||
status: 'In Progress',
|
||||
priority: 'Urgent',
|
||||
assignee: `${newUser.lastName} ${newUser.firstName}`,
|
||||
createLabel: true,
|
||||
labels: `CREATE-ISSUE-${generateId()}`,
|
||||
component: 'No component',
|
||||
estimation: '2',
|
||||
milestone: 'No Milestone',
|
||||
duedate: 'today'
|
||||
}
|
||||
const newWorkspaceName = `New Issue Name - ${generateId(2)}`
|
||||
await loginPage.goto()
|
||||
await loginPage.clickSignUp()
|
||||
await signUpPage.signUp(newUser)
|
||||
await selectWorkspacePage.createWorkspace(newWorkspaceName)
|
||||
|
||||
await trackerNavigationMenuPage.openIssuesForProject('Default')
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
await issuesPage.createNewIssue(newIssue)
|
||||
await issuesPage.openIssueByName(newIssue.title)
|
||||
|
||||
const issuesDetailsPage = new IssuesDetailsPage(page)
|
||||
await issuesDetailsPage.checkIssue(newIssue)
|
||||
})
|
||||
|
||||
test('Check validation steps description for the create flow', async ({ page }) => {
|
||||
const newUser: SignUpData = {
|
||||
firstName: `FirstName-${generateId()}`,
|
||||
lastName: `LastName-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
const newWorkspaceName = `New Workspace Name - ${generateId(2)}`
|
||||
|
||||
await loginPage.goto()
|
||||
await loginPage.clickSignUp()
|
||||
|
||||
await signUpPage.checkInfo(page, 'Required field First name')
|
||||
await signUpPage.enterFirstName(newUser.firstName)
|
||||
await signUpPage.checkInfo(page, 'Required field Last name')
|
||||
await signUpPage.enterLastName(newUser.lastName)
|
||||
await signUpPage.checkInfo(page, 'Required field Email')
|
||||
await signUpPage.enterEmail(newUser.email)
|
||||
await signUpPage.checkInfo(page, 'Required field Password')
|
||||
await signUpPage.enterPassword(newUser.password)
|
||||
await signUpPage.checkInfo(page, "Repeat password don't match Password")
|
||||
await signUpPage.enterRepeatPassword(newUser.password)
|
||||
await signUpPage.checkInfoSectionNotExist(page)
|
||||
await signUpPage.clickSignUp()
|
||||
await selectWorkspacePage.checkInfo(page, 'Required field Workspace name')
|
||||
await selectWorkspacePage.enterWorkspaceName(newWorkspaceName)
|
||||
await selectWorkspacePage.checkInfoSectionNotExist(page)
|
||||
})
|
||||
|
||||
test('Create a workspace with join link', async ({ page, browser }) => {
|
||||
const newUser: SignUpData = {
|
||||
firstName: `FirstName-${generateId()}`,
|
||||
lastName: `LastName-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
const newWorkspaceName = `Some HULY #@$ WS - ${generateId(12)}`
|
||||
await loginPage.goto()
|
||||
await loginPage.clickSignUp()
|
||||
await signUpPage.signUp(newUser)
|
||||
await selectWorkspacePage.createWorkspace(newWorkspaceName)
|
||||
await leftSideMenuPage.clickTracker()
|
||||
|
||||
// Generate invite link
|
||||
|
||||
await leftSideMenuPage.openProfileMenu()
|
||||
await leftSideMenuPage.inviteToWorkspace()
|
||||
await leftSideMenuPage.getInviteLink()
|
||||
|
||||
const linkText = await page.locator('.antiPopup .link').textContent()
|
||||
const page2 = await browser.newPage()
|
||||
try {
|
||||
await page2.goto(linkText ?? '')
|
||||
const newUser2: SignUpData = {
|
||||
firstName: `FirstName2-${generateId()}`,
|
||||
lastName: `LastName2-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
|
||||
await page2.getByRole('link', { name: 'Sign Up' }).click()
|
||||
const signUpPage2 = new SignUpPage(page2)
|
||||
await signUpPage2.signUp(newUser2, 'join')
|
||||
|
||||
const leftSideMenuPage2 = new LeftSideMenuPage(page2)
|
||||
await leftSideMenuPage2.clickTracker()
|
||||
} finally {
|
||||
await page2.close()
|
||||
}
|
||||
})
|
||||
|
||||
test('Create a workspace with join link - existing account', async ({ page, browser }) => {
|
||||
const newUser: SignUpData = {
|
||||
firstName: `FirstName-${generateId()}`,
|
||||
lastName: `LastName-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
const newWorkspaceName = `Some HULY #@$ WS - ${generateId(12)}`
|
||||
await loginPage.goto()
|
||||
await loginPage.clickSignUp()
|
||||
await signUpPage.signUp(newUser)
|
||||
await selectWorkspacePage.createWorkspace(newWorkspaceName)
|
||||
await leftSideMenuPage.clickTracker()
|
||||
|
||||
// Generate invite link
|
||||
await leftSideMenuPage.openProfileMenu()
|
||||
await leftSideMenuPage.inviteToWorkspace()
|
||||
await leftSideMenuPage.getInviteLink()
|
||||
|
||||
const linkText = await page.locator('.antiPopup .link').textContent()
|
||||
const page2 = await browser.newPage()
|
||||
try {
|
||||
const loginPage2 = new LoginPage(page2)
|
||||
await loginPage2.goto()
|
||||
await loginPage2.clickSignUp()
|
||||
|
||||
const newUser2: SignUpData = {
|
||||
firstName: `FirstName2-${generateId()}`,
|
||||
lastName: `LastName2-${generateId()}`,
|
||||
email: `sanity-email+${generateId()}@gmail.com`,
|
||||
password: '1234'
|
||||
}
|
||||
|
||||
const signUpPage2 = new SignUpPage(page2)
|
||||
await signUpPage2.signUp(newUser2)
|
||||
|
||||
// Ok we signed in, and no workspace present.
|
||||
await page2.goto(linkText ?? '')
|
||||
const joinPage = new SignInJoinPage(page2)
|
||||
await joinPage.join(newUser2)
|
||||
|
||||
const leftSideMenuPage2 = new LeftSideMenuPage(page2)
|
||||
await leftSideMenuPage2.clickTracker()
|
||||
} finally {
|
||||
await page2.close()
|
||||
}
|
||||
})
|
||||
|
||||
test('User can leave workspace', async () => {
|
||||
const newUser: SignUpData = {
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
email: faker.internet.email(),
|
||||
password: '1234'
|
||||
}
|
||||
const newWorkspaceName = `Some HULY #@$ WS - ${generateId(12)}`
|
||||
await loginPage.goto()
|
||||
await loginPage.clickSignUp()
|
||||
await signUpPage.signUp(newUser)
|
||||
await selectWorkspacePage.createWorkspace(newWorkspaceName)
|
||||
await trackerNavigationMenuPage.checkIfTrackerSidebarIsVisible()
|
||||
await userProfilePage.openProfileMenu()
|
||||
await userProfilePage.selectProfileByName(newUser.lastName + ' ' + newUser.firstName)
|
||||
await userProfilePage.clickLeaveWorkspaceButton()
|
||||
await userProfilePage.clickLeaveWorkspaceCancelButton()
|
||||
await userProfilePage.clickLeaveWorkspaceButton()
|
||||
await userProfilePage.clickLeaveWorkspaceConfirmButton()
|
||||
await userProfilePage.checkIfAccountIsDisabled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
ApiEndpoint,
|
||||
generateId,
|
||||
getSecondPage,
|
||||
IssuesDetailsPage,
|
||||
IssuesPage,
|
||||
LoginPage,
|
||||
NewIssue,
|
||||
SelectWorkspacePage,
|
||||
TrackerNavigationMenuPage
|
||||
} from '@hcengineering/tests-sanity'
|
||||
import { test } from '@playwright/test'
|
||||
import { AdminPage } from '../model/admin.page'
|
||||
|
||||
test.describe('Workspace Migration tests', () => {
|
||||
let loginPage: LoginPage
|
||||
let selectWorkspacePage: SelectWorkspacePage
|
||||
let trackerNavigationMenuPage: TrackerNavigationMenuPage
|
||||
let issuesPage: IssuesPage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page)
|
||||
selectWorkspacePage = new SelectWorkspacePage(page)
|
||||
trackerNavigationMenuPage = new TrackerNavigationMenuPage(page)
|
||||
issuesPage = new IssuesPage(page)
|
||||
})
|
||||
|
||||
test('New workspace migrate to europe', async ({ page, browser, request }) => {
|
||||
const api: ApiEndpoint = new ApiEndpoint(request)
|
||||
const workspaceInfo = await api.createWorkspaceWithLogin(generateId(), 'user1', '1234')
|
||||
|
||||
const newIssue: NewIssue = {
|
||||
title: `Issue with all parameters and attachments-${generateId()}`,
|
||||
description: 'Created issue with all parameters and attachments description',
|
||||
status: 'In Progress',
|
||||
priority: 'Urgent',
|
||||
createLabel: true,
|
||||
labels: `CREATE-ISSUE-${generateId()}`,
|
||||
component: 'No component',
|
||||
estimation: '2',
|
||||
milestone: 'No Milestone',
|
||||
duedate: 'today'
|
||||
}
|
||||
await test.step('create new workspace', async () => {
|
||||
await loginPage.goto()
|
||||
await loginPage.login('user1', '1234')
|
||||
|
||||
await selectWorkspacePage.selectWorkspace(workspaceInfo.workspaceName)
|
||||
|
||||
await trackerNavigationMenuPage.openIssuesForProject('Default')
|
||||
await issuesPage.clickModelSelectorAll()
|
||||
await issuesPage.createNewIssue(newIssue)
|
||||
await issuesPage.openIssueByName(newIssue.title)
|
||||
|
||||
const issuesDetailsPage = new IssuesDetailsPage(page)
|
||||
await issuesDetailsPage.checkIssue(newIssue)
|
||||
})
|
||||
|
||||
using adminSecondPage = await getSecondPage(browser)
|
||||
const page2 = adminSecondPage.page
|
||||
|
||||
await test.step('Migrate workspace', async () => {
|
||||
// login as admin
|
||||
const loginPage2 = new LoginPage(adminSecondPage.page)
|
||||
await loginPage2.goto()
|
||||
await loginPage2.login('admin', '1234')
|
||||
|
||||
const adminPage = new AdminPage(page2)
|
||||
await adminPage.gotoAdmin()
|
||||
|
||||
await page2.getByText('Today -').click()
|
||||
await page2.locator('div:nth-child(3) > .checkbox-container > .checkSVG').click()
|
||||
await page2.locator('div:nth-child(4) > .checkbox-container > .checkSVG').click()
|
||||
|
||||
await page2.getByRole('button', { name: 'America', exact: true }).click()
|
||||
await page2.getByRole('button', { name: 'europe (hidden)' }).click()
|
||||
await page2.getByPlaceholder('Search').click()
|
||||
await page2.getByPlaceholder('Search').fill(workspaceInfo.workspaceId)
|
||||
await page2.locator(`[id="${workspaceInfo.workspaceId}"]`).getByRole('button', { name: 'Migrate' }).click()
|
||||
|
||||
await page2.getByRole('button', { name: 'Ok' }).click()
|
||||
await page2.locator(`[id="${workspaceInfo.workspaceId}"]`).getByText('europe').waitFor()
|
||||
})
|
||||
await test.step('Check workspace is active again', async () => {
|
||||
await page.reload()
|
||||
|
||||
const issuesDetailsPage = new IssuesDetailsPage(page)
|
||||
await issuesDetailsPage.checkIssue(newIssue)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./tests",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"lib": ["esnext", "dom"],
|
||||
}
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export MODEL_VERSION=$(node ../common/scripts/show_version.js)
|
||||
export MINIO_ACCESS_KEY=minioadmin
|
||||
export MINIO_SECRET_KEY=minioadmin
|
||||
export MINIO_ENDPOINT=localhost:9002
|
||||
export ACCOUNTS_URL=http://localhost:3003
|
||||
export TRANSACTOR_URL=ws://localhost:3334
|
||||
# export ACCOUNT_DB_URL=postgresql://root@localhost:26258/defaultdb?sslmode=disable
|
||||
export ACCOUNT_DB_URL=mongodb://localhost:27018
|
||||
export MONGO_URL=mongodb://localhost:27018
|
||||
export ELASTIC_URL=http://localhost:9201
|
||||
export SERVER_SECRET=secret
|
||||
export DB_URL=postgresql://root@localhost:26258/defaultdb?sslmode=disable
|
||||
|
||||
node ${TOOL_OPTIONS} ../dev/tool/bundle/bundle.js $@
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
res=''
|
||||
port=$1
|
||||
echo "Warning Elastic to up and running with attachment processor... ${port}"
|
||||
for i in `seq 1 30`;
|
||||
do
|
||||
res=$(curl -s http://localhost:${port}/_cluster/health )
|
||||
echo "$res"
|
||||
if [[ $res = *"yellow"* ]]; then
|
||||
echo "Elastic up and running..."
|
||||
exit 0
|
||||
fi
|
||||
if [[ $res = *"green"* ]]; then
|
||||
echo "Elastic up and running..."
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
Reference in New Issue
Block a user