mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Merge staging 12feb (#7993)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
+90
-51
@@ -40,6 +40,8 @@ env:
|
|||||||
rush.json
|
rush.json
|
||||||
.prettierrc
|
.prettierrc
|
||||||
tools
|
tools
|
||||||
|
workers
|
||||||
|
ws-tests
|
||||||
PublishTempFolder: publish_artifacts
|
PublishTempFolder: publish_artifacts
|
||||||
INIT_SCRIPTS_BRANCH: 'unified-init-scripts'
|
INIT_SCRIPTS_BRANCH: 'unified-init-scripts'
|
||||||
|
|
||||||
@@ -81,13 +83,13 @@ jobs:
|
|||||||
run: node common/scripts/install-run-rush.js model-version
|
run: node common/scripts/install-run-rush.js model-version
|
||||||
|
|
||||||
- name: Building...
|
- name: Building...
|
||||||
run: node common/scripts/install-run-rush.js build -p 20
|
run: node common/scripts/install-run-rush.js build
|
||||||
|
|
||||||
- name: Bundle...
|
- name: Bundle...
|
||||||
run: node common/scripts/install-run-rush.js bundle -p 20
|
run: node common/scripts/install-run-rush.js bundle
|
||||||
|
|
||||||
- name: Validate...
|
- 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
|
- name: Cache build results
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -154,7 +156,7 @@ jobs:
|
|||||||
[ -z "$(git diff --name-only '*.js' '*.ts' '*.svelte' '*.json' '*.yaml' | cat)" ]
|
[ -z "$(git diff --name-only '*.js' '*.ts' '*.svelte' '*.json' '*.yaml' | cat)" ]
|
||||||
echo '================================================================'
|
echo '================================================================'
|
||||||
- name: Formatting...
|
- 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
|
- name: Check files formatting
|
||||||
run: |
|
run: |
|
||||||
echo '================================================================'
|
echo '================================================================'
|
||||||
@@ -223,7 +225,7 @@ jobs:
|
|||||||
run: node common/scripts/install-run-rush.js install
|
run: node common/scripts/install-run-rush.js install
|
||||||
|
|
||||||
- name: Docker Build
|
- 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:
|
env:
|
||||||
DOCKER_CLI_HINTS: false
|
DOCKER_CLI_HINTS: false
|
||||||
- name: Prepare server
|
- name: Prepare server
|
||||||
@@ -260,10 +262,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd ./tests/sanity
|
cd ./tests/sanity
|
||||||
mkdir logs
|
mkdir logs
|
||||||
docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log
|
cd logs
|
||||||
docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log
|
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||||
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
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -340,7 +340,7 @@ jobs:
|
|||||||
run: node common/scripts/install-run-rush.js install
|
run: node common/scripts/install-run-rush.js install
|
||||||
|
|
||||||
- name: Docker Build
|
- 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:
|
env:
|
||||||
DOCKER_CLI_HINTS: false
|
DOCKER_CLI_HINTS: false
|
||||||
- name: Prepare server
|
- name: Prepare server
|
||||||
@@ -361,9 +361,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd ./tests/sanity
|
cd ./tests/sanity
|
||||||
mkdir logs
|
mkdir logs
|
||||||
docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log
|
cd logs
|
||||||
docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log
|
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||||
docker logs $(docker ps | grep front | cut -f 1 -d ' ') > logs/front.log
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -405,12 +404,12 @@ jobs:
|
|||||||
run: node common/scripts/install-run-rush.js install
|
run: node common/scripts/install-run-rush.js install
|
||||||
|
|
||||||
- name: Docker Build
|
- 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:
|
env:
|
||||||
DOCKER_CLI_HINTS: false
|
DOCKER_CLI_HINTS: false
|
||||||
- name: Configure /etc/hosts
|
- name: Configure /etc/hosts
|
||||||
run: |
|
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
|
- name: Prepare server
|
||||||
run: |
|
run: |
|
||||||
cd ./qms-tests
|
cd ./qms-tests
|
||||||
@@ -429,50 +428,90 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd ./qms-tests/sanity
|
cd ./qms-tests/sanity
|
||||||
mkdir logs
|
mkdir logs
|
||||||
docker logs $(docker ps | grep transactor | cut -f 1 -d ' ') > logs/transactor.log
|
cd logs
|
||||||
docker logs $(docker ps | grep account | cut -f 1 -d ' ') > logs/account.log
|
docker ps -a --format '{{.Names}}' | xargs -I {} sh -c 'docker logs {} > {}_logs.log 2>&1'
|
||||||
docker logs $(docker ps | grep front | cut -f 1 -d ' ') > logs/front.log
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: playwright-results-qms
|
name: playwright-results-qms
|
||||||
path: ./qms-tests/sanity/playwright-report/
|
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
|
- name: Upload Logs
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: docker-logs-qms
|
name: docker-logs-qms
|
||||||
path: ./qms-tests/sanity/logs
|
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:
|
docker-build:
|
||||||
needs: [build, test, svelte-check, uitest]
|
needs: [build, test, svelte-check, uitest]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -531,9 +570,9 @@ jobs:
|
|||||||
DOCKER_EXTRA: --platform=linux/amd64,linux/arm64
|
DOCKER_EXTRA: --platform=linux/amd64,linux/arm64
|
||||||
- name: Docker build love-agent
|
- name: Docker build love-agent
|
||||||
run: |
|
run: |
|
||||||
cd ./services/ai-bot/love-agent
|
cd ./services/ai-bot/love-agent
|
||||||
pnpm install && pnpm build
|
pnpm install && pnpm build
|
||||||
pnpm docker:build -v
|
pnpm docker:build -v
|
||||||
env:
|
env:
|
||||||
DOCKER_CLI_HINTS: false
|
DOCKER_CLI_HINTS: false
|
||||||
DOCKER_EXTRA: --platform=linux/amd64,linux/arm64
|
DOCKER_EXTRA: --platform=linux/amd64,linux/arm64
|
||||||
@@ -590,7 +629,7 @@ jobs:
|
|||||||
- name: Model version from git tags
|
- name: Model version from git tags
|
||||||
run: node common/scripts/install-run-rush.js model-version
|
run: node common/scripts/install-run-rush.js model-version
|
||||||
- name: Package
|
- 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
|
- name: Package JSON
|
||||||
run: |
|
run: |
|
||||||
cd desktop-package
|
cd desktop-package
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ temp/
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
tests/sanity/screenshots
|
*tests/sanity/screenshots
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
*.pid
|
*.pid
|
||||||
|
|||||||
Vendored
+65
@@ -70,6 +70,38 @@
|
|||||||
"cwd": "${workspaceRoot}/pods/server",
|
"cwd": "${workspaceRoot}/pods/server",
|
||||||
"protocol": "inspector"
|
"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",
|
"name": "Debug Fulltext",
|
||||||
"type": "node",
|
"type": "node",
|
||||||
@@ -132,6 +164,39 @@
|
|||||||
"cwd": "${workspaceRoot}/pods/account",
|
"cwd": "${workspaceRoot}/pods/account",
|
||||||
"protocol": "inspector"
|
"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",
|
"name": "Debug Stats",
|
||||||
"type": "node",
|
"type": "node",
|
||||||
|
|||||||
@@ -331,7 +331,7 @@
|
|||||||
"summary": "Format changed projects",
|
"summary": "Format changed projects",
|
||||||
"description": "Format and autofix linting issues in changed projects",
|
"description": "Format and autofix linting issues in changed projects",
|
||||||
"safeForSimultaneousRushProcesses": true,
|
"safeForSimultaneousRushProcesses": true,
|
||||||
"shellCommand": "./common/scripts/each-diff.sh rushx format --force"
|
"shellCommand": "./common/scripts/fast-format.sh"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"commandKind": "global",
|
"commandKind": "global",
|
||||||
@@ -367,6 +367,16 @@
|
|||||||
"shortName": "-f",
|
"shortName": "-f",
|
||||||
"description": "Force formatting",
|
"description": "Force formatting",
|
||||||
"associatedCommands": ["format"]
|
"associatedCommands": ["format"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameterKind": "string",
|
||||||
|
"argumentName": "BRANCH",
|
||||||
|
"required": false,
|
||||||
|
"associatedPhases": [],
|
||||||
|
"shortName": "-b",
|
||||||
|
"longName": "--branch",
|
||||||
|
"description": "Force formatting of branch",
|
||||||
|
"associatedCommands": ["fast-format"]
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
// /**
|
// /**
|
||||||
|
|||||||
Generated
+29
@@ -1087,6 +1087,9 @@ importers:
|
|||||||
'@rush-temp/tests-sanity':
|
'@rush-temp/tests-sanity':
|
||||||
specifier: file:./projects/tests-sanity.tgz
|
specifier: file:./projects/tests-sanity.tgz
|
||||||
version: 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':
|
'@rush-temp/text':
|
||||||
specifier: file:./projects/text.tgz
|
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)
|
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)
|
||||||
@@ -5166,6 +5169,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-noV3nlBP0OvM6i4C5YUevkhltHxG/2bct4pusP3O3AEQ7Za+A4qwzpsE08pHfA9f9pedEG677GE4EmG9x61rVQ==, tarball: file:projects/tests-sanity.tgz}
|
resolution: {integrity: sha512-noV3nlBP0OvM6i4C5YUevkhltHxG/2bct4pusP3O3AEQ7Za+A4qwzpsE08pHfA9f9pedEG677GE4EmG9x61rVQ==, tarball: file:projects/tests-sanity.tgz}
|
||||||
version: 0.0.0
|
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':
|
'@rush-temp/text-core@file:projects/text-core.tgz':
|
||||||
resolution: {integrity: sha512-sQ1C/LTDJ6RoC7YHYOCrRGsOHerkeumxp9lRjRaTmjc65mAFtiizz4eYFlkbSVSQvahSdD5KMT1G+wvtmLq7Gg==, tarball: file:projects/text-core.tgz}
|
resolution: {integrity: sha512-sQ1C/LTDJ6RoC7YHYOCrRGsOHerkeumxp9lRjRaTmjc65mAFtiizz4eYFlkbSVSQvahSdD5KMT1G+wvtmLq7Gg==, tarball: file:projects/text-core.tgz}
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@@ -24910,6 +24917,28 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- 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)':
|
'@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:
|
dependencies:
|
||||||
'@types/jest': 29.5.12
|
'@types/jest': 29.5.12
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
[ -z "$FILES" ] && exit 0
|
||||||
|
|
||||||
roots=$(rush list -p --json | grep "path" | cut -f 2 -d ':' | cut -f 2 -d '"')
|
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
|
||||||
@@ -41,8 +41,6 @@ async function doBackup (dirName: string, token: string, endpoint: string, wsIds
|
|||||||
ctx.info('do backup', { ...wsIds, endpoint })
|
ctx.info('do backup', { ...wsIds, endpoint })
|
||||||
await backup(ctx, endpoint, wsIds, storage, {
|
await backup(ctx, endpoint, wsIds, storage, {
|
||||||
force: true,
|
force: true,
|
||||||
freshBackup: false,
|
|
||||||
clean: false,
|
|
||||||
skipDomains: [],
|
skipDomains: [],
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
connectTimeout: 60 * 1000,
|
connectTimeout: 60 * 1000,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"analyze": "rm -rf ./dist && cross-env NODE_ENV=production webpack --profile --json > stats.json",
|
"analyze": "rm -rf ./dist && cross-env NODE_ENV=production webpack --profile --json > stats.json",
|
||||||
"show": "webpack-bundle-analyzer stats.json dist",
|
"show": "webpack-bundle-analyzer stats.json dist",
|
||||||
"dev-server": "cross-env USE_CACHE=false CLIENT_TYPE=dev-server webpack serve",
|
"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": "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-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",
|
"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-huly': '/config-huly.json',
|
||||||
'dev-bold': '/config.json',
|
'dev-bold': '/config.json',
|
||||||
'dev-server': '/config.json',
|
'dev-server': '/config.json',
|
||||||
|
'dev-server-test': '/config-test.json',
|
||||||
'dev-worker': '/config-worker.json',
|
'dev-worker': '/config-worker.json',
|
||||||
'dev-worker-local': '/config-worker-local.json'
|
'dev-worker-local': '/config-worker-local.json'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ const mode = process.env.NODE_ENV || 'development'
|
|||||||
const prod = mode === 'production'
|
const prod = mode === 'production'
|
||||||
const clientType = process.env.CLIENT_TYPE ?? ''
|
const clientType = process.env.CLIENT_TYPE ?? ''
|
||||||
const devServer = clientType === 'dev-server'
|
const devServer = clientType === 'dev-server'
|
||||||
|
const devServerTest = clientType === 'dev-server-test'
|
||||||
const devServerWorker = clientType === 'dev-worker'
|
const devServerWorker = clientType === 'dev-worker'
|
||||||
const devServerWorkerLocal = clientType === 'dev-worker-local'
|
const devServerWorkerLocal = clientType === 'dev-worker-local'
|
||||||
const devProduction = clientType === 'dev-production'
|
const devProduction = clientType === 'dev-production'
|
||||||
const devProductionHuly = clientType === 'dev-huly'
|
const devProductionHuly = clientType === 'dev-huly'
|
||||||
const devProductionBold = clientType === 'dev-bold'
|
const devProductionBold = clientType === 'dev-bold'
|
||||||
const dev =
|
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 ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
|
||||||
|
|
||||||
const { EsbuildPlugin } = require('esbuild-loader')
|
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 = {
|
const devHulyProxy = {
|
||||||
'/account': {
|
'/account': {
|
||||||
target: 'https://account.huly.app/',
|
target: 'https://account.huly.app/',
|
||||||
@@ -148,6 +179,7 @@ const proxy = {
|
|||||||
'dev-worker': devProxy,
|
'dev-worker': devProxy,
|
||||||
'dev-worker-local': devProxy,
|
'dev-worker-local': devProxy,
|
||||||
'dev-server': devProxy,
|
'dev-server': devProxy,
|
||||||
|
'dev-server-test': devProxyTest,
|
||||||
'dev-production': devFrontProxy,
|
'dev-production': devFrontProxy,
|
||||||
'dev-bold': devBoldProxy,
|
'dev-bold': devBoldProxy,
|
||||||
'dev-huly': devHulyProxy
|
'dev-huly': devHulyProxy
|
||||||
|
|||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd ./pods/account
|
||||||
|
rushx bundle
|
||||||
|
|
||||||
|
port=${1:-3000}
|
||||||
|
echo "Running account on port: ${port}"
|
||||||
|
#MONGO_URL=mongodb://localhost:27017,
|
||||||
|
export DB_URL="mongodb://localhost:27018"
|
||||||
|
# DB_URL=postgresql://postgres:example@localhost:5432,
|
||||||
|
# DB_URL=postgresql://root@host.docker.internal:26257/defaultdb?sslmode=disable,
|
||||||
|
export SERVER_SECRET="secret"
|
||||||
|
export REGION_INFO="|America;europe|"
|
||||||
|
export TRANSACTOR_URL="ws://transactor:3334;ws://localhost:3334,ws://transactor-europe:3335;ws://localhost:3335;europe,"
|
||||||
|
export ACCOUNTS_URL="http://localhost:${port}"
|
||||||
|
export ACCOUNT_PORT=${port}
|
||||||
|
export FRONT_URL="http://localhost:8080"
|
||||||
|
export STATS_URL="http://host.docker.internal:4900"
|
||||||
|
export SES_URL=
|
||||||
|
export MINIO_ACCESS_KEY="minioadmin"
|
||||||
|
export MINIO_SECRET_KEY="minioadmin"
|
||||||
|
export MINIO_ENDPOINT="localhost"
|
||||||
|
export ADMIN_EMAILS=admin
|
||||||
|
# DISABLE_SIGNUP=true,
|
||||||
|
# INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml,
|
||||||
|
# INIT_WORKSPACE=onboarding,
|
||||||
|
node --inspect bundle/bundle.js
|
||||||
@@ -589,8 +589,6 @@ export function devTool (
|
|||||||
// })
|
// })
|
||||||
// },
|
// },
|
||||||
// cmd.region,
|
// cmd.region,
|
||||||
// true,
|
|
||||||
// true,
|
|
||||||
// 5000, // 5 gigabytes per blob
|
// 5000, // 5 gigabytes per blob
|
||||||
// sharedPipelineContextVars,
|
// sharedPipelineContextVars,
|
||||||
// async (storage, workspaceStorage) => {
|
// async (storage, workspaceStorage) => {
|
||||||
@@ -661,8 +659,6 @@ export function devTool (
|
|||||||
// })
|
// })
|
||||||
// },
|
// },
|
||||||
// cmd.region,
|
// cmd.region,
|
||||||
// false,
|
|
||||||
// false,
|
|
||||||
// 100,
|
// 100,
|
||||||
// sharedPipelineContextVars
|
// sharedPipelineContextVars
|
||||||
// )
|
// )
|
||||||
@@ -859,8 +855,6 @@ export function devTool (
|
|||||||
// )
|
// )
|
||||||
// .option('-bl, --blobLimit <blobLimit>', 'A blob size limit in megabytes (default 15mb)', '15')
|
// .option('-bl, --blobLimit <blobLimit>', 'A blob size limit in megabytes (default 15mb)', '15')
|
||||||
// .option('-f, --force', 'Force backup', false)
|
// .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')
|
// .option('-t, --timeout <timeout>', 'Connect timeout in seconds', '30')
|
||||||
// .action(
|
// .action(
|
||||||
// async (
|
// async (
|
||||||
@@ -869,8 +863,6 @@ export function devTool (
|
|||||||
// cmd: {
|
// cmd: {
|
||||||
// skip: string
|
// skip: string
|
||||||
// force: boolean
|
// force: boolean
|
||||||
// fresh: boolean
|
|
||||||
// clean: boolean
|
|
||||||
// timeout: string
|
// timeout: string
|
||||||
// include: string
|
// include: string
|
||||||
// blobLimit: string
|
// blobLimit: string
|
||||||
@@ -882,8 +874,6 @@ export function devTool (
|
|||||||
// const endpoint = await getTransactorEndpoint(generateToken(systemAccountEmail, wsid), 'external')
|
// const endpoint = await getTransactorEndpoint(generateToken(systemAccountEmail, wsid), 'external')
|
||||||
// await backup(toolCtx, endpoint, wsIds, storage, {
|
// await backup(toolCtx, endpoint, wsIds, storage, {
|
||||||
// force: cmd.force,
|
// force: cmd.force,
|
||||||
// freshBackup: cmd.fresh,
|
|
||||||
// clean: cmd.clean,
|
|
||||||
// include: cmd.include === '*' ? undefined : new Set(cmd.include.split(';').map((it) => it.trim())),
|
// include: cmd.include === '*' ? undefined : new Set(cmd.include.split(';').map((it) => it.trim())),
|
||||||
// skipDomains: (cmd.skip ?? '').split(';').map((it) => it.trim()),
|
// skipDomains: (cmd.skip ?? '').split(';').map((it) => it.trim()),
|
||||||
// timeout: 0,
|
// timeout: 0,
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ export default plugin(platformId, {
|
|||||||
AccountNotConfirmed: '' as StatusCode,
|
AccountNotConfirmed: '' as StatusCode,
|
||||||
WorkspaceNotFound: '' as StatusCode<{ workspaceUuid?: string, workspaceName?: string, workspaceUrl?: string }>,
|
WorkspaceNotFound: '' as StatusCode<{ workspaceUuid?: string, workspaceName?: string, workspaceUrl?: string }>,
|
||||||
WorkspaceArchived: '' as StatusCode<{ workspaceUuid: string }>,
|
WorkspaceArchived: '' as StatusCode<{ workspaceUuid: string }>,
|
||||||
|
WorkspaceMigration: '' as StatusCode<{ workspaceUuid: string }>,
|
||||||
SocialIdNotFound: '' as StatusCode<{ socialId: string, type: string }>,
|
SocialIdNotFound: '' as StatusCode<{ socialId: string, type: string }>,
|
||||||
SocialIdNotConfirmed: '' as StatusCode<{ socialId: string, type: string }>,
|
SocialIdNotConfirmed: '' as StatusCode<{ socialId: string, type: string }>,
|
||||||
SocialIdAlreadyConfirmed: '' as StatusCode<{ socialId: string, type: string }>,
|
SocialIdAlreadyConfirmed: '' as StatusCode<{ socialId: string, type: string }>,
|
||||||
|
|||||||
@@ -283,6 +283,9 @@ class Connection implements ClientConnection {
|
|||||||
if (resp.error?.code === platform.status.WorkspaceArchived) {
|
if (resp.error?.code === platform.status.WorkspaceArchived) {
|
||||||
this.opt?.onArchived?.()
|
this.opt?.onArchived?.()
|
||||||
}
|
}
|
||||||
|
if (resp.error?.code === platform.status.WorkspaceMigration) {
|
||||||
|
this.opt?.onMigration?.()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resp.id !== undefined) {
|
if (resp.id !== undefined) {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export interface ClientFactoryOptions {
|
|||||||
onUpgrade?: () => void
|
onUpgrade?: () => void
|
||||||
onUnauthorized?: () => void
|
onUnauthorized?: () => void
|
||||||
onArchived?: () => void
|
onArchived?: () => void
|
||||||
|
onMigration?: () => void
|
||||||
onConnect?: (event: ClientConnectEvent, lastTx: string | undefined, data: any) => Promise<void>
|
onConnect?: (event: ClientConnectEvent, lastTx: string | undefined, data: any) => Promise<void>
|
||||||
ctx?: MeasureContext
|
ctx?: MeasureContext
|
||||||
onDialTimeout?: () => void | Promise<void>
|
onDialTimeout?: () => void | Promise<void>
|
||||||
|
|||||||
@@ -27,11 +27,13 @@
|
|||||||
Scroller,
|
Scroller,
|
||||||
SearchEdit,
|
SearchEdit,
|
||||||
showPopup,
|
showPopup,
|
||||||
ticker
|
ticker,
|
||||||
|
Grid
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
import { getAllWorkspaces, getRegionInfo, performWorkspaceOperation } from '../utils'
|
import { getAllWorkspaces, getRegionInfo, performWorkspaceOperation } from '../utils'
|
||||||
import { RegionInfo } from '@hcengineering/account-client'
|
import { RegionInfo } from '@hcengineering/account-client'
|
||||||
|
import ColStar from '@hcengineering/ui/src/components/icons/ColStar.svelte'
|
||||||
|
|
||||||
$: now = $ticker
|
$: now = $ticker
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkspaceInfo = WorkspaceInfoWithStatus & { attempts: number }
|
type WorkspaceInfo = WorkspaceInfoWithStatus & { processingAttempts: number }
|
||||||
|
|
||||||
let workspaces: WorkspaceInfo[] = []
|
let workspaces: WorkspaceInfo[] = []
|
||||||
|
|
||||||
@@ -193,13 +195,19 @@
|
|||||||
|
|
||||||
let selectedRegionId: string = ''
|
let selectedRegionId: string = ''
|
||||||
void getRegionInfo().then((_regionInfo) => {
|
void getRegionInfo().then((_regionInfo) => {
|
||||||
regionInfo = _regionInfo?.filter((it) => it.name !== '') ?? []
|
regionInfo = _regionInfo ?? []
|
||||||
if (selectedRegionId === '' && regionInfo.length > 0) {
|
if (selectedRegionId === '' && regionInfo.length > 0) {
|
||||||
selectedRegionId = regionInfo[0].region
|
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(
|
$: byVersion = groupByArray(
|
||||||
workspaces.filter((it) => {
|
workspaces.filter((it) => {
|
||||||
@@ -278,8 +286,11 @@
|
|||||||
<ButtonMenu
|
<ButtonMenu
|
||||||
selected={selectedRegionId}
|
selected={selectedRegionId}
|
||||||
autoSelectionIfOne
|
autoSelectionIfOne
|
||||||
title={regionInfo.find((it) => it.region === selectedRegionId)?.name}
|
title={selectedRegionName}
|
||||||
items={regionInfo.map((it) => ({ id: it.region === '' ? '#' : it.region, label: getEmbeddedLabel(it.name) }))}
|
items={regionInfo.map((it) => ({
|
||||||
|
id: it.region === '' ? '#' : it.region,
|
||||||
|
label: getEmbeddedLabel(it.name.length > 0 ? it.name : it.region + ' (hidden)')
|
||||||
|
}))}
|
||||||
on:selected={(it) => {
|
on:selected={(it) => {
|
||||||
selectedRegionId = it.detail === '#' ? '' : it.detail
|
selectedRegionId = it.detail === '#' ? '' : it.detail
|
||||||
}}
|
}}
|
||||||
@@ -291,12 +302,13 @@
|
|||||||
{#each Object.keys(dayRanges) as k}
|
{#each Object.keys(dayRanges) as k}
|
||||||
{@const v = groupped.get(k) ?? []}
|
{@const v = groupped.get(k) ?? []}
|
||||||
{@const hasMore = (groupped.get(k) ?? []).length > limit}
|
{@const hasMore = (groupped.get(k) ?? []).length > limit}
|
||||||
{@const activeV = v.filter((it) => it.mode === 'active' && (it.region ?? '') !== selectedRegionId)}
|
{@const activeV = v.filter((it) => isActiveMode(it.mode) && it.region !== selectedRegionId)}
|
||||||
{@const archivedV = v.filter((it) => it.mode === 'archived')}
|
{@const activeAll = v.filter((it) => isActiveMode(it.mode))}
|
||||||
{@const deletedV = v.filter((it) => it.mode === 'deleted')}
|
{@const archivedV = v.filter((it) => isArchivingMode(it.mode))}
|
||||||
{@const av = v.length - archivedV.length - deletedV.length}
|
{@const deletedV = v.filter((it) => isDeletingMode(it.mode))}
|
||||||
|
{@const maintenance = v.length - activeAll.length - archivedV.length - deletedV.length}
|
||||||
{#if v.length > 0}
|
{#if v.length > 0}
|
||||||
<Expandable expandable={true} bordered={true}>
|
<Expandable expandable={true} bordered={true} expanded={search.trim().length > 0}>
|
||||||
<svelte:fragment slot="title">
|
<svelte:fragment slot="title">
|
||||||
<span class="fs-title focused-button flex-row-center">
|
<span class="fs-title focused-button flex-row-center">
|
||||||
{k} -
|
{k} -
|
||||||
@@ -305,8 +317,8 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{v.length}
|
{v.length}
|
||||||
{/if}
|
{/if}
|
||||||
{#if av > 0}
|
{#if maintenance > 0}
|
||||||
- maitenance: {av}
|
- maitenance: {maintenance}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
@@ -324,18 +336,18 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="tools">
|
<svelte:fragment slot="tools">
|
||||||
{#if archivedV.length > 0}
|
{#if activeAll.length > 0}
|
||||||
<Button
|
<Button
|
||||||
icon={IconStop}
|
icon={IconStop}
|
||||||
label={getEmbeddedLabel(`Mass Archive ${archivedV.length}`)}
|
label={getEmbeddedLabel(`Mass Archive ${activeAll.length}`)}
|
||||||
kind={'ghost'}
|
kind={'ghost'}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showPopup(MessageBox, {
|
showPopup(MessageBox, {
|
||||||
label: getEmbeddedLabel(`Mass Archive ${archivedV.length}`),
|
label: getEmbeddedLabel(`Mass Archive ${activeAll.length}`),
|
||||||
message: getEmbeddedLabel(`Please confirm archive ${archivedV.length} workspaces`),
|
message: getEmbeddedLabel(`Please confirm archive ${activeAll.length} workspaces`),
|
||||||
action: async () => {
|
action: async () => {
|
||||||
void performWorkspaceOperation(
|
void performWorkspaceOperation(
|
||||||
archivedV.map((it) => it.uuid),
|
activeAll.map((it) => it.uuid),
|
||||||
'archive'
|
'archive'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -351,8 +363,8 @@
|
|||||||
label={getEmbeddedLabel(`Mass Migrate ${activeV.length} to ${selectedRegionName ?? ''}`)}
|
label={getEmbeddedLabel(`Mass Migrate ${activeV.length} to ${selectedRegionName ?? ''}`)}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showPopup(MessageBox, {
|
showPopup(MessageBox, {
|
||||||
label: getEmbeddedLabel(`Mass Migrate ${archivedV.length}`),
|
label: getEmbeddedLabel(`Mass Migrate ${activeV.length}`),
|
||||||
message: getEmbeddedLabel(`Please confirm migrate ${archivedV.length} workspaces`),
|
message: getEmbeddedLabel(`Please confirm migrate ${activeV.length} workspaces`),
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await performWorkspaceOperation(
|
await performWorkspaceOperation(
|
||||||
activeV.map((it) => it.uuid),
|
activeV.map((it) => it.uuid),
|
||||||
@@ -371,38 +383,36 @@
|
|||||||
{@const bIdx = backupIdx.get(workspace.uuid)}
|
{@const bIdx = backupIdx.get(workspace.uuid)}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- 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.uuid}`}>
|
||||||
<div class="flex p-2">
|
<Grid column={10} equalHeight={false} columnGap={0}>
|
||||||
<span class="label overflow-label flex-row-center" style:width={'12rem'}>
|
<span class="label overflow-label flex-row-center flex-grow flex-shrink">
|
||||||
{wsName}
|
{wsName}
|
||||||
<div class="ml-1">
|
<div class="ml-1">
|
||||||
<Button icon={IconOpen} size={'small'} on:click={() => select(workspace.url)} />
|
<Button icon={IconOpen} size={'small'} on:click={() => select(workspace.url)} />
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="ml-1" style:width={'12rem'}>
|
<div class="ml-1">
|
||||||
{workspace.createdBy}
|
<!-- Retrieve created by-->
|
||||||
|
<!-- {workspace.createdBy} -->
|
||||||
</div>
|
</div>
|
||||||
<span class="label overflow-label" style:width={'8rem'}>
|
<span class="label overflow-label flex-shrink p-1">
|
||||||
{workspace.region ?? ''}
|
{workspace.region ?? ''}
|
||||||
</span>
|
</span>
|
||||||
<span class="label overflow-label" style:width={'5rem'}>
|
<span class="label overflow-label flex-shrink p-1">
|
||||||
{lastUsageDays} days
|
{lastUsageDays} days
|
||||||
</span>
|
</span>
|
||||||
|
<span class="label overflow-label p-1">
|
||||||
<span class="label overflow-label" style:width={'10rem'}>
|
|
||||||
{workspace.mode ?? '-'}
|
{workspace.mode ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="label overflow-label flex-shrink">
|
||||||
<span class="label overflow-label" style:width={'2rem'}>
|
{workspace.processingAttempts}
|
||||||
{workspace.attempts}
|
|
||||||
</span>
|
</span>
|
||||||
|
<span class="flex flex-shrink">
|
||||||
<span class="flex flex-between" style:width={'5rem'}>
|
|
||||||
{#if workspace.processingProgress !== 100 && workspace.processingProgress !== 0}
|
{#if workspace.processingProgress !== 100 && workspace.processingProgress !== 0}
|
||||||
({workspace.processingProgress}%)
|
({workspace.processingProgress}%)
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
<span class="flex flex-between" style:width={'5rem'}>
|
<span class="flex flex-shrink">
|
||||||
{#if workspace.backupInfo != null}
|
{#if workspace.backupInfo != null}
|
||||||
{@const sz = Math.max(
|
{@const sz = Math.max(
|
||||||
workspace.backupInfo.backupSize,
|
workspace.backupInfo.backupSize,
|
||||||
@@ -419,7 +429,7 @@
|
|||||||
[#{bIdx}]
|
[#{bIdx}]
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
<span class="flex flex-between" style:width={'5rem'}>
|
<span class="flex flex-shrink">
|
||||||
{#if workspace.backupInfo != null}
|
{#if workspace.backupInfo != null}
|
||||||
{@const hours = Math.round((now - workspace.backupInfo.lastBackup) / (1000 * 3600))}
|
{@const hours = Math.round((now - workspace.backupInfo.lastBackup) / (1000 * 3600))}
|
||||||
|
|
||||||
@@ -430,82 +440,81 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
<div class="flex flex-grow gap-1-5 flex-shrink">
|
||||||
<div class="flex flex-grow gap-1-5 flex-between">
|
<div class="flex flex-row-center gap-1-5">
|
||||||
<div class="flex flex-row-center gap-1-5">
|
{#if workspace.mode === 'active'}
|
||||||
{#if workspace.mode === 'active'}
|
<Button
|
||||||
<Button
|
icon={IconStop}
|
||||||
icon={IconStop}
|
size={'small'}
|
||||||
size={'small'}
|
label={getEmbeddedLabel('Archive')}
|
||||||
label={getEmbeddedLabel('Archive')}
|
kind={'ghost'}
|
||||||
kind={'ghost'}
|
on:click={() => {
|
||||||
on:click={() => {
|
showPopup(MessageBox, {
|
||||||
showPopup(MessageBox, {
|
label: getEmbeddedLabel(`Archive ${workspace.url}`),
|
||||||
label: getEmbeddedLabel(`Archive ${workspace.url}`),
|
message: getEmbeddedLabel('Please confirm'),
|
||||||
message: getEmbeddedLabel('Please confirm'),
|
action: async () => {
|
||||||
action: async () => {
|
await performWorkspaceOperation(workspace.uuid, 'archive')
|
||||||
await performWorkspaceOperation(workspace.uuid, 'archive')
|
}
|
||||||
}
|
})
|
||||||
})
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
{/if}
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if workspace.mode === 'archived'}
|
{#if workspace.mode === 'archived'}
|
||||||
<Button
|
<Button
|
||||||
icon={IconStart}
|
icon={IconStart}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
kind={'ghost'}
|
kind={'ghost'}
|
||||||
label={getEmbeddedLabel('Unarchive')}
|
label={getEmbeddedLabel('Unarchive')}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showPopup(MessageBox, {
|
showPopup(MessageBox, {
|
||||||
label: getEmbeddedLabel(`Unarchive ${workspace.url}`),
|
label: getEmbeddedLabel(`Unarchive ${workspace.url}`),
|
||||||
message: getEmbeddedLabel('Please confirm'),
|
message: getEmbeddedLabel('Please confirm'),
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await performWorkspaceOperation(workspace.uuid, 'unarchive')
|
await performWorkspaceOperation(workspace.uuid, 'unarchive')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if regionInfo.length > 0 && workspace.mode === 'active' && (workspace.region ?? '') !== selectedRegionId}
|
||||||
|
<Button
|
||||||
|
icon={IconArrowRight}
|
||||||
|
size={'small'}
|
||||||
|
kind={'positive'}
|
||||||
|
label={getEmbeddedLabel('Migrate')}
|
||||||
|
on:click={() => {
|
||||||
|
showPopup(MessageBox, {
|
||||||
|
label: getEmbeddedLabel(`Migrate ${workspace.url}`),
|
||||||
|
message: getEmbeddedLabel('Please confirm'),
|
||||||
|
action: async () => {
|
||||||
|
await performWorkspaceOperation(workspace.uuid, 'migrate-to', selectedRegionId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if regionInfo.length > 0 && workspace.mode === 'active' && (workspace.region ?? '') !== selectedRegionId}
|
{#if superAdminMode && !isDeletingMode(workspace.mode) && !isArchivingMode(workspace.mode)}
|
||||||
<Button
|
<Button
|
||||||
icon={IconArrowRight}
|
icon={IconStop}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
kind={'positive'}
|
kind={'dangerous'}
|
||||||
label={getEmbeddedLabel('Migrate ' + (selectedRegionName ?? ''))}
|
label={getEmbeddedLabel('Delete')}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showPopup(MessageBox, {
|
showPopup(MessageBox, {
|
||||||
label: getEmbeddedLabel(`Migrate ${workspace.url}`),
|
label: getEmbeddedLabel(`Delete ${workspace.url}`),
|
||||||
message: getEmbeddedLabel('Please confirm'),
|
message: getEmbeddedLabel('Please confirm'),
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await performWorkspaceOperation(workspace.uuid, 'migrate-to', selectedRegionId)
|
await performWorkspaceOperation(workspace.uuid, 'delete')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
{#if superAdminMode && !isDeletingMode(workspace.mode) && !isArchivingMode(workspace.mode)}
|
|
||||||
<Button
|
|
||||||
icon={IconStop}
|
|
||||||
size={'small'}
|
|
||||||
kind={'dangerous'}
|
|
||||||
label={getEmbeddedLabel('Delete')}
|
|
||||||
on:click={() => {
|
|
||||||
showPopup(MessageBox, {
|
|
||||||
label: getEmbeddedLabel(`Delete ${workspace.url}`),
|
|
||||||
message: getEmbeddedLabel('Please confirm'),
|
|
||||||
action: async () => {
|
|
||||||
await performWorkspaceOperation(workspace.uuid, 'delete')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Expandable>
|
</Expandable>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"OpenInSidebarNewTab": "Otevřít v postranním panelu na nové kartě",
|
"OpenInSidebarNewTab": "Otevřít v postranním panelu na nové kartě",
|
||||||
"ConfigureWidgets": "Nastavit widgety",
|
"ConfigureWidgets": "Nastavit widgety",
|
||||||
"Tab": "Karta",
|
"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",
|
"OpenInSidebarNewTab": "In neuer Seitenleiste öffnen",
|
||||||
"ConfigureWidgets": "Widgets konfigurieren",
|
"ConfigureWidgets": "Widgets konfigurieren",
|
||||||
"Tab": "Tab",
|
"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",
|
"OpenInSidebarNewTab": "Open in sidebar new tab",
|
||||||
"ConfigureWidgets": "Configure widgets",
|
"ConfigureWidgets": "Configure widgets",
|
||||||
"Tab": "Tab",
|
"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",
|
"OpenInSidebarNewTab": "Abrir en una nueva pestaña de la barra lateral",
|
||||||
"ConfigureWidgets": "Configurar widgets",
|
"ConfigureWidgets": "Configurar widgets",
|
||||||
"Tab": "Pestaña",
|
"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",
|
"OpenInSidebarNewTab": "Ouvrir dans un nouvel onglet de la barre latérale",
|
||||||
"ConfigureWidgets": "Configurer les widgets",
|
"ConfigureWidgets": "Configurer les widgets",
|
||||||
"Tab": "Onglet",
|
"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",
|
"MobileNotSupported": "Spiacenti, il supporto per i dispositivi mobili arriverà presto. Nel frattempo, si prega di utilizzare un computer desktop",
|
||||||
"LogInAnyway": "Accedi comunque",
|
"LogInAnyway": "Accedi comunque",
|
||||||
"WorkspaceCreating": "Creazione in corso...",
|
"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",
|
"OpenInSidebarNewTab": "Abrir em uma nova aba da barra lateral",
|
||||||
"ConfigureWidgets": "Configurar widgets",
|
"ConfigureWidgets": "Configurar widgets",
|
||||||
"Tab": "Aba",
|
"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": "Открыть в новой вкладке боковой панели",
|
"OpenInSidebarNewTab": "Открыть в новой вкладке боковой панели",
|
||||||
"ConfigureWidgets": "Настроить виджеты",
|
"ConfigureWidgets": "Настроить виджеты",
|
||||||
"Tab": "Вкладка",
|
"Tab": "Вкладка",
|
||||||
"WorkspaceIsArchived": "Рабочее пространство архивировано из-за неиспользования, пожалуйста, свяжитесь с нами для восстановления..."
|
"WorkspaceIsArchived": "Рабочее пространство архивировано из-за неиспользования, пожалуйста, свяжитесь с нами для восстановления...",
|
||||||
|
"WorkspaceIsMigrating": "Рабочее пространство обновляется. Пожалуйста, подождите..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"OpenInSidebarNewTab": "在侧边栏新标签页中打开",
|
"OpenInSidebarNewTab": "在侧边栏新标签页中打开",
|
||||||
"ConfigureWidgets": "配置小部件",
|
"ConfigureWidgets": "配置小部件",
|
||||||
"Tab": "选项卡",
|
"Tab": "选项卡",
|
||||||
"WorkspaceIsArchived": "工作区因未使用而归档,请与我们联系以恢复..."
|
"WorkspaceIsArchived": "工作区因未使用而归档,请与我们联系以恢复...",
|
||||||
|
"WorkspaceIsMigrating": "工作区正在更新。请稍候..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,9 +213,9 @@
|
|||||||
<div class="p-3">2.</div>
|
<div class="p-3">2.</div>
|
||||||
<Button
|
<Button
|
||||||
icon={IconArrowRight}
|
icon={IconArrowRight}
|
||||||
label={getEmbeddedLabel('Reboot server')}
|
label={getEmbeddedLabel('Reboot workspace')}
|
||||||
on:click={() => {
|
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'
|
method: 'PUT'
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -243,6 +243,17 @@ export async function connect (title: string): Promise<Client | undefined> {
|
|||||||
onArchived: () => {
|
onArchived: () => {
|
||||||
translateCB(plugin.string.WorkspaceIsArchived, {}, get(themeStore).language, (r) => {
|
translateCB(plugin.string.WorkspaceIsArchived, {}, get(themeStore).language, (r) => {
|
||||||
versionError.set(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.
|
// We need to refresh all active live queries and clear old queries.
|
||||||
|
|||||||
@@ -256,7 +256,8 @@ export default plugin(workbenchId, {
|
|||||||
OpenInSidebar: '' as IntlString,
|
OpenInSidebar: '' as IntlString,
|
||||||
OpenInSidebarNewTab: '' as IntlString,
|
OpenInSidebarNewTab: '' as IntlString,
|
||||||
ConfigureWidgets: '' as IntlString,
|
ConfigureWidgets: '' as IntlString,
|
||||||
WorkspaceIsArchived: '' as IntlString
|
WorkspaceIsArchived: '' as IntlString,
|
||||||
|
WorkspaceIsMigrating: '' as IntlString
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
Search: '' as Asset
|
Search: '' as Asset
|
||||||
|
|||||||
@@ -1140,6 +1140,11 @@
|
|||||||
"projectFolder": "tests/sanity",
|
"projectFolder": "tests/sanity",
|
||||||
"shouldPublish": false
|
"shouldPublish": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"packageName": "@hcengineering/tests-ws-sanity",
|
||||||
|
"projectFolder": "ws-tests/sanity",
|
||||||
|
"shouldPublish": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"packageName": "@hcengineering/qms-tests-sanity",
|
"packageName": "@hcengineering/qms-tests-sanity",
|
||||||
"projectFolder": "qms-tests/sanity",
|
"projectFolder": "qms-tests/sanity",
|
||||||
|
|||||||
@@ -241,7 +241,6 @@ export class WorkspaceStatusMongoDbCollection implements DbCollection<WorkspaceS
|
|||||||
|
|
||||||
for (const key of Object.keys(query)) {
|
for (const key of Object.keys(query)) {
|
||||||
const qVal = (query as any)[key]
|
const qVal = (query as any)[key]
|
||||||
|
|
||||||
if (key === 'workspaceUuid') {
|
if (key === 'workspaceUuid') {
|
||||||
res.uuid = qVal
|
res.uuid = qVal
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,22 +15,22 @@
|
|||||||
|
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
import {
|
import {
|
||||||
|
AccountRole,
|
||||||
concatLink,
|
concatLink,
|
||||||
Data,
|
Data,
|
||||||
|
isActiveMode,
|
||||||
isWorkspaceCreating,
|
isWorkspaceCreating,
|
||||||
MeasureContext,
|
MeasureContext,
|
||||||
|
SocialIdType,
|
||||||
systemAccountUuid,
|
systemAccountUuid,
|
||||||
Version,
|
Version,
|
||||||
SocialIdType,
|
|
||||||
type BackupStatus,
|
type BackupStatus,
|
||||||
type Branding,
|
type Branding,
|
||||||
AccountRole,
|
|
||||||
type Person,
|
type Person,
|
||||||
type WorkspaceMemberInfo,
|
|
||||||
type PersonUuid,
|
type PersonUuid,
|
||||||
isActiveMode,
|
type WorkspaceMemberInfo,
|
||||||
type WorkspaceUuid,
|
type WorkspaceMode,
|
||||||
type WorkspaceMode
|
type WorkspaceUuid
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import platform, {
|
import platform, {
|
||||||
getMetadata,
|
getMetadata,
|
||||||
@@ -45,48 +45,48 @@ import { decodeTokenVerbose, generateToken } from '@hcengineering/server-token'
|
|||||||
import { accountPlugin } from './plugin'
|
import { accountPlugin } from './plugin'
|
||||||
import type {
|
import type {
|
||||||
AccountDB,
|
AccountDB,
|
||||||
|
AccountMethodHandler,
|
||||||
LoginInfo,
|
LoginInfo,
|
||||||
OtpInfo,
|
OtpInfo,
|
||||||
RegionInfo,
|
RegionInfo,
|
||||||
WorkspaceEvent,
|
|
||||||
WorkspaceOperation,
|
|
||||||
AccountMethodHandler,
|
|
||||||
WorkspaceLoginInfo,
|
|
||||||
WorkspaceInfoWithStatus,
|
|
||||||
WorkspaceStatus,
|
|
||||||
SocialId,
|
SocialId,
|
||||||
Workspace
|
Workspace,
|
||||||
|
WorkspaceEvent,
|
||||||
|
WorkspaceInfoWithStatus,
|
||||||
|
WorkspaceLoginInfo,
|
||||||
|
WorkspaceOperation,
|
||||||
|
WorkspaceStatus
|
||||||
} from './types'
|
} from './types'
|
||||||
import {
|
import {
|
||||||
|
checkInvite,
|
||||||
cleanEmail,
|
cleanEmail,
|
||||||
EndpointKind,
|
confirmEmail,
|
||||||
getEndpoint,
|
|
||||||
getRegions,
|
|
||||||
verifyPassword,
|
|
||||||
isOtpValid,
|
|
||||||
createAccount,
|
createAccount,
|
||||||
|
createWorkspaceRecord,
|
||||||
|
doJoinByInvite,
|
||||||
|
EndpointKind,
|
||||||
|
getAccount,
|
||||||
|
getEmailSocialId,
|
||||||
|
getEndpoint,
|
||||||
|
getFrontUrl,
|
||||||
|
getRegions,
|
||||||
|
getRolePower,
|
||||||
|
getSesUrl,
|
||||||
|
getSocialIdByKey,
|
||||||
|
getWorkspaceById,
|
||||||
|
getWorkspaceInfoWithStatusById,
|
||||||
|
getWorkspaceInvite,
|
||||||
|
getWorkspaces,
|
||||||
|
getWorkspacesInfoWithStatusByIds,
|
||||||
|
GUEST_ACCOUNT,
|
||||||
|
isOtpValid,
|
||||||
|
selectWorkspace,
|
||||||
|
sendEmailConfirmation,
|
||||||
sendOtp,
|
sendOtp,
|
||||||
setPassword,
|
setPassword,
|
||||||
wrap,
|
|
||||||
createWorkspaceRecord,
|
|
||||||
checkInvite,
|
|
||||||
GUEST_ACCOUNT,
|
|
||||||
getWorkspaceById,
|
|
||||||
getWorkspaceInvite,
|
|
||||||
getRolePower,
|
|
||||||
sendEmailConfirmation,
|
|
||||||
getEmailSocialId,
|
|
||||||
confirmEmail,
|
|
||||||
getAccount,
|
|
||||||
getSesUrl,
|
|
||||||
getFrontUrl,
|
|
||||||
getWorkspaceInfoWithStatusById,
|
|
||||||
signUpByEmail,
|
signUpByEmail,
|
||||||
selectWorkspace,
|
verifyPassword,
|
||||||
doJoinByInvite,
|
wrap
|
||||||
getWorkspacesInfoWithStatusByIds,
|
|
||||||
getSocialIdByKey,
|
|
||||||
getWorkspaces
|
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
// Move to config?
|
// Move to config?
|
||||||
@@ -857,7 +857,7 @@ export async function performWorkspaceOperation (
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { extra } = decodeTokenVerbose(ctx, token)
|
const { extra } = decodeTokenVerbose(ctx, token)
|
||||||
|
|
||||||
if (extra?.service !== 'admin') {
|
if (extra?.admin !== 'true') {
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,7 +874,7 @@ export async function performWorkspaceOperation (
|
|||||||
switch (event) {
|
switch (event) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
if (workspace.status.mode !== 'active') {
|
if (workspace.status.mode !== 'active') {
|
||||||
throw new PlatformError(unknownError('Archive allowed only for active workspaces'))
|
throw new PlatformError(unknownError('Delete allowed only for active workspaces'))
|
||||||
}
|
}
|
||||||
|
|
||||||
update.mode = 'pending-deletion'
|
update.mode = 'pending-deletion'
|
||||||
@@ -883,7 +883,7 @@ export async function performWorkspaceOperation (
|
|||||||
update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step
|
update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step
|
||||||
break
|
break
|
||||||
case 'archive':
|
case 'archive':
|
||||||
if (isActiveMode(workspace.status.mode)) {
|
if (!isActiveMode(workspace.status.mode)) {
|
||||||
throw new PlatformError(unknownError('Archiving allowed only for active workspaces'))
|
throw new PlatformError(unknownError('Archiving allowed only for active workspaces'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,7 +921,7 @@ export async function performWorkspaceOperation (
|
|||||||
|
|
||||||
update.mode = 'migration-pending-backup'
|
update.mode = 'migration-pending-backup'
|
||||||
// NOTE: will only work for Mongo accounts
|
// NOTE: will only work for Mongo accounts
|
||||||
;(update as any).targetRegion = params[0]
|
update.targetRegion = params[0]
|
||||||
update.processingAttempts = 0
|
update.processingAttempts = 0
|
||||||
update.processingProgress = 0
|
update.processingProgress = 0
|
||||||
update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step
|
update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step
|
||||||
@@ -1278,7 +1278,7 @@ export async function updateWorkspaceInfo (
|
|||||||
update.processingProgress = progress
|
update.processingProgress = progress
|
||||||
break
|
break
|
||||||
case 'migrate-clean-done':
|
case 'migrate-clean-done':
|
||||||
wsUpdate.region = (workspace as any).targetRegion ?? ''
|
wsUpdate.region = workspace.status.targetRegion ?? ''
|
||||||
update.mode = 'pending-restore'
|
update.mode = 'pending-restore'
|
||||||
update.processingProgress = progress
|
update.processingProgress = progress
|
||||||
update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step
|
update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step
|
||||||
@@ -1323,8 +1323,8 @@ export async function updateWorkspaceInfo (
|
|||||||
await db.workspaceStatus.updateOne(
|
await db.workspaceStatus.updateOne(
|
||||||
{ workspaceUuid: workspace.uuid },
|
{ workspaceUuid: workspace.uuid },
|
||||||
{
|
{
|
||||||
...update,
|
lastProcessingTime: Date.now(), // Some operations override it.
|
||||||
lastProcessingTime: Date.now()
|
...update
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ export interface WorkspaceStatus {
|
|||||||
processingAttempts?: number
|
processingAttempts?: number
|
||||||
processingMessage?: string
|
processingMessage?: string
|
||||||
backupInfo?: BackupStatus
|
backupInfo?: BackupStatus
|
||||||
|
|
||||||
|
targetRegion?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Workspace {
|
export interface Workspace {
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import {
|
|||||||
systemAccountUuid,
|
systemAccountUuid,
|
||||||
type WorkspaceInfoWithStatus as WorkspaceInfoWithStatusCore,
|
type WorkspaceInfoWithStatus as WorkspaceInfoWithStatusCore,
|
||||||
isActiveMode,
|
isActiveMode,
|
||||||
isArchivingMode,
|
|
||||||
isMigrationMode,
|
|
||||||
type PersonUuid
|
type PersonUuid
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getMongoClient } from '@hcengineering/mongo' // TODO: get rid of this import later
|
import { getMongoClient } from '@hcengineering/mongo' // TODO: get rid of this import later
|
||||||
@@ -560,19 +558,6 @@ export async function selectWorkspace (
|
|||||||
|
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUrl }))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUrl }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArchivingMode(wsStatus.mode) || isMigrationMode(wsStatus.mode)) {
|
|
||||||
const result: WorkspaceLoginInfo = {
|
|
||||||
account: accountUuid,
|
|
||||||
endpoint: '',
|
|
||||||
token: generateToken(accountUuid, workspace.uuid),
|
|
||||||
workspace: workspace.uuid,
|
|
||||||
workspaceUrl: workspace.url,
|
|
||||||
workspaceDataId: workspace.dataId,
|
|
||||||
role
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ export async function backupWorkspace (
|
|||||||
externalStorage: StorageAdapter
|
externalStorage: StorageAdapter
|
||||||
) => DbConfiguration,
|
) => DbConfiguration,
|
||||||
region: string,
|
region: string,
|
||||||
freshBackup: boolean = false,
|
|
||||||
clean: boolean = false,
|
|
||||||
downloadLimit: number,
|
downloadLimit: number,
|
||||||
contextVars: Record<string, any>,
|
contextVars: Record<string, any>,
|
||||||
|
|
||||||
@@ -129,8 +127,6 @@ export async function backupWorkspace (
|
|||||||
return getConfig(ctx, mainDbUrl, workspace, branding, externalStorage)
|
return getConfig(ctx, mainDbUrl, workspace, branding, externalStorage)
|
||||||
},
|
},
|
||||||
region,
|
region,
|
||||||
freshBackup,
|
|
||||||
clean,
|
|
||||||
downloadLimit,
|
downloadLimit,
|
||||||
[],
|
[],
|
||||||
contextVars
|
contextVars
|
||||||
|
|||||||
@@ -672,8 +672,6 @@ export async function backup (
|
|||||||
include?: Set<string>
|
include?: Set<string>
|
||||||
skipDomains: string[]
|
skipDomains: string[]
|
||||||
force: boolean
|
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
|
timeout: number
|
||||||
connectTimeout: number
|
connectTimeout: number
|
||||||
skipBlobContentTypes: string[]
|
skipBlobContentTypes: string[]
|
||||||
@@ -687,8 +685,6 @@ export async function backup (
|
|||||||
token?: string
|
token?: string
|
||||||
} = {
|
} = {
|
||||||
force: false,
|
force: false,
|
||||||
freshBackup: false,
|
|
||||||
clean: false,
|
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
skipDomains: [],
|
skipDomains: [],
|
||||||
connectTimeout: 30000,
|
connectTimeout: 30000,
|
||||||
@@ -706,7 +702,6 @@ export async function backup (
|
|||||||
ctx = ctx.newChild('backup', {
|
ctx = ctx.newChild('backup', {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
force: options.force,
|
force: options.force,
|
||||||
recheck: options.freshBackup,
|
|
||||||
timeout: options.timeout
|
timeout: options.timeout
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -767,7 +762,7 @@ export async function backup (
|
|||||||
|
|
||||||
let lastTxChecked = false
|
let lastTxChecked = false
|
||||||
// Skip backup if there is no transaction changes.
|
// Skip backup if there is no transaction changes.
|
||||||
if (options.getLastTx !== undefined && !options.freshBackup) {
|
if (options.getLastTx !== undefined) {
|
||||||
lastTx = await options.getLastTx()
|
lastTx = await options.getLastTx()
|
||||||
if (lastTx !== undefined) {
|
if (lastTx !== undefined) {
|
||||||
if (lastTx._id === backupInfo.lastTxId && !options.force) {
|
if (lastTx._id === backupInfo.lastTxId && !options.force) {
|
||||||
@@ -793,7 +788,7 @@ export async function backup (
|
|||||||
? await options.getConnection()
|
? await options.getConnection()
|
||||||
: ((await createClient(transactorUrl, token, undefined, options.connectTimeout)) as CoreClient & BackupClient)
|
: ((await createClient(transactorUrl, token, undefined, options.connectTimeout)) as CoreClient & BackupClient)
|
||||||
|
|
||||||
if (!lastTxChecked && !options.freshBackup) {
|
if (!lastTxChecked) {
|
||||||
lastTx = await connection.findOne(
|
lastTx = await connection.findOne(
|
||||||
core.class.Tx,
|
core.class.Tx,
|
||||||
{ objectSpace: { $ne: core.space.Model } },
|
{ objectSpace: { $ne: core.space.Model } },
|
||||||
@@ -932,7 +927,7 @@ export async function backup (
|
|||||||
if (digest.delete(id as Ref<Doc>)) {
|
if (digest.delete(id as Ref<Doc>)) {
|
||||||
oldHash.set(id as Ref<Doc>, currentHash)
|
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>)) {
|
if (changes.updated.has(id as Ref<Doc>)) {
|
||||||
removeFromNeedRetrieve(needRetrieve, id as Ref<Doc>)
|
removeFromNeedRetrieve(needRetrieve, id as Ref<Doc>)
|
||||||
}
|
}
|
||||||
@@ -1355,36 +1350,6 @@ export async function backup (
|
|||||||
if (!canceled()) {
|
if (!canceled()) {
|
||||||
backupInfo.lastTxId = lastTx?._id ?? '0' // We could store last tx, since full backup is complete
|
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 }))
|
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> => {
|
const addFileSize = async (file: string | undefined | null): Promise<void> => {
|
||||||
@@ -1735,7 +1700,7 @@ export async function restore (
|
|||||||
try {
|
try {
|
||||||
let serverEndpoint = await getTransactorEndpoint(token, 'external')
|
let serverEndpoint = await getTransactorEndpoint(token, 'external')
|
||||||
serverEndpoint = serverEndpoint.replaceAll('wss://', 'https://').replace('ws://', 'http://')
|
serverEndpoint = serverEndpoint.replaceAll('wss://', 'https://').replace('ws://', 'http://')
|
||||||
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close&wsId=${workspaceId}`, {
|
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ class BackupWorker {
|
|||||||
) => DbConfiguration,
|
) => DbConfiguration,
|
||||||
readonly region: string,
|
readonly region: string,
|
||||||
readonly contextVars: Record<string, any>,
|
readonly contextVars: Record<string, any>,
|
||||||
readonly freshWorkspace: boolean = false,
|
|
||||||
readonly clean: boolean = false,
|
|
||||||
readonly skipDomains: string[] = []
|
readonly skipDomains: string[] = []
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -282,8 +280,6 @@ class BackupWorker {
|
|||||||
backup(ctx, '', wsIds, storage, {
|
backup(ctx, '', wsIds, storage, {
|
||||||
skipDomains: this.skipDomains,
|
skipDomains: this.skipDomains,
|
||||||
force: true,
|
force: true,
|
||||||
freshBackup: this.freshWorkspace,
|
|
||||||
clean: this.clean,
|
|
||||||
timeout: this.config.Timeout * 1000,
|
timeout: this.config.Timeout * 1000,
|
||||||
connectTimeout: 5 * 60 * 1000, // 5 minutes to,
|
connectTimeout: 5 * 60 * 1000, // 5 minutes to,
|
||||||
blobDownloadLimit: this.downloadLimit,
|
blobDownloadLimit: this.downloadLimit,
|
||||||
@@ -408,8 +404,6 @@ export async function doBackupWorkspace (
|
|||||||
externalStorage: StorageAdapter
|
externalStorage: StorageAdapter
|
||||||
) => DbConfiguration,
|
) => DbConfiguration,
|
||||||
region: string,
|
region: string,
|
||||||
freshWorkspace: boolean,
|
|
||||||
clean: boolean,
|
|
||||||
downloadLimit: number,
|
downloadLimit: number,
|
||||||
skipDomains: string[],
|
skipDomains: string[],
|
||||||
contextVars: Record<string, any>,
|
contextVars: Record<string, any>,
|
||||||
@@ -423,8 +417,6 @@ export async function doBackupWorkspace (
|
|||||||
getConfig,
|
getConfig,
|
||||||
region,
|
region,
|
||||||
contextVars,
|
contextVars,
|
||||||
freshWorkspace,
|
|
||||||
clean,
|
|
||||||
skipDomains
|
skipDomains
|
||||||
)
|
)
|
||||||
backupWorker.downloadLimit = downloadLimit
|
backupWorker.downloadLimit = downloadLimit
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type Account,
|
type Account,
|
||||||
type PersonId,
|
|
||||||
type Branding,
|
type Branding,
|
||||||
type Class,
|
type Class,
|
||||||
type Doc,
|
type Doc,
|
||||||
@@ -29,6 +28,8 @@ import {
|
|||||||
type MeasureContext,
|
type MeasureContext,
|
||||||
type ModelDb,
|
type ModelDb,
|
||||||
type Obj,
|
type Obj,
|
||||||
|
type PersonId,
|
||||||
|
type PersonUuid,
|
||||||
type Ref,
|
type Ref,
|
||||||
type SearchOptions,
|
type SearchOptions,
|
||||||
type SearchQuery,
|
type SearchQuery,
|
||||||
@@ -39,10 +40,9 @@ import {
|
|||||||
type Tx,
|
type Tx,
|
||||||
type TxFactory,
|
type TxFactory,
|
||||||
type TxResult,
|
type TxResult,
|
||||||
type WorkspaceUuid,
|
|
||||||
type WorkspaceIds,
|
|
||||||
type WorkspaceDataId,
|
type WorkspaceDataId,
|
||||||
type PersonUuid
|
type WorkspaceIds,
|
||||||
|
type WorkspaceUuid
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import type { Asset, Resource } from '@hcengineering/platform'
|
import type { Asset, Resource } from '@hcengineering/platform'
|
||||||
import type { LiveQuery } from '@hcengineering/query'
|
import type { LiveQuery } from '@hcengineering/query'
|
||||||
@@ -649,8 +649,8 @@ export interface AddSessionActive {
|
|||||||
}
|
}
|
||||||
export type AddSessionResponse =
|
export type AddSessionResponse =
|
||||||
| AddSessionActive
|
| AddSessionActive
|
||||||
| { upgrade: true }
|
| { upgrade: true, progress?: number }
|
||||||
| { error: any, terminate?: boolean, archived?: boolean }
|
| { error: any, terminate?: boolean, specialError?: 'archived' | 'migration' }
|
||||||
|
|
||||||
export type SessionFactory = (token: Token, workspace: Workspace, account: Account) => Session
|
export type SessionFactory = (token: Token, workspace: Workspace, account: Account) => Session
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
SessionManager,
|
SessionManager,
|
||||||
StorageAdapter,
|
StorageAdapter,
|
||||||
type SessionFactory,
|
type SessionFactory,
|
||||||
|
type AddSessionResponse,
|
||||||
type ClientSessionCtx,
|
type ClientSessionCtx,
|
||||||
type ConnectionSocket,
|
type ConnectionSocket,
|
||||||
type Session,
|
type Session,
|
||||||
@@ -355,11 +356,7 @@ class TSessionManager implements SessionManager {
|
|||||||
rawToken: string,
|
rawToken: string,
|
||||||
pipelineFactory: PipelineFactory,
|
pipelineFactory: PipelineFactory,
|
||||||
sessionId: string | undefined
|
sessionId: string | undefined
|
||||||
): Promise<
|
): Promise<AddSessionResponse> {
|
||||||
| { session: Session, context: MeasureContext, workspaceId: WorkspaceUuid }
|
|
||||||
| { upgrade: true, progress?: number }
|
|
||||||
| { error: any, terminate?: boolean, archived?: boolean }
|
|
||||||
> {
|
|
||||||
const { workspace: workspaceUuid } = token
|
const { workspace: workspaceUuid } = token
|
||||||
|
|
||||||
let workspaceInfo: WorkspaceInfoWithStatus | undefined
|
let workspaceInfo: WorkspaceInfoWithStatus | undefined
|
||||||
@@ -376,15 +373,15 @@ class TSessionManager implements SessionManager {
|
|||||||
|
|
||||||
if (isArchivingMode(workspaceInfo.mode)) {
|
if (isArchivingMode(workspaceInfo.mode)) {
|
||||||
// No access to disabled workspaces for regular users
|
// 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)) {
|
if (isMigrationMode(workspaceInfo.mode)) {
|
||||||
// No access to disabled workspaces for regular users
|
// 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)) {
|
if (isRestoringMode(workspaceInfo.mode)) {
|
||||||
// No access to disabled workspaces for regular users
|
// 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.isDisabled === true && token.account !== systemAccountUuid && token.extra?.admin !== 'true') {
|
if (workspaceInfo.isDisabled === true && token.account !== systemAccountUuid && token.extra?.admin !== 'true') {
|
||||||
@@ -836,11 +833,14 @@ class TSessionManager implements SessionManager {
|
|||||||
async forceClose (wsId: WorkspaceUuid, ignoreSocket?: ConnectionSocket): Promise<void> {
|
async forceClose (wsId: WorkspaceUuid, ignoreSocket?: ConnectionSocket): Promise<void> {
|
||||||
const ws = this.workspaces.get(wsId)
|
const ws = this.workspaces.get(wsId)
|
||||||
if (ws !== undefined) {
|
if (ws !== undefined) {
|
||||||
|
this.ctx.warn('force-close', { name: ws.workspaceName })
|
||||||
ws.upgrade = true // We need to similare upgrade to refresh all clients.
|
ws.upgrade = true // We need to similare upgrade to refresh all clients.
|
||||||
ws.closing = this.closeAll(wsId, ws, 99, 'force-close', ignoreSocket)
|
ws.closing = this.closeAll(wsId, ws, 99, 'force-close', ignoreSocket)
|
||||||
this.workspaces.delete(wsId)
|
this.workspaces.delete(wsId)
|
||||||
await ws.closing
|
await ws.closing
|
||||||
ws.closing = undefined
|
ws.closing = undefined
|
||||||
|
} else {
|
||||||
|
this.ctx.warn('force-close-unknown', { wsId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ export async function upgradeModel (
|
|||||||
const token = generateToken(systemAccountUuid, wsIds.uuid, { service: 'tool', admin: 'true' })
|
const token = generateToken(systemAccountUuid, wsIds.uuid, { service: 'tool', admin: 'true' })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close&wsId=${wsIds.uuid}`, {
|
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -12,23 +12,28 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
import { getClient as getAccountClient } from '@hcengineering/account-client'
|
||||||
import {
|
import {
|
||||||
|
getBranding,
|
||||||
|
isArchivingMode,
|
||||||
|
isMigrationMode,
|
||||||
|
isRestoringMode,
|
||||||
|
systemAccountUuid,
|
||||||
type BrandingMap,
|
type BrandingMap,
|
||||||
type Data,
|
type Data,
|
||||||
type MeasureContext,
|
type MeasureContext,
|
||||||
type Tx,
|
type Tx,
|
||||||
type Version,
|
type Version,
|
||||||
type WorkspaceUpdateEvent,
|
|
||||||
isArchivingMode,
|
|
||||||
isMigrationMode,
|
|
||||||
isRestoringMode,
|
|
||||||
type WorkspaceInfoWithStatus,
|
type WorkspaceInfoWithStatus,
|
||||||
getBranding,
|
type WorkspaceUpdateEvent,
|
||||||
systemAccountUuid
|
type WorkspaceUuid
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { type MigrateOperation, type ModelLogger } from '@hcengineering/model'
|
import { type MigrateOperation, type ModelLogger } from '@hcengineering/model'
|
||||||
import { getClient as getAccountClient } from '@hcengineering/account-client'
|
import {
|
||||||
import { withRetryConnUntilSuccess, withRetryConnUntilTimeout } from '@hcengineering/server-client'
|
getTransactorEndpoint,
|
||||||
|
withRetryConnUntilSuccess,
|
||||||
|
withRetryConnUntilTimeout
|
||||||
|
} from '@hcengineering/server-client'
|
||||||
import { generateToken } from '@hcengineering/server-token'
|
import { generateToken } from '@hcengineering/server-token'
|
||||||
import { FileModelLogger, prepareTools } from '@hcengineering/server-tool'
|
import { FileModelLogger, prepareTools } from '@hcengineering/server-tool'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -420,6 +425,20 @@ export class WorkspaceWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendTransactorMaitenance (token: string, ws: WorkspaceUuid): Promise<void> {
|
||||||
|
try {
|
||||||
|
let serverEndpoint = await getTransactorEndpoint(token)
|
||||||
|
serverEndpoint = serverEndpoint.replaceAll('wss://', 'https://').replace('ws://', 'http://')
|
||||||
|
console.log('sending event', serverEndpoint, ws)
|
||||||
|
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=force-close`, {
|
||||||
|
method: 'PUT'
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log(err)
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async doWorkspaceOperation (
|
private async doWorkspaceOperation (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
workspace: WorkspaceInfoWithStatus,
|
workspace: WorkspaceInfoWithStatus,
|
||||||
@@ -454,6 +473,8 @@ export class WorkspaceWorker {
|
|||||||
case 'archiving-pending-backup':
|
case 'archiving-pending-backup':
|
||||||
case 'archiving-backup': {
|
case 'archiving-backup': {
|
||||||
await sendEvent('archiving-backup-started', 0)
|
await sendEvent('archiving-backup-started', 0)
|
||||||
|
|
||||||
|
await this.sendTransactorMaitenance(token, workspace.uuid)
|
||||||
if (await this.doBackup(ctx, workspace, opt, true)) {
|
if (await this.doBackup(ctx, workspace, opt, true)) {
|
||||||
await sendEvent('archiving-backup-done', 100)
|
await sendEvent('archiving-backup-done', 100)
|
||||||
}
|
}
|
||||||
@@ -476,6 +497,7 @@ export class WorkspaceWorker {
|
|||||||
case 'deleting': {
|
case 'deleting': {
|
||||||
// We should remove DB, not storages.
|
// We should remove DB, not storages.
|
||||||
await sendEvent('delete-started', 0)
|
await sendEvent('delete-started', 0)
|
||||||
|
await this.sendTransactorMaitenance(token, workspace.uuid)
|
||||||
try {
|
try {
|
||||||
await this.doCleanup(ctx, workspace, true)
|
await this.doCleanup(ctx, workspace, true)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -489,6 +511,7 @@ export class WorkspaceWorker {
|
|||||||
case 'migration-pending-backup':
|
case 'migration-pending-backup':
|
||||||
case 'migration-backup':
|
case 'migration-backup':
|
||||||
await sendEvent('migrate-backup-started', 0)
|
await sendEvent('migrate-backup-started', 0)
|
||||||
|
await this.sendTransactorMaitenance(token, workspace.uuid)
|
||||||
if (await this.doBackup(ctx, workspace, opt, false)) {
|
if (await this.doBackup(ctx, workspace, opt, false)) {
|
||||||
await sendEvent('migrate-backup-done', 100)
|
await sendEvent('migrate-backup-done', 100)
|
||||||
}
|
}
|
||||||
@@ -497,6 +520,7 @@ export class WorkspaceWorker {
|
|||||||
case 'migration-clean': {
|
case 'migration-clean': {
|
||||||
// We should remove DB, not storages.
|
// We should remove DB, not storages.
|
||||||
await sendEvent('migrate-clean-started', 0)
|
await sendEvent('migrate-clean-started', 0)
|
||||||
|
await this.sendTransactorMaitenance(token, workspace.uuid)
|
||||||
try {
|
try {
|
||||||
await this.doCleanup(ctx, workspace, false)
|
await this.doCleanup(ctx, workspace, false)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -590,8 +614,6 @@ export class WorkspaceWorker {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
this.region,
|
this.region,
|
||||||
archive,
|
|
||||||
archive,
|
|
||||||
50000,
|
50000,
|
||||||
['blob'],
|
['blob'],
|
||||||
sharedPipelineContextVars,
|
sharedPipelineContextVars,
|
||||||
@@ -604,7 +626,7 @@ export class WorkspaceWorker {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (result) {
|
if (result) {
|
||||||
console.log('backup completed')
|
ctx.info('backup completed')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -681,7 +703,7 @@ export class WorkspaceWorker {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (result) {
|
if (result) {
|
||||||
console.log('backup completed')
|
ctx.info('restore completed')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export function startHttpServer (
|
|||||||
}
|
}
|
||||||
case 'force-close': {
|
case 'force-close': {
|
||||||
const wsId = req.query.wsId as WorkspaceUuid
|
const wsId = req.query.wsId as WorkspaceUuid
|
||||||
void sessions.forceClose(wsId)
|
void sessions.forceClose(wsId ?? payload.workspace)
|
||||||
res.writeHead(200)
|
res.writeHead(200)
|
||||||
res.end()
|
res.end()
|
||||||
return
|
return
|
||||||
@@ -433,7 +433,7 @@ export function startHttpServer (
|
|||||||
if (webSocketData.session instanceof Promise) {
|
if (webSocketData.session instanceof Promise) {
|
||||||
void webSocketData.session.then((s) => {
|
void webSocketData.session.then((s) => {
|
||||||
if ('error' in s) {
|
if ('error' in s) {
|
||||||
if (s.archived === true) {
|
if (s.specialError === 'archived') {
|
||||||
cs.send(
|
cs.send(
|
||||||
ctx,
|
ctx,
|
||||||
{
|
{
|
||||||
@@ -446,6 +446,19 @@ export function startHttpServer (
|
|||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
} else if (s.specialError === 'migration') {
|
||||||
|
cs.send(
|
||||||
|
ctx,
|
||||||
|
{
|
||||||
|
id: -1,
|
||||||
|
error: new Status(Severity.ERROR, platform.status.WorkspaceMigration, {
|
||||||
|
workspaceUuid: token.workspace
|
||||||
|
}),
|
||||||
|
terminate: s.terminate
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
cs.send(
|
cs.send(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -41,7 +41,15 @@ export class ApiEndpoint {
|
|||||||
return token
|
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 token = await this.loginAndGetToken(username, password)
|
||||||
const url = this.baseUrl
|
const url = this.baseUrl
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -50,7 +58,7 @@ export class ApiEndpoint {
|
|||||||
}
|
}
|
||||||
const headers = this.getDefaultHeaders(token)
|
const headers = this.getDefaultHeaders(token)
|
||||||
const response = await this.request.post(url, { data: payload, headers })
|
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> {
|
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 './utils'
|
||||||
export * from './model/tracker/issues-page'
|
export * from './model/tracker/issues-page'
|
||||||
export * from './model/tracker/issues-details-page'
|
export * from './model/tracker/issues-details-page'
|
||||||
|
export * from './model/tracker/types'
|
||||||
export * from './model/tracker/common-tracker-page'
|
export * from './model/tracker/common-tracker-page'
|
||||||
export * from './model/tracker/tracker-navigation-menu-page'
|
export * from './model/tracker/tracker-navigation-menu-page'
|
||||||
export * from './model/profile/user-profile-page'
|
export * from './model/profile/user-profile-page'
|
||||||
|
export * from './API/Api'
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export class LoginPage {
|
|||||||
await (await this.page.goto(`${PlatformURI}/login/login`))?.finished()
|
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> {
|
async clickSignUp (): Promise<void> {
|
||||||
await this.linkSignUp().click()
|
await this.linkSignUp().click()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { LoginPage } from '../model/login-page'
|
import { LoginPage } from '../model/login-page'
|
||||||
import { DefaultWorkspace, generateId, PlatformURI, PlatformUser } from '../utils'
|
import { DefaultWorkspace, generateId, PlatformURI, PlatformUser } from '../utils'
|
||||||
import { SelectWorkspacePage } from '../model/select-workspace-page'
|
import { SelectWorkspacePage } from '../model/select-workspace-page'
|
||||||
@@ -10,8 +10,6 @@ import { IssuesPage } from '../model/tracker/issues-page'
|
|||||||
import { IssuesDetailsPage } from '../model/tracker/issues-details-page'
|
import { IssuesDetailsPage } from '../model/tracker/issues-details-page'
|
||||||
import { TrackerNavigationMenuPage } from '../model/tracker/tracker-navigation-menu-page'
|
import { TrackerNavigationMenuPage } from '../model/tracker/tracker-navigation-menu-page'
|
||||||
import { SignInJoinPage } from '../model/signin-page'
|
import { SignInJoinPage } from '../model/signin-page'
|
||||||
import { UserProfilePage } from '../model/profile/user-profile-page'
|
|
||||||
import { faker } from '@faker-js/faker'
|
|
||||||
|
|
||||||
test.describe('Workspace tests', () => {
|
test.describe('Workspace tests', () => {
|
||||||
let loginPage: LoginPage
|
let loginPage: LoginPage
|
||||||
@@ -20,7 +18,6 @@ test.describe('Workspace tests', () => {
|
|||||||
let leftSideMenuPage: LeftSideMenuPage
|
let leftSideMenuPage: LeftSideMenuPage
|
||||||
let trackerNavigationMenuPage: TrackerNavigationMenuPage
|
let trackerNavigationMenuPage: TrackerNavigationMenuPage
|
||||||
let issuesPage: IssuesPage
|
let issuesPage: IssuesPage
|
||||||
let userProfilePage: UserProfilePage
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
loginPage = new LoginPage(page)
|
loginPage = new LoginPage(page)
|
||||||
@@ -29,7 +26,6 @@ test.describe('Workspace tests', () => {
|
|||||||
leftSideMenuPage = new LeftSideMenuPage(page)
|
leftSideMenuPage = new LeftSideMenuPage(page)
|
||||||
trackerNavigationMenuPage = new TrackerNavigationMenuPage(page)
|
trackerNavigationMenuPage = new TrackerNavigationMenuPage(page)
|
||||||
issuesPage = new IssuesPage(page)
|
issuesPage = new IssuesPage(page)
|
||||||
userProfilePage = new UserProfilePage(page)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Create a workspace with a custom name', async () => {
|
test('Create a workspace with a custom name', async () => {
|
||||||
@@ -232,26 +228,4 @@ test.describe('Workspace tests', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
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 expect(selectWorkspacePage.title()).toBeVisible()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/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 create-account user1 -f John -l Appleseed -p 1234
|
||||||
|
./tool.sh create-account user2 -f Kainin -l Dirak -p 1234
|
||||||
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,102 @@
|
|||||||
|
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 workspaceName = generateId()
|
||||||
|
const workspaceInfo = await api.createWorkspaceWithLogin(workspaceName, '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(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.workspace)
|
||||||
|
await page2.locator(`[id="${workspaceInfo.workspace}"]`).getByRole('button', { name: 'Archive' }).click()
|
||||||
|
|
||||||
|
await page2.getByRole('button', { name: 'Ok' }).click()
|
||||||
|
await page2.locator(`[id="${workspaceInfo.workspace}"]`).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.workspace}"]`).getByRole('button', { name: 'Unarchive' }).click()
|
||||||
|
|
||||||
|
await page2.getByRole('button', { name: 'Ok' }).click()
|
||||||
|
await page2.locator(`[id="${workspaceInfo.workspace}"]`).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,228 @@
|
|||||||
|
import { faker } from '@faker-js/faker'
|
||||||
|
import {
|
||||||
|
generateId,
|
||||||
|
IssuesDetailsPage,
|
||||||
|
IssuesPage,
|
||||||
|
LeftSideMenuPage,
|
||||||
|
LoginPage,
|
||||||
|
NewIssue,
|
||||||
|
SelectWorkspacePage,
|
||||||
|
SignInJoinPage,
|
||||||
|
SignUpData,
|
||||||
|
SignUpPage,
|
||||||
|
TrackerNavigationMenuPage,
|
||||||
|
UserProfilePage
|
||||||
|
} from '@hcengineering/tests-sanity'
|
||||||
|
import { expect, 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 ({ page }) => {
|
||||||
|
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 expect(page.locator('form')).toContainText('Select workspace')
|
||||||
|
await expect(page.getByText(newWorkspaceName)).toHaveCount(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
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 workspaceName = generateId()
|
||||||
|
const workspaceInfo = await api.createWorkspaceWithLogin(workspaceName, '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(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.workspace)
|
||||||
|
await page2.locator(`[id="${workspaceInfo.workspace}"]`).getByRole('button', { name: 'Migrate' }).click()
|
||||||
|
|
||||||
|
await page2.getByRole('button', { name: 'Ok' }).click()
|
||||||
|
await page2.locator(`[id="${workspaceInfo.workspace}"]`).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