Merge remote-tracking branch 'origin/master' into remote-users-not-count-for-license

This commit is contained in:
Jesús Espino 2023-05-05 16:59:40 +02:00
commit d02cfca92a
2499 changed files with 34779 additions and 16140 deletions

View File

@ -4,15 +4,12 @@ on:
workflows: ["Mattermost Build"] workflows: ["Mattermost Build"]
types: types:
- completed - completed
jobs: jobs:
upload-s3: upload-s3:
name: cd/Upload artifacts to S3 name: cd/Upload artifacts to S3
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
REPO_NAME: ${{ github.event.repository.name }}
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps: steps:
- name: cd/Configure AWS - name: cd/Configure AWS
uses: aws-actions/configure-aws-credentials@07c2f971bac433df982ccc261983ae443861db49 # v1-node16 uses: aws-actions/configure-aws-credentials@07c2f971bac433df982ccc261983ae443861db49 # v1-node16
@ -28,23 +25,18 @@ jobs:
workflow_conclusion: success workflow_conclusion: success
name: server-dist-artifact name: server-dist-artifact
path: server/dist path: server/dist
# Get Branch name from calling workflow
# Search for the string "pull" and replace it with "PR" in branch-name
- name: cd/Get branch name
run: echo "BRANCH_NAME=$(echo ${{ github.event.workflow_run.head_branch }} | sed 's/^pull\//PR-/g')" >> $GITHUB_ENV
- name: cd/Upload artifacts to S3 - name: cd/Upload artifacts to S3
env:
REPO_NAME: ${{ github.event.repository.name }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
run: | run: |
aws s3 cp server/dist/ s3://pr-builds.mattermost.com/$REPO_NAME/$BRANCH_NAME/ --acl public-read --cache-control "no-cache" --recursive --no-progress aws s3 cp server/dist/ s3://pr-builds.mattermost.com/$REPO_NAME/commit/$COMMIT_SHA/ --acl public-read --cache-control "no-cache" --recursive --no-progress
aws s3 cp server/dist/ s3://pr-builds.mattermost.com/$REPO_NAME/commit/${{ github.sha }}/ --acl public-read --cache-control "no-cache" --recursive --no-progress
build-docker: build-docker:
name: cd/Build and push docker image name: cd/Build and push docker image
needs: upload-s3 needs: upload-s3
env:
REPO_NAME: ${{ github.event.repository.name }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: > if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps: steps:
- name: cd/Login to Docker Hub - name: cd/Login to Docker Hub
uses: docker/login-action@3da7dc6e2b31f99ef2cb9fb4c50fb0971e0d0139 # v2.1.0 uses: docker/login-action@3da7dc6e2b31f99ef2cb9fb4c50fb0971e0d0139 # v2.1.0
@ -64,53 +56,19 @@ jobs:
- name: cd/Docker build and push - name: cd/Docker build and push
env: env:
DOCKER_CLI_EXPERIMENTAL: enabled DOCKER_CLI_EXPERIMENTAL: enabled
run: |
export TAG=$(echo "${{ github.event.pull_request.head.sha || github.sha }}" | cut -c1-7)
cd server/build
export DOCKER_CLI_EXPERIMENTAL=enabled
export MM_PACKAGE=https://pr-builds.mattermost.com/$REPO_NAME/commit/${{ github.sha }}/mattermost-team-linux-amd64.tar.gz
docker buildx build --push --build-arg MM_PACKAGE=$MM_PACKAGE -t mattermostdevelopment/mm-te-test:${TAG} .
# Temporary uploading also to mattermost/mm-te-test:${TAG} except mattermostdevelopment/mm-te-test:${TAG}
# Context: https://community.mattermost.com/private-core/pl/3jzzxzfiji8hx833ewyuthzkjh
build-docker-temp:
name: cd/Build and push docker image
needs: upload-s3
env:
REPO_NAME: ${{ github.event.repository.name }} REPO_NAME: ${{ github.event.repository.name }}
runs-on: ubuntu-22.04 COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: cd/Login to Docker Hub
uses: docker/login-action@3da7dc6e2b31f99ef2cb9fb4c50fb0971e0d0139 # v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: cd/Download artifacts
uses: dawidd6/action-download-artifact@0c49384d39ceb023b8040f480a25596fd6cf441b # v2.26.0
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
run_id: ${{ github.event.workflow_run.id }}
workflow_conclusion: success
name: server-build-artifact
path: server/build/
- name: cd/Setup Docker Buildx
uses: docker/setup-buildx-action@11e8a2e2910826a92412015c515187a2d6750279 # v2.4
- name: cd/Docker build and push
env:
DOCKER_CLI_EXPERIMENTAL: enabled
run: | run: |
export TAG=$(echo "${{ github.event.pull_request.head.sha || github.sha }}" | cut -c1-7) export TAG=$(echo "${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}" | cut -c1-7)
cd server/build cd server/build
export DOCKER_CLI_EXPERIMENTAL=enabled export DOCKER_CLI_EXPERIMENTAL=enabled
export MM_PACKAGE=https://pr-builds.mattermost.com/$REPO_NAME/commit/${{ github.sha }}/mattermost-team-linux-amd64.tar.gz export MM_PACKAGE=https://pr-builds.mattermost.com/$REPO_NAME/commit/$COMMIT_SHA/mattermost-team-linux-amd64.tar.gz
docker buildx build --push --build-arg MM_PACKAGE=$MM_PACKAGE -t mattermost/mm-te-test:${TAG} . docker buildx build --push --build-arg MM_PACKAGE=$MM_PACKAGE -t mattermostdevelopment/mm-te-test:${TAG} .
sentry: sentry:
name: Send build info to sentry name: Send build info to sentry
if: > if: >
github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.event == 'push'
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
SENTRY_AUTH_TOKEN: ${{ secrets.MM_SERVER_SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.MM_SERVER_SENTRY_AUTH_TOKEN }}

View File

@ -7,7 +7,7 @@ on:
- mono-repo* - mono-repo*
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
defaults: defaults:
run: run:
shell: bash shell: bash
@ -83,6 +83,16 @@ jobs:
npm run mmjstool -- i18n clean-empty --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir --check npm run mmjstool -- i18n clean-empty --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir --check
npm run mmjstool -- i18n check-empty-src --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir npm run mmjstool -- i18n check-empty-src --webapp-dir ./src --mobile-dir /tmp/fake-mobile-dir
rm -rf tmp rm -rf tmp
- name: ci/lint-boards
working-directory: webapp/boards
run: |
npm run i18n-extract
git --no-pager diff --exit-code i18n/en.json || (echo "Please run \"cd webapp/boards && npm run i18n-extract\" and commit the changes in webapp/boards/i18n/en.json." && exit 1)
- name: ci/lint-playbooks
working-directory: webapp/playbooks
run: |
npm run i18n-extract
git --no-pager diff --exit-code i18n/en.json || (echo "Please run \"cd webapp/playbooks && npm run i18n-extract\" and commit the changes in webapp/playbooks/i18n/en.json." && exit 1)
check-types: check-types:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
defaults: defaults:

View File

@ -11,7 +11,7 @@ env:
go-version: "1.19.5" go-version: "1.19.5"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs: jobs:
check-mocks: check-mocks:
name: Check mocks name: Check mocks
@ -26,6 +26,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Generate mocks - name: Generate mocks
run: make mocks run: make mocks
- name: Check mocks - name: Check mocks
@ -43,6 +44,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Run go mod tidy - name: Run go mod tidy
run: make modules-tidy run: make modules-tidy
- name: Check modules - name: Check modules
@ -60,13 +62,14 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Run make-gen-serialized - name: Run make-gen-serialized
run: make gen-serialized run: make gen-serialized
- name: Check serialized - name: Check serialized
run: if [[ -n $(git status --porcelain) ]]; then echo "Please update the serialized files using 'make gen-serialized'"; exit 1; fi run: if [[ -n $(git status --porcelain) ]]; then echo "Please update the serialized files using 'make gen-serialized'"; exit 1; fi
check-mattermost-vet: check-mattermost-vet:
name: Check style name: Check style
runs-on: ubuntu-latest-8-cores runs-on: ubuntu-22.04
defaults: defaults:
run: run:
working-directory: server working-directory: server
@ -77,6 +80,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Reset config - name: Reset config
run: make config-reset run: make config-reset
- name: Run plugin-checker - name: Run plugin-checker
@ -106,6 +110,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Checkout mattermost-api-reference - name: Checkout mattermost-api-reference
run: | run: |
cd .. cd ..
@ -128,6 +133,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Generate work templates - name: Generate work templates
run: make generate-worktemplates run: make generate-worktemplates
- name: Check generated work templates - name: Check generated work templates
@ -160,6 +166,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Generate store layers - name: Generate store layers
run: make store-layers run: make store-layers
- name: Check generated code - name: Check generated code
@ -177,6 +184,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Generate app layers - name: Generate app layers
run: make app-layers run: make app-layers
- name: Check generated code - name: Check generated code
@ -216,6 +224,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Build - name: Build
run: | run: |
make config-reset make config-reset

View File

@ -2,7 +2,7 @@ name: "CodeQL"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
on: on:
pull_request: pull_request:

View File

@ -7,7 +7,7 @@ on:
- mono-repo* - mono-repo*
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
defaults: defaults:
run: run:
shell: bash shell: bash

159
.github/workflows/esrupgrade-common.yml vendored Normal file
View File

@ -0,0 +1,159 @@
name: ESR Upgrade
on:
workflow_call:
inputs:
db-dump-url:
required: true
type: string
initial-version:
required: true
type: string
final-version:
required: true
type: string
env:
COMPOSE_PROJECT_NAME: ghactions
BUILD_IMAGE: mattermost/mattermost-enterprise-edition:${{ inputs.final-version }}
MYSQL_CONN_ARGS: -h localhost -P 3306 --protocol=tcp -ummuser -pmostest mattermost_test
DUMP_SERVER_NAME: esr.${{ inputs.initial-version }}-${{ inputs.final-version }}.dump.server.sql
DUMP_SCRIPT_NAME: esr.${{ inputs.initial-version }}-${{ inputs.final-version }}.dump.script.sql
MIGRATION_SCRIPT: esr.${{ inputs.initial-version }}-${{ inputs.final-version }}.mysql.up.sql
CLEANUP_SCRIPT: esr.${{ inputs.initial-version }}-${{ inputs.final-version }}.mysql.cleanup.sql
PREPROCESS_SCRIPT: esr.common.mysql.preprocess.sql
DIFF_NAME: esr.${{ inputs.initial-version }}-${{ inputs.final-version }}.diff
jobs:
esr-upgrade-server:
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
steps:
- name: Checkout mattermost-server
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Run docker compose
run: |
cd server/build
docker-compose --no-ansi run --rm start_dependencies
cat ../tests/test-data.ldif | docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T minio sh -c 'mkdir -p /data/mattermost-test';
docker-compose --no-ansi ps
- name: Wait for docker compose
run: |
until docker network inspect ghactions_mm-test; do echo "Waiting for Docker Compose Network..."; sleep 1; done;
docker run --net ghactions_mm-test appropriate/curl:latest sh -c "until curl --max-time 5 --output - http://mysql:3306; do echo waiting for mysql; sleep 5; done;"
docker run --net ghactions_mm-test appropriate/curl:latest sh -c "until curl --max-time 5 --output - http://elasticsearch:9200; do echo waiting for elasticsearch; sleep 5; done;"
- name: Initialize the database with the source DB dump
run: |
curl ${{ inputs.db-dump-url }} | zcat | docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS
- name: Common preprocessing of the DB dump
run: |
cd server/scripts/esrupgrades
docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS < $PREPROCESS_SCRIPT
- name: Pull EE image
run: |
docker pull $BUILD_IMAGE
- name: Run migration through server
run: |
mkdir -p client/plugins
cd server/build
# Run the server in the background to trigger the migrations
docker run --name mmserver \
--net ghactions_mm-test \
--ulimit nofile=8096:8096 \
--env-file=dotenv/test.env \
--env MM_SQLSETTINGS_DRIVERNAME="mysql" \
--env MM_SQLSETTINGS_DATASOURCE="mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8&multiStatements=true" \
-v ~/work/mattermost-server:/mattermost-server \
-w /mattermost-server/mattermost-server \
$BUILD_IMAGE &
# In parallel, wait for the migrations to finish.
# To verify this, we check that the server has finished the startup job through the log line "Server is listening on"
until docker logs mmserver | grep "Server is listening on"; do\
echo "Waiting for migrations to finish..."; \
sleep 1; \
done;
# Make sure to stop the server. Also, redirect output to null;
# otherwise, the name of the container gets written to the console, which is weird
docker stop mmserver > /dev/null
- name: Cleanup DB
run : |
cd server/scripts/esrupgrades
docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS < $CLEANUP_SCRIPT
- name: Dump upgraded database
run: |
# Use --skip-opt to have each INSERT into one line.
# Use --set-gtid-purged=OFF to suppress GTID-related statements.
docker exec -i ghactions_mysql_1 mysqldump \
--skip-opt --set-gtid-purged=OFF \
$MYSQL_CONN_ARGS > $DUMP_SERVER_NAME
- name: Cleanup dump and compress
run: |
# We skip the very last line, which simply contains the date of the dump
head -n -1 ${DUMP_SERVER_NAME} | gzip > ${DUMP_SERVER_NAME}.gz
- name: Upload dump
uses: actions/upload-artifact@v3
with:
name: upgraded-dump-server
path: ${{ env.DUMP_SERVER_NAME }}.gz
esr-upgrade-script:
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
steps:
- name: Checkout mattermost-server
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Run docker compose
run: |
cd server/build
docker-compose --no-ansi run --rm start_dependencies
cat ../tests/test-data.ldif | docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T minio sh -c 'mkdir -p /data/mattermost-test';
docker-compose --no-ansi ps
- name: Wait for docker compose
run: |
until docker network inspect ghactions_mm-test; do echo "Waiting for Docker Compose Network..."; sleep 1; done;
docker run --net ghactions_mm-test appropriate/curl:latest sh -c "until curl --max-time 5 --output - http://mysql:3306; do echo waiting for mysql; sleep 5; done;"
docker run --net ghactions_mm-test appropriate/curl:latest sh -c "until curl --max-time 5 --output - http://elasticsearch:9200; do echo waiting for elasticsearch; sleep 5; done;"
- name: Initialize the database with the source DB dump
run: |
curl ${{ inputs.db-dump-url }} | zcat | docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS
- name: Preprocess the DB dump
run: |
cd server/scripts/esrupgrades
docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS < $PREPROCESS_SCRIPT
- name: Run migration through script
run : |
cd server/scripts/esrupgrades
docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS < $MIGRATION_SCRIPT
- name: Cleanup DB
run : |
cd server/scripts/esrupgrades
docker exec -i ghactions_mysql_1 mysql -AN $MYSQL_CONN_ARGS < $CLEANUP_SCRIPT
- name: Dump upgraded database
run: |
docker exec -i ghactions_mysql_1 mysqldump --skip-opt --set-gtid-purged=OFF $MYSQL_CONN_ARGS > $DUMP_SCRIPT_NAME
- name: Cleanup dump and compress
run: |
# We skip the very last line, which simply contains the date of the dump
head -n -1 ${DUMP_SCRIPT_NAME} | gzip > ${DUMP_SCRIPT_NAME}.gz
- name: Upload dump
uses: actions/upload-artifact@v3
with:
name: upgraded-dump-script
path: ${{ env.DUMP_SCRIPT_NAME }}.gz
esr-upgrade-diff:
runs-on: ubuntu-latest-8-cores
needs:
- esr-upgrade-server
- esr-upgrade-script
steps:
- name: Retrieve dumps
uses: actions/download-artifact@v3
- name: Diff dumps
run: |
gzip -d upgraded-dump-server/${DUMP_SERVER_NAME}.gz
gzip -d upgraded-dump-script/${DUMP_SCRIPT_NAME}.gz
diff upgraded-dump-server/$DUMP_SERVER_NAME upgraded-dump-script/$DUMP_SCRIPT_NAME > $DIFF_NAME
- name: Upload diff
if: failure() # Upload the diff only if the previous step failed; i.e., if the diff is non-empty
uses: actions/upload-artifact@v3
with:
name: dumps-diff
path: ${{ env.DIFF_NAME }}

33
.github/workflows/esrupgrade.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: ESR Upgrade
on:
pull_request:
paths:
- 'server/scripts/esrupgrades/*'
- '.github/workflows/esr*'
push:
branches:
- master
- cloud
- release-*
jobs:
esr-upgrade-5_37-7_8:
name: Run ESR upgrade script from 5.37 to 7.8
uses: ./.github/workflows/esrupgrade-common.yml
with:
db-dump-url: https://lt-public-data.s3.amazonaws.com/47K_537_mysql_collationfixed.sql.gz
initial-version: 5.37
final-version: 7.8
esr-upgrade-5_37-6_3:
name: Run ESR upgrade script from 5.37 to 6.3
uses: ./.github/workflows/esrupgrade-common.yml
with:
db-dump-url: https://lt-public-data.s3.amazonaws.com/47K_537_mysql_collationfixed.sql.gz
initial-version: 5.37
final-version: 6.3
esr-upgrade-6_3-7_8:
name: Run ESR upgrade script from 6.3 to 7.8
uses: ./.github/workflows/esrupgrade-common.yml
with:
db-dump-url: https://lt-public-data.s3.amazonaws.com/47K_63_mysql.sql.gz
initial-version: 6.3
final-version: 7.8

View File

@ -7,7 +7,7 @@ on:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains( github.ref , 'heads/ref/master') }} cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
# Declare default permissions as read only. # Declare default permissions as read only.
permissions: read-all permissions: read-all

View File

@ -23,6 +23,7 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: ${{ env.go-version }} go-version: ${{ env.go-version }}
cache-dependency-path: server/go.sum
- name: Run docker compose - name: Run docker compose
run: | run: |
cd server/build cd server/build

View File

@ -5,3 +5,6 @@
/webapp/package-lock.json @mattermost/web-platform /webapp/package-lock.json @mattermost/web-platform
/webapp/platform/*/package.json @mattermost/web-platform /webapp/platform/*/package.json @mattermost/web-platform
/webapp/scripts @mattermost/web-platform /webapp/scripts @mattermost/web-platform
/server/channels/db/migrations @mattermost/server-platform
/server/boards/services/store/sqlstore/migrations @mattermost/server-platform
/server/playbooks/server/sqlstore/migrations @mattermost/server-platform

View File

@ -12,7 +12,6 @@
"@babel/eslint-parser": "7.19.1", "@babel/eslint-parser": "7.19.1",
"@babel/eslint-plugin": "7.19.1", "@babel/eslint-plugin": "7.19.1",
"@cypress/request": "2.88.11", "@cypress/request": "2.88.11",
"@cypress/skip-test": "2.6.1",
"@mattermost/types": "7.4.0", "@mattermost/types": "7.4.0",
"@testing-library/cypress": "9.0.0", "@testing-library/cypress": "9.0.0",
"@types/async": "3.2.16", "@types/async": "3.2.16",
@ -2250,12 +2249,6 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/@cypress/skip-test": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@cypress/skip-test/-/skip-test-2.6.1.tgz",
"integrity": "sha512-X+ibefBiuOmC5gKG91wRIT0/OqXeETYvu7zXktjZ3yLeO186Y8ia0K7/gQUpAwuUi28DuqMd1+7tBQVtPkzbPA==",
"dev": true
},
"node_modules/@cypress/xvfb": { "node_modules/@cypress/xvfb": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",
@ -19062,12 +19055,6 @@
} }
} }
}, },
"@cypress/skip-test": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@cypress/skip-test/-/skip-test-2.6.1.tgz",
"integrity": "sha512-X+ibefBiuOmC5gKG91wRIT0/OqXeETYvu7zXktjZ3yLeO186Y8ia0K7/gQUpAwuUi28DuqMd1+7tBQVtPkzbPA==",
"dev": true
},
"@cypress/xvfb": { "@cypress/xvfb": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",

View File

@ -3,7 +3,6 @@
"@babel/eslint-parser": "7.19.1", "@babel/eslint-parser": "7.19.1",
"@babel/eslint-plugin": "7.19.1", "@babel/eslint-plugin": "7.19.1",
"@cypress/request": "2.88.11", "@cypress/request": "2.88.11",
"@cypress/skip-test": "2.6.1",
"@mattermost/types": "7.4.0", "@mattermost/types": "7.4.0",
"@testing-library/cypress": "9.0.0", "@testing-library/cypress": "9.0.0",
"@types/async": "3.2.16", "@types/async": "3.2.16",

View File

@ -98,6 +98,72 @@ describe('Create and delete board / card', () => {
cy.findByText('for testing purposes only').should('be.visible'); cy.findByText('for testing purposes only').should('be.visible');
}); });
it('MM-T4276 Set up Board emoji', () => {
cy.visit('/boards');
// # Create an empty board and change tile to Testing
cy.findByText('Create an empty board').should('exist').click({force: true});
cy.get('.BoardComponent').should('exist');
// # Change Title
cy.findByPlaceholderText('Untitled board').should('be.visible').wait(timeouts.HALF_SEC);
// * Assert that the title is changed to "testing"
cy.findByPlaceholderText('Untitled board').
clear().
type('Testing').
type('{enter}').
should('have.value', 'Testing');
// # "Add icon" and "Show description" options appear
cy.findByText('Add icon').should('exist');
cy.findByText('show description').should('exist');
// # Click on "Add icon"
cy.findByText('Add icon').should('exist').click({force: true});
// * Assert that a random emoji is selected and added at the beginning of the board title
cy.get('.IconSelector').should('exist');
// # Click on the emoji next to the board title
cy.get('.IconSelector .MenuWrapper').should('exist').click({force: true});
// * Assert that Dropdown menu with 3 options appears
cy.findByText('Random').should('exist');
cy.findByText('Pick icon').should('exist');
cy.findByText('Remove icon').should('exist');
// # Hover your mouse over the "Pick Icon" option
cy.findByText('Pick icon').trigger('mouseover');
// * Assert that emoji picker menu appears
cy.get('.IconSelector .menu-contents').should('exist');
// # Click on the emoji from the picker
cy.get('.EmojiPicker').should('exist').and('be.visible').within(() => {
// # Click on the emoji
cy.get("[aria-label='😀, grinning']").should('exist');
cy.get("[aria-label='😀, grinning']").eq(0).click({force: true});
});
// * Assert that Selected emoji is now displayed next to the board title
cy.get('.IconSelector span').contains('😀');
// # Click on the emoji next to the board title
cy.get('.IconSelector .MenuWrapper').should('exist').click({force: true});
// * Assert that Dropdown menu with 3 options appears
cy.findByText('Random').should('exist');
cy.findByText('Pick icon').should('exist');
cy.findByText('Remove icon').should('exist');
// # Click "Remove icon"
cy.findByText('Remove icon').click({force: true});
// * Assert that Icon next to the board title is removed
cy.get('.IconSelector').should('not.exist');
});
it('MM-T5397 Can create and delete a board and a card', () => { it('MM-T5397 Can create and delete a board and a card', () => {
// Visit a page and create new empty board // Visit a page and create new empty board
cy.visit('/boards'); cy.visit('/boards');

View File

@ -18,7 +18,6 @@ describe('Verify Accessibility Support in Post', () => {
let otherUser; let otherUser;
let testTeam; let testTeam;
let testChannel; let testChannel;
let emojiPickerEnabled;
before(() => { before(() => {
cy.apiInitSetup().then(({team, channel, user}) => { cy.apiInitSetup().then(({team, channel, user}) => {
@ -33,10 +32,6 @@ describe('Verify Accessibility Support in Post', () => {
cy.apiAddUserToChannel(testChannel.id, otherUser.id); cy.apiAddUserToChannel(testChannel.id, otherUser.id);
}); });
}); });
cy.apiGetConfig().then(({config}) => {
emojiPickerEnabled = config.ServiceSettings.EnableEmojiPicker;
});
}); });
}); });
@ -179,18 +174,11 @@ describe('Verify Accessibility Support in Post', () => {
cy.get(`#CENTER_time_${postId}`).should('be.focused'); cy.get(`#CENTER_time_${postId}`).should('be.focused');
cy.focused().tab(); cy.focused().tab();
// eslint-disable-next-line no-negated-condition
if (!emojiPickerEnabled) {
// * Verify focus is on the actions button
cy.get(`#CENTER_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'more');
cy.focused().tab();
} else {
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
// * Verify focus is on the reactions button // * Verify focus is on the reactions button
cy.get(`#recent_reaction_${i}`).should('have.class', 'emoticon--post-menu').and('have.attr', 'aria-label'); cy.get(`#recent_reaction_${i}`).should('have.class', 'emoticon--post-menu').and('have.attr', 'aria-label');
cy.focused().tab(); cy.focused().tab();
} }
}
// * Verify focus is on the reactions button // * Verify focus is on the reactions button
cy.get(`#CENTER_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'add reaction'); cy.get(`#CENTER_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'add reaction');
@ -200,15 +188,17 @@ describe('Verify Accessibility Support in Post', () => {
cy.get(`#CENTER_flagIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'save'); cy.get(`#CENTER_flagIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'save');
cy.focused().tab(); cy.focused().tab();
// * Verify focus is on message actions button
cy.get(`#CENTER_actions_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'actions');
cy.focused().tab();
// * Verify focus is on the comment button // * Verify focus is on the comment button
cy.get(`#CENTER_commentIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'reply'); cy.get(`#CENTER_commentIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'reply');
cy.focused().tab(); cy.focused().tab();
if (emojiPickerEnabled) {
// * Verify focus is on the more button // * Verify focus is on the more button
cy.get(`#CENTER_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'More'); cy.get(`#CENTER_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'more');
cy.focused().tab(); cy.focused().tab();
}
// * Verify focus is on the post text // * Verify focus is on the post text
cy.get(`#postMessageText_${postId}`).should('be.focused').and('have.attr', 'aria-readonly', 'true'); cy.get(`#postMessageText_${postId}`).should('be.focused').and('have.attr', 'aria-readonly', 'true');
@ -244,11 +234,13 @@ describe('Verify Accessibility Support in Post', () => {
cy.get(`#rhsPostMessageText_${postId}`).should('be.focused').and('have.attr', 'aria-readonly', 'true'); cy.get(`#rhsPostMessageText_${postId}`).should('be.focused').and('have.attr', 'aria-readonly', 'true');
cy.focused().tab({shift: true}); cy.focused().tab({shift: true});
if (emojiPickerEnabled) { // * Verify focus is on the more button
// * Verify focus is on the actions button cy.get(`#RHS_COMMENT_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'more');
cy.get(`#RHS_COMMENT_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'More'); cy.focused().tab({shift: true});
// * Verify focus is on message actions button
cy.get(`#RHS_COMMENT_actions_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'actions');
cy.focused().tab({shift: true}); cy.focused().tab({shift: true});
}
// * Verify focus is on the save icon // * Verify focus is on the save icon
cy.get(`#RHS_COMMENT_flagIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'save'); cy.get(`#RHS_COMMENT_flagIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'save');
@ -258,15 +250,9 @@ describe('Verify Accessibility Support in Post', () => {
cy.get(`#RHS_COMMENT_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'add reaction'); cy.get(`#RHS_COMMENT_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'add reaction');
cy.focused().tab({shift: true}); cy.focused().tab({shift: true});
// eslint-disable-next-line no-negated-condition // * Verify focus is on most recent action
if (!emojiPickerEnabled) {
// * Verify focus is on the actions button
cy.get(`#RHS_COMMENT_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'more');
cy.focused().tab({shift: true});
} else {
cy.get('#recent_reaction_0').should('have.class', 'emoticon--post-menu').and('have.attr', 'aria-label'); cy.get('#recent_reaction_0').should('have.class', 'emoticon--post-menu').and('have.attr', 'aria-label');
cy.focused().tab({shift: true}); cy.focused().tab({shift: true});
}
// * Verify focus is on the time // * Verify focus is on the time
cy.get(`#RHS_COMMENT_time_${postId}`).should('be.focused'); cy.get(`#RHS_COMMENT_time_${postId}`).should('be.focused');

View File

@ -11,6 +11,7 @@
// Group: @channels @bot_accounts // Group: @channels @bot_accounts
import {createBotPatch} from '../../../support/api/bots'; import {createBotPatch} from '../../../support/api/bots';
import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('Bot tags', () => { describe('Bot tags', () => {
let me; let me;
@ -48,8 +49,8 @@ describe('Bot tags', () => {
await client.pinPost(postId); await client.pinPost(postId);
cy.visit(`/${team.name}/channels/${channel.name}`); cy.visit(`/${team.name}/channels/${channel.name}`);
cy.clickPostDotMenu(postId); cy.get(`#post_${postId}`).trigger('mouseover', {force: true});
cy.get(`#CENTER_flagIcon_${postId}`).click(); cy.wait(TIMEOUTS.HALF_SEC).get(`#CENTER_flagIcon_${postId}`).click();
}); });
}); });
}); });

View File

@ -15,7 +15,10 @@ import {getRandomId} from '../../../utils';
describe('Leave an archived channel', () => { describe('Leave an archived channel', () => {
let testTeam; let testTeam;
let offTopicUrl; let offTopicUrl;
const channelType = {
public: 'Channel Type: Public',
archived: 'Channel Type: Archived',
};
before(() => { before(() => {
cy.apiUpdateConfig({ cy.apiUpdateConfig({
TeamSettings: { TeamSettings: {
@ -97,7 +100,7 @@ describe('Leave an archived channel', () => {
// # More channels modal opens // # More channels modal opens
cy.get('#moreChannelsModal').should('be.visible').within(() => { cy.get('#moreChannelsModal').should('be.visible').within(() => {
// # Click on dropdown // # Click on dropdown
cy.findByText('Show: Public Channels').should('be.visible').click(); cy.findByText(channelType.public).should('be.visible').click();
// # Click archived channels // # Click archived channels
cy.findByText('Archived Channels').click(); cy.findByText('Archived Channels').click();
@ -145,7 +148,7 @@ describe('Leave an archived channel', () => {
// # More channels modal opens // # More channels modal opens
cy.get('.more-modal').should('be.visible').within(() => { cy.get('.more-modal').should('be.visible').within(() => {
// # Public channel list opens by default // # Public channel list opens by default
cy.findByText('Show: Public Channels').should('be.visible').click(); cy.findByText(channelType.public).should('be.visible').click();
// # Click on archived channels // # Click on archived channels
cy.findByText('Archived Channels').click(); cy.findByText('Archived Channels').click();
@ -198,7 +201,7 @@ describe('Leave an archived channel', () => {
// # More channels modal opens // # More channels modal opens
cy.get('.more-modal').should('be.visible').within(() => { cy.get('.more-modal').should('be.visible').within(() => {
// # Public channels are shown by default // # Public channels are shown by default
cy.findByText('Show: Public Channels').should('be.visible').click(); cy.findByText(channelType.public).should('be.visible').click();
// # Go to archived channels // # Go to archived channels
cy.findByText('Archived Channels').click(); cy.findByText('Archived Channels').click();
@ -252,7 +255,7 @@ describe('Leave an archived channel', () => {
// # More channels modal opens // # More channels modal opens
cy.get('.more-modal').should('be.visible').within(() => { cy.get('.more-modal').should('be.visible').within(() => {
// # Show public channels is visible by default // # Show public channels is visible by default
cy.findByText('Show: Public Channels').should('be.visible').click(); cy.findByText(channelType.public).should('be.visible').click();
// # Go to archived channels // # Go to archived channels
cy.findByText('Archived Channels').click(); cy.findByText('Archived Channels').click();
@ -286,7 +289,7 @@ describe('Leave an archived channel', () => {
// # More channels modal opens and lands on public channels // # More channels modal opens and lands on public channels
cy.get('#moreChannelsModal').should('be.visible').within(() => { cy.get('#moreChannelsModal').should('be.visible').within(() => {
cy.findByText('Show: Public Channels').should('be.visible').click(); cy.findByText(channelType.public).should('be.visible').click();
// # Go to archived channels // # Go to archived channels
cy.findByText('Archived Channels').click(); cy.findByText('Archived Channels').click();

View File

@ -14,6 +14,11 @@ import * as TIMEOUTS from '../../../fixtures/timeouts';
import {createPrivateChannel} from '../enterprise/elasticsearch_autocomplete/helpers'; import {createPrivateChannel} from '../enterprise/elasticsearch_autocomplete/helpers';
const channelType = {
public: 'Channel Type: Public',
archived: 'Channel Type: Archived',
};
describe('Channels', () => { describe('Channels', () => {
let testUser; let testUser;
let otherUser; let otherUser;
@ -65,7 +70,7 @@ describe('Channels', () => {
cy.get('#moreChannelsModal').should('be.visible').within(() => { cy.get('#moreChannelsModal').should('be.visible').within(() => {
// * Dropdown should be visible, defaulting to "Public Channels" // * Dropdown should be visible, defaulting to "Public Channels"
cy.get('#channelsMoreDropdown').should('be.visible').and('contain', 'Show: Public Channels').wait(TIMEOUTS.HALF_SEC); cy.get('#channelsMoreDropdown').should('be.visible').and('contain', channelType.public).wait(TIMEOUTS.HALF_SEC);
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC); cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => { cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => {
@ -113,7 +118,7 @@ describe('Channels', () => {
cy.findByText('Archived Channels').should('be.visible').click(); cy.findByText('Archived Channels').should('be.visible').click();
// * Channel test should be visible as an archived channel in the list // * Channel test should be visible as an archived channel in the list
cy.wrap(el).should('contain', 'Show: Archived Channels'); cy.wrap(el).should('contain', channelType.archived);
}); });
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC); cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
@ -196,7 +201,7 @@ describe('Channels', () => {
// * Dropdown should be visible, defaulting to "Public Channels" // * Dropdown should be visible, defaulting to "Public Channels"
cy.get('#channelsMoreDropdown').should('be.visible').within((el) => { cy.get('#channelsMoreDropdown').should('be.visible').within((el) => {
cy.wrap(el).should('contain', 'Show: Public Channels'); cy.wrap(el).should('contain', channelType.public);
}); });
// * Users should be able to type and search // * Users should be able to type and search
@ -207,12 +212,12 @@ describe('Channels', () => {
cy.get('#moreChannelsModal').should('be.visible').within(() => { cy.get('#moreChannelsModal').should('be.visible').within(() => {
// * Users should be able to switch to "Archived Channels" list // * Users should be able to switch to "Archived Channels" list
cy.get('#channelsMoreDropdown').should('be.visible').and('contain', 'Show: Public Channels').click().within((el) => { cy.get('#channelsMoreDropdown').should('be.visible').and('contain', channelType.public).click().within((el) => {
// # Click on archived channels item // # Click on archived channels item
cy.findByText('Archived Channels').should('be.visible').click(); cy.findByText('Archived Channels').should('be.visible').click();
// * Modal should show the archived channels list // * Modal should show the archived channels list
cy.wrap(el).should('contain', 'Show: Archived Channels'); cy.wrap(el).should('contain', channelType.archived);
}).wait(TIMEOUTS.HALF_SEC); }).wait(TIMEOUTS.HALF_SEC);
cy.get('#searchChannelsTextbox').clear(); cy.get('#searchChannelsTextbox').clear();
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2); cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
@ -250,7 +255,7 @@ function verifyMoreChannelsModal(isEnabled) {
// * Verify that the more channels modal is open and with or without option to view archived channels // * Verify that the more channels modal is open and with or without option to view archived channels
cy.get('#moreChannelsModal').should('be.visible').within(() => { cy.get('#moreChannelsModal').should('be.visible').within(() => {
if (isEnabled) { if (isEnabled) {
cy.get('#channelsMoreDropdown').should('be.visible').and('have.text', 'Show: Public Channels'); cy.get('#channelsMoreDropdown').should('be.visible').and('have.text', channelType.public);
} else { } else {
cy.get('#channelsMoreDropdown').should('not.exist'); cy.get('#channelsMoreDropdown').should('not.exist');
} }

View File

@ -142,7 +142,7 @@ describe('System Console - Subscriptions section', () => {
cy.get('.RHS').find('button').should('be.enabled'); cy.get('.RHS').find('button').should('be.enabled');
// # Change the user seats field to a value smaller than the current number of users // # Change the user seats field to a value smaller than the current number of users
const lessThanUserCount = count - 5; const lessThanUserCount = 1;
cy.get('#input_UserSeats').clear().type(lessThanUserCount); cy.get('#input_UserSeats').clear().type(lessThanUserCount);
// * Ensure that the yearly, monthly, and yearly saving prices match the new user seats value entered // * Ensure that the yearly, monthly, and yearly saving prices match the new user seats value entered

View File

@ -0,0 +1,48 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
describe('Insights', () => {
let teamA;
before(() => {
cy.shouldHaveFeatureFlag('InsightsEnabled', true);
cy.apiInitSetup().then(({team}) => {
teamA = team;
});
});
it('Check all the cards exist', () => {
cy.apiAdminLogin();
// # Go to the Insights view
cy.visit(`/${teamA.name}/activity-and-insights`);
// * Check top channels exists
cy.get('.top-channels-card').should('exist');
// * Check top threads exists
cy.get('.top-threads-card').should('exist');
// * Check top boards exists because product mode is enabled
cy.get('.top-boards-card').should('exist');
// * Check top reactions exists
cy.get('.top-reactions-card').should('exist');
// * Check top dms exists
cy.get('.top-dms-card').should('exist');
// * Check least active channels exists
cy.get('.least-active-channels-card').should('exist');
// * Check top playbooks exists because product mode is enabled
cy.get('.top-playbooks-card').should('exist');
});
});

View File

@ -69,6 +69,12 @@ describe('Incoming webhook', () => {
cy.getLastPost().within(() => { cy.getLastPost().within(() => {
cy.findByRole('link', {name: 'Testing Integration Attachments', hidden: true}); cy.findByRole('link', {name: 'Testing Integration Attachments', hidden: true});
});
// # Scroll to the bottom of the posts
cy.get('.post-list__dynamic').scrollTo('bottom');
cy.getLastPost().within(() => {
cy.get('.attachment__image').should('be.visible'); cy.get('.attachment__image').should('be.visible');
cy.get(':nth-child(2) > thead > tr > .attachment-field__caption').should('have.text', 'Area'); cy.get(':nth-child(2) > thead > tr > .attachment-field__caption').should('have.text', 'Area');
cy.get(':nth-child(3) > thead > tr > :nth-child(1)').should('have.text', 'Iteration'); cy.get(':nth-child(3) > thead > tr > :nth-child(1)').should('have.text', 'Iteration');

View File

@ -191,7 +191,9 @@ describe('Keyboard shortcut CTRL/CMD+Shift+\\ for adding reaction to last messag
Cypress._.times(3, () => { Cypress._.times(3, () => {
doReactToLastMessageShortcut('CENTER'); doReactToLastMessageShortcut('CENTER');
cy.get('#emojiPicker').should('exist'); cy.get('#emojiPicker').should('exist');
cy.get('body').click();
// # Click anywhere to close emoji picker
cy.get('#channelHeaderInfo').click();
cy.get('#emojiPicker').should('not.exist'); cy.get('#emojiPicker').should('not.exist');
}); });

View File

@ -15,7 +15,7 @@ import theme from '../../../fixtures/theme.json';
describe('Status dropdown menu', () => { describe('Status dropdown menu', () => {
const statusTestCases = [ const statusTestCases = [
{text: 'Online', className: 'icon-check', profileClassName: 'icon-check-circle'}, {text: 'Online', className: 'icon-check-circle', profileClassName: 'icon-check-circle'},
{text: 'Away', className: 'icon-clock'}, {text: 'Away', className: 'icon-clock'},
{text: 'Do Not Disturb', className: 'icon-minus-circle'}, {text: 'Do Not Disturb', className: 'icon-minus-circle'},
{text: 'Offline', className: 'icon-circle-outline'}, {text: 'Offline', className: 'icon-circle-outline'},

View File

@ -145,4 +145,42 @@ describe('Edit Message', () => {
cy.get(postText).should('have.text', `${secondMessage} Another new message Edited`); cy.get(postText).should('have.text', `${secondMessage} Another new message Edited`);
}); });
}); });
it('MM-T5416 should discard any changes made after cancelling the edit and opening the edit textbox again should display the original message', () => {
const message = 'World!';
cy.postMessage(message);
// * Verify message is sent and not pending
cy.getLastPostId().then((postId) => {
const postText = `#postMessageText_${postId}`;
cy.get(postText).should('have.text', message);
// # Open edit textbox
cy.uiGetPostTextBox().type('{uparrow}');
// * Edit Post Input should appear, and edit the post
cy.get('#edit_textbox').should('be.visible');
// * Press the escape key to cancel
cy.get('#edit_textbox').should('have.text', message).type(' Another new message{esc}');
cy.get('#edit_textbox').should('not.exist');
// * Check that the message wasn't edited
cy.get(postText).should('have.text', message);
});
cy.getLastPostId().then((postId) => {
const postText = `#postMessageText_${postId}`;
cy.get(postText).should('have.text', message);
// # Open edit textbox again
cy.uiGetPostTextBox().type('{uparrow}');
// * Edit Post Input should appear, and edit the post
cy.get('#edit_textbox').should('be.visible');
// * Opening the edit textbox again after previously cancelling the edit should contain the original message.
cy.get('#edit_textbox').should('have.text', message);
});
});
}); });

View File

@ -9,71 +9,45 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
import {onlyOn} from '@cypress/skip-test';
describe('channels > App Bar', {testIsolation: true}, () => { describe('channels > App Bar', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook;
let appBarEnabled;
before(() => { before(() => {
cy.apiInitSetup().then(({team, user}) => { cy.apiInitSetup().then(({team, user}) => {
testTeam = team; testTeam = team;
testUser = user; testUser = user;
// # Login as testUser
cy.apiLogin(testUser);
// # Create a playbook
cy.apiCreateTestPlaybook({
teamId: testTeam.id,
title: 'Playbook',
userId: testUser.id,
}).then((playbook) => {
testPlaybook = playbook;
// # Start a playbook run
cy.apiRunPlaybook({
teamId: testTeam.id,
playbookId: testPlaybook.id,
playbookRunName: 'Playbook Run',
ownerUserId: testUser.id,
});
});
cy.apiGetConfig(true).then(({config}) => {
appBarEnabled = config.EnableAppBar === 'true';
});
}); });
}); });
beforeEach(() => { beforeEach(() => {
// # Size the viewport to show the RHS without covering posts. cy.apiAdminLogin();
cy.viewport('macbook-13');
// # Login as testUser
cy.apiLogin(testUser);
}); });
describe('App Bar disabled', () => { describe('App Bar disabled', () => {
it('should not show the Playbook App Bar icon', () => { it('should not show the Playbook App Bar icon', () => {
onlyOn(!appBarEnabled); cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: false}});
// # Login as testUser
cy.apiLogin(testUser);
// # Navigate directly to a non-playbook run channel // # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`); cy.visit(`/${testTeam.name}/channels/town-square`);
// * Verify App Bar icon is not showing // * Verify App Bar icon is not showing
cy.get('#channel_view').within(() => { cy.get('.app-bar').should('not.exist');
cy.getPlaybooksAppBarIcon().should('not.exist');
});
}); });
}); });
describe('App Bar enabled', () => { describe('App Bar enabled', () => {
it('should show the Playbook App Bar icon', () => { beforeEach(() => {
onlyOn(appBarEnabled); cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: true}});
// # Login as testUser
cy.apiLogin(testUser);
});
it('should show the Playbook App Bar icon', () => {
// # Navigate directly to a non-playbook run channel // # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`); cy.visit(`/${testTeam.name}/channels/town-square`);
@ -82,8 +56,6 @@ describe('channels > App Bar', {testIsolation: true}, () => {
}); });
it('should show "Playbooks" tooltip for Playbook App Bar icon', () => { it('should show "Playbooks" tooltip for Playbook App Bar icon', () => {
onlyOn(appBarEnabled);
// # Navigate directly to a non-playbook run channel // # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`); cy.visit(`/${testTeam.name}/channels/town-square`);

View File

@ -9,14 +9,11 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
import {onlyOn} from '@cypress/skip-test';
describe('channels > channel header', {testIsolation: true}, () => { describe('channels > channel header', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;
let testPlaybookRun; let testPlaybookRun;
let appBarEnabled;
before(() => { before(() => {
cy.apiInitSetup().then(({team, user}) => { cy.apiInitSetup().then(({team, user}) => {
@ -44,24 +41,16 @@ describe('channels > channel header', {testIsolation: true}, () => {
testPlaybookRun = run; testPlaybookRun = run;
}); });
}); });
cy.apiGetConfig(true).then(({config}) => {
appBarEnabled = config.EnableAppBar === 'true';
}); });
}); });
});
beforeEach(() => {
// # Size the viewport to show the RHS without covering posts.
cy.viewport('macbook-13');
// # Login as testUser
cy.apiLogin(testUser);
});
describe('App Bar enabled', () => { describe('App Bar enabled', () => {
it('webapp should hide the Playbook channel header button', () => { it('webapp should hide the Playbook channel header button', () => {
onlyOn(appBarEnabled); cy.apiAdminLogin();
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: true}});
// # Login as testUser
cy.apiLogin(testUser);
// # Navigate directly to a non-playbook run channel // # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`); cy.visit(`/${testTeam.name}/channels/town-square`);
@ -74,9 +63,15 @@ describe('channels > channel header', {testIsolation: true}, () => {
}); });
describe('App Bar disabled', () => { describe('App Bar disabled', () => {
it('webapp should show the Playbook channel header button', () => { beforeEach(() => {
onlyOn(!appBarEnabled); cy.apiAdminLogin();
cy.apiUpdateConfig({ExperimentalSettings: {EnableAppBar: false}});
// # Login as testUser
cy.apiLogin(testUser);
});
it('webapp should show the Playbook channel header button', () => {
// # Navigate directly to a non-playbook run channel // # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`); cy.visit(`/${testTeam.name}/channels/town-square`);
@ -87,8 +82,6 @@ describe('channels > channel header', {testIsolation: true}, () => {
}); });
it('tooltip text should show "Playbooks" for Playbook channel header button', () => { it('tooltip text should show "Playbooks" for Playbook channel header button', () => {
onlyOn(!appBarEnabled);
// # Navigate directly to a non-playbook run channel // # Navigate directly to a non-playbook run channel
cy.visit(`/${testTeam.name}/channels/town-square`); cy.visit(`/${testTeam.name}/channels/town-square`);
@ -103,6 +96,11 @@ describe('channels > channel header', {testIsolation: true}, () => {
}); });
describe('description text', () => { describe('description text', () => {
beforeEach(() => {
// # Login as testUser
cy.apiLogin(testUser);
});
it('should contain a link to the playbook', () => { it('should contain a link to the playbook', () => {
// # Navigate directly to a playbook run channel // # Navigate directly to a playbook run channel
cy.visit(`/${testTeam.name}/channels/playbook-run`); cy.visit(`/${testTeam.name}/channels/playbook-run`);
@ -112,6 +110,7 @@ describe('channels > channel header', {testIsolation: true}, () => {
expect(href).to.equals(`/playbooks/playbooks/${testPlaybook.id}`); expect(href).to.equals(`/playbooks/playbooks/${testPlaybook.id}`);
}); });
}); });
it('should contain a link to the overview page', () => { it('should contain a link to the overview page', () => {
// # Navigate directly to a playbook run channel // # Navigate directly to a playbook run channel
cy.visit(`/${testTeam.name}/channels/playbook-run`); cy.visit(`/${testTeam.name}/channels/playbook-run`);

View File

@ -82,7 +82,7 @@ describe('channels > rhs > status update', {testIsolation: true}, () => {
}); });
}); });
it.skip('description link navigates to run overview', () => { it('description link navigates to run overview', () => {
// # Run the `/playbook update` slash command. // # Run the `/playbook update` slash command.
cy.uiPostMessageQuickly('/playbook update'); cy.uiPostMessageQuickly('/playbook update');

View File

@ -153,7 +153,7 @@ describe('lhs', {testIsolation: true}, () => {
cy.findByTestId('dropdownmenu').should('be.visible'); cy.findByTestId('dropdownmenu').should('be.visible');
}); });
it.skip('can copy link', () => { it('can copy link', () => {
// # Visit the playbook run // # Visit the playbook run
cy.visit(`/playbooks/runs/${playbookRun.id}`); cy.visit(`/playbooks/runs/${playbookRun.id}`);
stubClipboard().as('clipboard'); stubClipboard().as('clipboard');
@ -295,7 +295,7 @@ describe('lhs', {testIsolation: true}, () => {
}); });
}); });
it.skip('leave run, when on rdp of the same run', () => { it('leave run, when on rdp of the same run', () => {
// # Click on leave menu item // # Click on leave menu item
getRunDropdownItemByText('Runs', playbookRun.name, 'Leave and unfollow run').click(); getRunDropdownItemByText('Runs', playbookRun.name, 'Leave and unfollow run').click();

View File

@ -290,7 +290,7 @@ describe('playbooks > edit_metrics', {testIsolation: true}, () => {
}); });
describe('delete metric', () => { describe('delete metric', () => {
it.skip('verifies when clicking delete button; saved metrics have different confirmation text; deleted metrics are deleted', () => { it('verifies when clicking delete button; saved metrics have different confirmation text; deleted metrics are deleted', () => {
// # Visit the selected playbook // # Visit the selected playbook
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`); cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);

View File

@ -160,9 +160,7 @@ describe('runs > permissions', {testIsolation: true}, () => {
}); });
describe('should be visible', () => { describe('should be visible', () => {
// XXX: Skipping this test, since public playbooks currently have no members. This will it('to playbook members', () => {
// likely change in the future, so keeping the skeleton.
it.skip('to playbook members', () => {
assertRunIsVisible(run, playbookMember); assertRunIsVisible(run, playbookMember);
}); });
@ -242,9 +240,7 @@ describe('runs > permissions', {testIsolation: true}, () => {
}); });
describe('should be visible', () => { describe('should be visible', () => {
// XXX: Skipping this test, since public playbooks currently have no members. This will it('to playbook members', () => {
// likely change in the future.
it.skip('to playbook members', () => {
assertRunIsVisible(run, playbookMember); assertRunIsVisible(run, playbookMember);
}); });
@ -332,10 +328,9 @@ describe('runs > permissions', {testIsolation: true}, () => {
assertRunIsVisible(run, runParticipant); assertRunIsVisible(run, runParticipant);
}); });
// Skipping this test, since followers cannot follow a run with a private channel from // Followers cannot follow a run with a private channel from a private playbook
// a private playbook. (But leaving it for clarity in the code.) it('to run followers', () => {
it.skip('to run followers', () => { assertRunIsNotVisible(run, runFollower);
assertRunIsVisible(run, runFollower);
}); });
it('to admins in the team', () => { it('to admins in the team', () => {
@ -414,10 +409,9 @@ describe('runs > permissions', {testIsolation: true}, () => {
assertRunIsVisible(run, runParticipant); assertRunIsVisible(run, runParticipant);
}); });
// Skipping this test, since followers cannot follow a run with a private channel from // Followers cannot follow a run with a private channel from a private playbook
// a private playbook. (But leaving it for clarity in the code.) it('to run followers', () => {
it.skip('to run followers', () => { assertRunIsNotVisible(run, runFollower);
assertRunIsVisible(run, runFollower);
}); });
it('to admins in the team', () => { it('to admins in the team', () => {

View File

@ -692,7 +692,7 @@ describe('runs > run details page > header', {testIsolation: true}, () => {
}); });
}); });
describe.skip('Join action disabled', () => { describe('Join action disabled', () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(testUser); cy.apiLogin(testUser);

View File

@ -267,7 +267,7 @@ describe('runs > run details page > status update', {testIsolation: true}, () =>
}); });
}); });
it.skip('requests an update and confirm', () => { it('requests an update and confirm', () => {
// # Click on request update // # Click on request update
cy.findByTestId('run-statusupdate-section'). cy.findByTestId('run-statusupdate-section').
should('be.visible'). should('be.visible').
@ -281,11 +281,11 @@ describe('runs > run details page > status update', {testIsolation: true}, () =>
cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`); cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`);
// * Assert that message has been sent // * Assert that message has been sent
cy.getLastPost().contains(`${testUser.username} requested a status update for ${testPublicPlaybook.name}.`); cy.getLastPost().contains(`${testViewerUser.username} requested a status update for ${testRun.name}.`);
}); });
}); });
it.skip('requests an update and cancel', () => { it('requests an update and cancel', () => {
// # Click request update // # Click request update
cy.findByTestId('run-statusupdate-section'). cy.findByTestId('run-statusupdate-section').
should('be.visible'). should('be.visible').

File diff suppressed because one or more lines are too long

View File

@ -2,8 +2,21 @@
#### 1. Start local server in a separate terminal. #### 1. Start local server in a separate terminal.
```
# Typically run the local server with:
cd server && make run
# Or build and distribute webapp including channels, boards and playbooks
# so that their product URLs do not rely on Webpack dev server.
# Especially important when running test inside the Playwright's docker container.
cd webapp && make dist
cd server && make run-server
```
#### 2. Install dependencies and run the test. #### 2. Install dependencies and run the test.
Note: If you're using Node.js version 18 and above, you may need to set `NODE_OPTIONS='--no-experimental-fetch'`.
``` ```
# Install npm packages # Install npm packages
npm i npm i
@ -32,14 +45,16 @@ npm run test
Change to root directory, run docker container Change to root directory, run docker container
``` ```
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.30.0-focal /bin/bash docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.32.0-focal /bin/bash
``` ```
#### 2. Inside the docker container #### 2. Inside the docker container
``` ```
export NODE_OPTIONS='--no-experimental-fetch'
export PW_BASE_URL=http://host.docker.internal:8065 export PW_BASE_URL=http://host.docker.internal:8065
cd mattermost/e2e/playwright export PW_HEADLESS=true
cd mattermost/e2e-tests/playwright
# Install npm packages. Use "npm ci" to match the automated environment # Install npm packages. Use "npm ci" to match the automated environment
npm ci npm ci

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,35 @@
{ {
"scripts": { "scripts": {
"test": "PW_SNAPSHOT_ENABLE=true playwright test", "test": "cross-env PW_SNAPSHOT_ENABLE=true playwright test",
"percy": "PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=iphone --project=ipad", "percy": "cross-env PERCY_TOKEN=$PERCY_TOKEN PW_PERCY_ENABLE=true percy exec -- playwright test --project=chrome --project=iphone --project=ipad",
"tsc": "tsc -b", "tsc": "tsc -b",
"lint": "eslint . --ext .js,.ts", "lint": "eslint . --ext .js,.ts",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"check": "npm run tsc && npm run lint && npm run prettier", "check": "npm run tsc && npm run lint && npm run prettier",
"codegen": "playwright codegen $PW_BASE_URL", "codegen": "cross-env playwright codegen $PW_BASE_URL",
"test-slomo": "PW_SNAPSHOT_ENABLE=true PW_HEADLESS=false PW_SLOWMO=1000 playwright test", "playwright-ui": "playwright test --ui",
"test-slomo": "cross-env PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test",
"show-report": "npx playwright show-report" "show-report": "npx playwright show-report"
}, },
"dependencies": { "dependencies": {
"@percy/cli": "1.18.0", "@percy/cli": "1.23.0",
"@percy/playwright": "1.0.4", "@percy/playwright": "1.0.4",
"@playwright/test": "1.30.0", "@playwright/test": "1.32.3",
"async-wait-until": "2.0.12", "async-wait-until": "2.0.12",
"chalk": "4.1.2", "chalk": "4.1.2",
"deepmerge": "4.3.0", "deepmerge": "4.3.1",
"dotenv": "16.0.3", "dotenv": "16.0.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"isomorphic-unfetch": "4.0.2", "isomorphic-unfetch": "4.0.2",
"uuid": "9.0.0" "uuid": "9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/uuid": "9.0.0", "@types/uuid": "9.0.1",
"@typescript-eslint/eslint-plugin": "5.51.0", "@typescript-eslint/eslint-plugin": "5.59.0",
"@typescript-eslint/parser": "5.51.0", "@typescript-eslint/parser": "5.59.0",
"eslint": "8.34.0", "cross-env": "7.0.3",
"prettier": "2.8.4", "eslint": "8.38.0",
"typescript": "4.9.5" "prettier": "2.8.7",
"typescript": "5.0.4"
} }
} }

View File

@ -6,7 +6,7 @@ import {defineConfig, devices} from '@playwright/test';
import {duration} from '@e2e-support/util'; import {duration} from '@e2e-support/util';
import testConfig from '@e2e-test.config'; import testConfig from '@e2e-test.config';
const defaultOutputFolder = 'playwright-report'; const defaultOutputFolder = './playwright-report';
export default defineConfig({ export default defineConfig({
globalSetup: require.resolve('./global_setup'), globalSetup: require.resolve('./global_setup'),

View File

@ -35,7 +35,7 @@
# - Default to "false" if not set. # - Default to "false" if not set.
# 12. PW_HEADLESS # 12. PW_HEADLESS
# - Default to "true" if not set. Set to false to run test in head mode. # - Default to "false" or headless mode if not set. Set to true to run test in headed mode.
# 13. PW_SLOWMO # 13. PW_SLOWMO
# - Default to "0" if not set which means normal test speed run. Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. # - Default to "0" if not set which means normal test speed run. Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.

View File

@ -3,16 +3,18 @@
import {writeFile} from 'node:fs/promises'; import {writeFile} from 'node:fs/promises';
import {request, Browser} from '@playwright/test'; import {request, Browser, BrowserContext} from '@playwright/test';
import {UserProfile} from '@mattermost/types/users'; import {UserProfile} from '@mattermost/types/users';
import testConfig from '@e2e-test.config'; import testConfig from '@e2e-test.config';
export class TestBrowser { export class TestBrowser {
readonly browser: Browser; readonly browser: Browser;
context: BrowserContext | null;
constructor(browser: Browser) { constructor(browser: Browser) {
this.browser = browser; this.browser = browser;
this.context = null;
} }
async login(user: UserProfile | null) { async login(user: UserProfile | null) {
@ -27,8 +29,16 @@ export class TestBrowser {
const context = await this.browser.newContext(options); const context = await this.browser.newContext(options);
const page = await context.newPage(); const page = await context.newPage();
this.context = context;
return {context, page}; return {context, page};
} }
async close() {
if (this.context) {
await this.context.close();
}
}
} }
export async function loginByAPI(loginId: string, password: string, token = '', ldapOnly = false) { export async function loginByAPI(loginId: string, password: string, token = '', ldapOnly = false) {

View File

@ -4,25 +4,32 @@
import {getRandomId} from '@e2e-support/util'; import {getRandomId} from '@e2e-support/util';
import {Channel, ChannelType} from '@mattermost/types/channels'; import {Channel, ChannelType} from '@mattermost/types/channels';
export function createRandomChannel( type ChannelInput = {
teamId: string, teamId: string;
name: string, name: string;
displayName: string, displayName: string;
type: ChannelType = 'O', type?: ChannelType;
purpose = '', purpose?: string;
header = '', header?: string;
unique = true unique?: boolean;
): Channel { };
export function createRandomChannel(channelInput: ChannelInput): Channel {
const channel = {
team_id: channelInput.teamId,
name: channelInput.name,
display_name: channelInput.displayName,
type: channelInput.type || 'O',
purpose: channelInput.type || '',
header: channelInput.type || '',
};
if (channelInput.unique) {
const randomSuffix = getRandomId(); const randomSuffix = getRandomId();
const channel = { channel.name = `${channelInput.name}-${randomSuffix}`;
team_id: teamId, channel.display_name = `${channelInput.displayName} ${randomSuffix}`;
name: unique ? `${name}-${randomSuffix}` : name, }
display_name: unique ? `${displayName} ${randomSuffix}` : displayName,
type,
purpose,
header,
};
return channel as Channel; return channel as Channel;
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
// This is based on "packages/client/src/client4.ts". Modified for node client. // This is based on "webapp/platform/client/src/client4.ts". Modified for node client.
// Update should be made in comparison with the base Client4. // Update should be made in comparison with the base Client4.
import fs from 'node:fs'; import fs from 'node:fs';
@ -134,7 +134,7 @@ export default class Client extends Client4 {
// ***************************************************************************** // *****************************************************************************
// Boards client // Boards client
// based on https://github.com/mattermost/focalboard/blob/main/webapp/src/octoClient.ts // based on "webapp/boards/src/octoClient.ts"
// ***************************************************************************** // *****************************************************************************
async patchUserConfig(userID: string, patch: UserConfigPatch): Promise<UserPreference[] | undefined> { async patchUserConfig(userID: string, patch: UserConfigPatch): Promise<UserPreference[] | undefined> {

View File

@ -170,7 +170,6 @@ const defaultServerConfig: AdminConfig = {
EnableCustomGroups: true, EnableCustomGroups: true,
SelfHostedPurchase: true, SelfHostedPurchase: true,
AllowSyncedDrafts: true, AllowSyncedDrafts: true,
SelfHostedExpansion: false,
}, },
TeamSettings: { TeamSettings: {
SiteName: 'Mattermost', SiteName: 'Mattermost',
@ -319,7 +318,6 @@ const defaultServerConfig: AdminConfig = {
LoginButtonColor: '#0000', LoginButtonColor: '#0000',
LoginButtonBorderColor: '#2389D7', LoginButtonBorderColor: '#2389D7',
LoginButtonTextColor: '#2389D7', LoginButtonTextColor: '#2389D7',
EnableInactivityEmail: true,
}, },
RateLimitSettings: { RateLimitSettings: {
Enable: false, Enable: false,
@ -532,7 +530,8 @@ const defaultServerConfig: AdminConfig = {
EnableSharedChannels: false, EnableSharedChannels: false,
EnableRemoteClusterService: false, EnableRemoteClusterService: false,
EnableAppBar: false, EnableAppBar: false,
PatchPluginsReactDOM: false, DisableRefetchingOnBrowserFocus: false,
DelayChannelAutocomplete: false,
}, },
AnalyticsSettings: { AnalyticsSettings: {
MaxUsersForStatistics: 2500, MaxUsersForStatistics: 2500,
@ -622,12 +621,6 @@ const defaultServerConfig: AdminConfig = {
'com.mattermost.nps': { 'com.mattermost.nps': {
Enable: true, Enable: true,
}, },
focalboard: {
Enable: true,
},
playbooks: {
Enable: true,
},
}, },
EnableMarketplace: true, EnableMarketplace: true,
EnableRemoteMarketplace: true, EnableRemoteMarketplace: true,
@ -671,13 +664,11 @@ const defaultServerConfig: AdminConfig = {
BoardsFeatureFlags: '', BoardsFeatureFlags: '',
BoardsDataRetention: false, BoardsDataRetention: false,
NormalizeLdapDNs: false, NormalizeLdapDNs: false,
EnableInactivityCheckJob: true,
UseCaseOnboarding: true,
GraphQL: false, GraphQL: false,
InsightsEnabled: true, InsightsEnabled: true,
CommandPalette: false, CommandPalette: false,
SendWelcomePost: true, SendWelcomePost: true,
WorkTemplate: false, WorkTemplate: true,
PostPriority: true, PostPriority: true,
WysiwygEditor: false, WysiwygEditor: false,
PeopleProduct: false, PeopleProduct: false,
@ -686,7 +677,9 @@ const defaultServerConfig: AdminConfig = {
ThreadsEverywhere: false, ThreadsEverywhere: false,
GlobalDrafts: true, GlobalDrafts: true,
OnboardingTourTips: true, OnboardingTourTips: true,
DeprecateCloudFree: false,
AppsSidebarCategory: false, AppsSidebarCategory: false,
CloudReverseTrial: false,
}, },
ImportSettings: { ImportSettings: {
Directory: './import', Directory: './import',

View File

@ -3,7 +3,9 @@
import path from 'node:path'; import path from 'node:path';
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import chalk from 'chalk';
import {ClientError} from '@mattermost/client/client4';
import {PreferenceType} from '@mattermost/types/preferences'; import {PreferenceType} from '@mattermost/types/preferences';
import testConfig from '@e2e-test.config'; import testConfig from '@e2e-test.config';
@ -77,10 +79,21 @@ export async function initSetup({
offTopicUrl: getUrl(team.name, 'off-topic'), offTopicUrl: getUrl(team.name, 'off-topic'),
townSquareUrl: getUrl(team.name, 'town-square'), townSquareUrl: getUrl(team.name, 'town-square'),
}; };
} catch (err) { } catch (error) {
// log an error for debugging // log an error for debugging
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(err); const err = error as ClientError;
if (err.message === 'Could not parse multipart form.') {
// eslint-disable-next-line no-console
console.log(chalk.yellow(`node version: ${process.version}\nNODE_OPTIONS: ${process.env.NODE_OPTIONS}`));
// eslint-disable-next-line no-console
console.log(
chalk.green(
`This failed due to the experimental fetch support in Node.js starting v18.0.0.\nYou may set environment variable: "export NODE_OPTIONS='--no-experimental-fetch'", then try again.'`
)
);
}
expect(err, 'Should not throw an error').toBeFalsy(); expect(err, 'Should not throw an error').toBeFalsy();
throw err; throw err;
} }

View File

@ -1,8 +1,9 @@
import {test as base, Browser} from '@playwright/test'; import {test as base, Browser, ViewportSize} from '@playwright/test';
import {TestBrowser} from './browser_context'; import {TestBrowser} from './browser_context';
import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag'; import {shouldHaveCallsEnabled, shouldHaveFeatureFlag, shouldSkipInSmallScreen, shouldRunInLinux} from './flag';
import {initSetup, getAdminClient} from './server'; import {initSetup, getAdminClient} from './server';
import {isSmallScreen} from './util';
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action'; import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
import {pages} from './ui/pages'; import {pages} from './ui/pages';
import {matchSnapshot} from './visual'; import {matchSnapshot} from './visual';
@ -15,9 +16,10 @@ type ExtendedFixtures = {
}; };
export const test = base.extend<ExtendedFixtures>({ export const test = base.extend<ExtendedFixtures>({
pw: async ({browser}, use) => { pw: async ({browser, viewport}, use) => {
const pw = new PlaywrightExtended(browser); const pw = new PlaywrightExtended(browser, viewport);
await use(pw); await use(pw);
await pw.testBrowser.close();
}, },
// eslint-disable-next-line no-empty-pattern // eslint-disable-next-line no-empty-pattern
pages: async ({}, use) => { pages: async ({}, use) => {
@ -47,10 +49,13 @@ class PlaywrightExtended {
// ./ui/pages // ./ui/pages
readonly pages; readonly pages;
// ./util
readonly isSmallScreen;
// ./visual // ./visual
readonly matchSnapshot; readonly matchSnapshot;
constructor(browser: Browser) { constructor(browser: Browser, viewport: ViewportSize | null) {
// ./browser_context // ./browser_context
this.testBrowser = new TestBrowser(browser); this.testBrowser = new TestBrowser(browser);
@ -72,6 +77,9 @@ class PlaywrightExtended {
// ./ui/pages // ./ui/pages
this.pages = pages; this.pages = pages;
// ./util
this.isSmallScreen = () => isSmallScreen(viewport);
// ./visual // ./visual
this.matchSnapshot = matchSnapshot; this.matchSnapshot = matchSnapshot;
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class FindChannelsModal {
readonly container: Locator;
readonly input;
readonly searchList;
constructor(container: Locator) {
this.container = container;
this.input = container.getByRole('textbox', {name: 'quick switch input'});
this.searchList = container.locator('.suggestion-list__item');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {FindChannelsModal};

View File

@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class ChannelsHeaderMobile {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toggleSidebar() {
await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click();
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {ChannelsHeaderMobile};

View File

@ -9,6 +9,7 @@ export default class ChannelsPostCreate {
readonly input; readonly input;
readonly attachmentButton; readonly attachmentButton;
readonly emojiButton; readonly emojiButton;
readonly sendButton: Locator;
constructor(container: Locator) { constructor(container: Locator) {
this.container = container; this.container = container;
@ -16,12 +17,17 @@ export default class ChannelsPostCreate {
this.input = container.getByTestId('post_textbox'); this.input = container.getByTestId('post_textbox');
this.attachmentButton = container.getByLabel('attachment'); this.attachmentButton = container.getByLabel('attachment');
this.emojiButton = container.getByLabel('select an emoji'); this.emojiButton = container.getByLabel('select an emoji');
this.sendButton = container.getByTestId('SendMessageButton');
} }
async postMessage(message: string) { async postMessage(message: string) {
await this.input.fill(message); await this.input.fill(message);
} }
async sendMessage() {
await this.sendButton.click();
}
async toBeVisible() { async toBeVisible() {
await expect(this.container).toBeVisible(); await expect(this.container).toBeVisible();
await expect(this.input).toBeVisible(); await expect(this.input).toBeVisible();

View File

@ -0,0 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class ChannelsSidebarLeft {
readonly container: Locator;
readonly findChannelButton;
constructor(container: Locator) {
this.container = container;
this.findChannelButton = container.getByRole('button', {name: 'Find Channels'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
export {ChannelsSidebarLeft};

View File

@ -16,7 +16,7 @@ export default class GlobalHeader {
async switchProduct(name: string) { async switchProduct(name: string) {
await this.productSwitchMenu.click(); await this.productSwitchMenu.click();
await this.container.getByRole('link', {name: `${name}`}).click(); await this.container.getByRole('link', {name}).click();
} }
async toBeVisible(name: string) { async toBeVisible(name: string) {

View File

@ -3,19 +3,25 @@
import {BoardsSidebar} from './boards/sidebar'; import {BoardsSidebar} from './boards/sidebar';
import {ChannelsHeader} from './channels/header'; import {ChannelsHeader} from './channels/header';
import {ChannelsHeaderMobile} from './channels/header_mobile';
import {ChannelsAppBar} from './channels/app_bar'; import {ChannelsAppBar} from './channels/app_bar';
import {ChannelsPostCreate} from './channels/post_create'; import {ChannelsPostCreate} from './channels/post_create';
import {ChannelsPost} from './channels/post'; import {ChannelsPost} from './channels/post';
import {ChannelsSidebarLeft} from './channels/sidebar_left';
import {ChannelsSidebarRight} from './channels/sidebar_right'; import {ChannelsSidebarRight} from './channels/sidebar_right';
import {FindChannelsModal} from './channels/find_channels_modal';
import {GlobalHeader} from './global_header'; import {GlobalHeader} from './global_header';
const components = { const components = {
BoardsSidebar, BoardsSidebar,
ChannelsAppBar, ChannelsAppBar,
ChannelsHeader, ChannelsHeader,
ChannelsHeaderMobile,
ChannelsPostCreate, ChannelsPostCreate,
ChannelsPost, ChannelsPost,
ChannelsSidebarLeft,
ChannelsSidebarRight, ChannelsSidebarRight,
FindChannelsModal,
GlobalHeader, GlobalHeader,
}; };
@ -24,8 +30,11 @@ export {
BoardsSidebar, BoardsSidebar,
ChannelsAppBar, ChannelsAppBar,
ChannelsHeader, ChannelsHeader,
ChannelsHeaderMobile,
ChannelsPostCreate, ChannelsPostCreate,
ChannelsPost, ChannelsPost,
ChannelsSidebarLeft,
ChannelsSidebarRight, ChannelsSidebarRight,
FindChannelsModal,
GlobalHeader, GlobalHeader,
}; };

View File

@ -11,17 +11,23 @@ export default class ChannelsPage {
readonly channels = 'Channels'; readonly channels = 'Channels';
readonly page: Page; readonly page: Page;
readonly postCreate; readonly postCreate;
readonly findChannelsModal;
readonly globalHeader; readonly globalHeader;
readonly header; readonly header;
readonly headerMobile;
readonly appBar; readonly appBar;
readonly sidebarLeft;
readonly sidebarRight; readonly sidebarRight;
constructor(page: Page) { constructor(page: Page) {
this.page = page; this.page = page;
this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create')); this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create'));
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
this.globalHeader = new components.GlobalHeader(page.locator('#global-header')); this.globalHeader = new components.GlobalHeader(page.locator('#global-header'));
this.header = new components.ChannelsHeader(page.locator('.channel-header')); this.header = new components.ChannelsHeader(page.locator('.channel-header'));
this.headerMobile = new components.ChannelsHeaderMobile(page.locator('.navbar'));
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar')); this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right')); this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
} }
@ -49,6 +55,10 @@ export default class ChannelsPage {
await this.postCreate.postMessage(message); await this.postCreate.postMessage(message);
} }
async sendMessage() {
await this.postCreate.sendMessage();
}
async getFirstPost() { async getFirstPost() {
await this.page.getByTestId('postView').first().waitFor(); await this.page.getByTestId('postView').first().waitFor();
const post = await this.page.getByTestId('postView').first(); const post = await this.page.getByTestId('postView').first();

View File

@ -55,7 +55,7 @@ const config: TestConfig = {
// CI // CI
isCI: !!process.env.CI, isCI: !!process.env.CI,
// Playwright // Playwright
headless: parseBool(process.env.PW_HEADLESS, false), headless: parseBool(process.env.PW_HEADLESS, true),
slowMo: parseNumber(process.env.PW_SLOWMO, 0), slowMo: parseNumber(process.env.PW_SLOWMO, 0),
workers: parseNumber(process.env.PW_WORKERS, 1), workers: parseNumber(process.env.PW_WORKERS, 1),
// Visual tests // Visual tests

View File

@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture';
import {createRandomChannel} from '@e2e-support/server';
test('MM-T5424 Find channel search returns only 50 results when there are more than 50 channels with similar names', async ({
pw,
pages,
}) => {
const {adminClient, user, team} = await pw.initSetup();
const commonName = 'test_channel';
// # Create more than 50 channels with similar names
const channelsRes = [];
for (let i = 0; i < 100; i++) {
let suffix = i.toString();
if (i < 10) {
suffix = `0${i}`;
}
const channel = createRandomChannel({
teamId: team.id,
name: `${commonName}_${suffix}`,
displayName: `Test Channel ${suffix}`,
});
channelsRes.push(adminClient.createChannel(channel));
}
await Promise.all(channelsRes);
// # Log in a user in new browser context
const {page} = await pw.testBrowser.login(user);
// # Visit a default channel page
const channelsPage = new pages.ChannelsPage(page);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on "Find channel" and type "test_channel"
if (pw.isSmallScreen()) {
await channelsPage.headerMobile.toggleSidebar();
}
await channelsPage.sidebarLeft.findChannelButton.click();
await channelsPage.findChannelsModal.toBeVisible();
await channelsPage.findChannelsModal.input.fill(commonName);
const limitCount = 50;
// # Only 50 results for similar name should be displayed.
await expect(channelsPage.findChannelsModal.searchList).toHaveCount(limitCount);
for (let i = 0; i < limitCount; i++) {
let suffix = i.toString();
if (i < 10) {
suffix = `0${i}`;
}
await expect(channelsPage.findChannelsModal.container.getByTestId(`${commonName}_${suffix}`)).toBeVisible();
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -2,7 +2,6 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {expect, test} from '@e2e-support/test_fixture'; import {expect, test} from '@e2e-support/test_fixture';
import {isSmallScreen} from '@e2e-support/util';
test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => { test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => {
// Create and sign in a new user // Create and sign in a new user
@ -23,7 +22,7 @@ test('Intro to channel as regular user', async ({pw, pages, browserName, viewpor
// await wait(duration.one_sec); // await wait(duration.one_sec);
// Wait for Playbooks icon to be loaded in App bar, except in iphone // Wait for Playbooks icon to be loaded in App bar, except in iphone
if (!isSmallScreen(viewport)) { if (!pw.isSmallScreen()) {
await expect(channelsPage.appBar.playbooksIcon).toBeVisible(); await expect(channelsPage.appBar.playbooksIcon).toBeVisible();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 266 KiB

View File

@ -1,4 +1,4 @@
.PHONY: build package run stop run-client run-server run-haserver stop-haserver stop-client stop-server restart restart-server restart-client restart-haserver start-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows package-prep package-linux package-osx package-windows internal-test-web-client vet run-server-for-web-client-tests diff-config prepackaged-plugins prepackaged-binaries test-server test-server-ee test-server-quick test-server-race new-migration migrations-extract .PHONY: build package run stop run-client run-server run-haserver stop-haserver stop-client stop-server restart restart-server restart-client restart-haserver start-docker update-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows package-prep package-linux package-osx package-windows internal-test-web-client vet run-server-for-web-client-tests diff-config prepackaged-plugins prepackaged-binaries test-server test-server-ee test-server-quick test-server-race new-migration migrations-extract
ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
@ -102,11 +102,11 @@ GOFLAGS ?= $(GOFLAGS:)
export GOBIN ?= $(PWD)/bin export GOBIN ?= $(PWD)/bin
GO=go GO=go
DELVE=dlv DELVE=dlv
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildNumber=$(BUILD_NUMBER)" LDFLAGS += -X "github.com/mattermost/mattermost-server/server/v8/model.BuildNumber=$(BUILD_NUMBER)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildDate=$(BUILD_DATE)" LDFLAGS += -X "github.com/mattermost/mattermost-server/server/v8/model.BuildDate=$(BUILD_DATE)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHash=$(BUILD_HASH)" LDFLAGS += -X "github.com/mattermost/mattermost-server/server/v8/model.BuildHash=$(BUILD_HASH)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)" LDFLAGS += -X "github.com/mattermost/mattermost-server/server/v8/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)"
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)" LDFLAGS += -X "github.com/mattermost/mattermost-server/server/v8/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)"
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1) GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
@ -132,21 +132,22 @@ DIST_PATH_WIN=$(DIST_ROOT)/windows/mattermost
TESTS=. TESTS=.
# Packages lists # Packages lists
TE_PACKAGES=$(shell $(GO) list ./... | grep -vE 'v6/server/playbooks|v6/server/boards') TE_PACKAGES=$(shell $(GO) list ./... | grep -vE 'server/v8/playbooks|server/v8/boards')
BOARDS_PACKAGES=$(shell $(GO) list ./... | grep -E 'v6/server/boards') BOARDS_PACKAGES=$(shell $(GO) list ./... | grep -E 'server/v8/boards')
PLAYBOOKS_PACKAGES=$(shell $(GO) list ./... | grep -E 'v6/server/playbooks') PLAYBOOKS_PACKAGES=$(shell $(GO) list ./... | grep -E 'server/v8/playbooks')
SUITE_PACKAGES=$(shell $(GO) list ./...)
TEMPLATES_DIR=templates TEMPLATES_DIR=templates
# Plugins Packages # Plugins Packages
PLUGIN_PACKAGES ?= mattermost-plugin-antivirus-v0.1.2 PLUGIN_PACKAGES ?= mattermost-plugin-antivirus-v0.1.2
PLUGIN_PACKAGES += mattermost-plugin-autolink-v1.2.2 PLUGIN_PACKAGES += mattermost-plugin-autolink-v1.4.0
PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.2.0 PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.2.0
PLUGIN_PACKAGES += mattermost-plugin-calls-v0.15.1 PLUGIN_PACKAGES += mattermost-plugin-calls-v0.15.1
PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.0.0 PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.0.0
PLUGIN_PACKAGES += mattermost-plugin-confluence-v1.3.0 PLUGIN_PACKAGES += mattermost-plugin-confluence-v1.3.0
PLUGIN_PACKAGES += mattermost-plugin-custom-attributes-v1.3.1 PLUGIN_PACKAGES += mattermost-plugin-custom-attributes-v1.3.1
PLUGIN_PACKAGES += mattermost-plugin-github-v2.1.4 PLUGIN_PACKAGES += mattermost-plugin-github-v2.1.5
PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.6.0 PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.6.0
PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.1.0 PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.1.0
PLUGIN_PACKAGES += mattermost-plugin-jira-v3.2.2 PLUGIN_PACKAGES += mattermost-plugin-jira-v3.2.2
@ -155,7 +156,7 @@ PLUGIN_PACKAGES += mattermost-plugin-nps-v1.3.1
PLUGIN_PACKAGES += mattermost-plugin-todo-v0.6.1 PLUGIN_PACKAGES += mattermost-plugin-todo-v0.6.1
PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.2.0 PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.2.0
PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.6.0 PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.6.0
PLUGIN_PACKAGES += mattermost-plugin-apps-v1.2.0 PLUGIN_PACKAGES += mattermost-plugin-apps-v1.2.1
# Prepares the enterprise build if exists. The IGNORE stuff is a hack to get the Makefile to execute the commands outside a target # Prepares the enterprise build if exists. The IGNORE stuff is a hack to get the Makefile to execute the commands outside a target
ifeq ($(BUILD_ENTERPRISE_READY),true) ifeq ($(BUILD_ENTERPRISE_READY),true)
@ -189,7 +190,7 @@ endif
include config.mk include config.mk
include build/*.mk include build/*.mk
LDFLAGS += -X "github.com/mattermost/mattermost-server/v6/model.MockCWS=$(MM_ENABLE_CWS_MOCK)" LDFLAGS += -X "github.com/mattermost/mattermost-server/server/v8/model.MockCWS=$(MM_ENABLE_CWS_MOCK)"
RUN_IN_BACKGROUND ?= RUN_IN_BACKGROUND ?=
ifeq ($(RUN_SERVER_IN_BACKGROUND),true) ifeq ($(RUN_SERVER_IN_BACKGROUND),true)
@ -239,6 +240,11 @@ else
endif endif
endif endif
update-docker: stop-docker ## Updates the docker containers for local development.
@echo Updating docker containers
$(GO) run ./build/docker-compose-generator/main.go $(ENABLED_DOCKER_SERVICES) | docker-compose -f docker-compose.makefile.yml -f /dev/stdin $(DOCKER_COMPOSE_OVERRIDE) up --no-start
run-haserver: run-haserver:
ifeq ($(BUILD_ENTERPRISE_READY),true) ifeq ($(BUILD_ENTERPRISE_READY),true)
@echo Starting mattermost in an HA topology '(3 node cluster)' @echo Starting mattermost in an HA topology '(3 node cluster)'
@ -270,7 +276,7 @@ else
endif endif
plugin-checker: plugin-checker:
$(GO) run $(GOFLAGS) ../plugin/checker $(GO) run $(GOFLAGS) ./plugin/checker
prepackaged-plugins: ## Populate the prepackaged-plugins directory prepackaged-plugins: ## Populate the prepackaged-plugins directory
@echo Downloading prepackaged plugins @echo Downloading prepackaged plugins
@ -289,7 +295,7 @@ endif
golangci-lint: ## Run golangci-lint on codebase golangci-lint: ## Run golangci-lint on codebase
@# Keep the version in sync with the command in .circleci/config.yml @# Keep the version in sync with the command in .circleci/config.yml
$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1 $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2
@echo Running golangci-lint @echo Running golangci-lint
$(GOBIN)/golangci-lint run ./... $(GOBIN)/golangci-lint run ./...
@ -349,9 +355,9 @@ ldap-mocks: ## Creates mock files for ldap.
plugin-mocks: ## Creates mock files for plugins. plugin-mocks: ## Creates mock files for plugins.
$(GO) install github.com/vektra/mockery/v2/...@v2.23.2 $(GO) install github.com/vektra/mockery/v2/...@v2.23.2
$(GOBIN)/mockery --dir ../plugin --name API --output ../plugin/plugintest --outpkg plugintest --case underscore --note 'Regenerate this file using `make plugin-mocks`.' $(GOBIN)/mockery --dir ./plugin --name API --output ./plugin/plugintest --outpkg plugintest --case underscore --note 'Regenerate this file using `make plugin-mocks`.'
$(GOBIN)/mockery --dir ../plugin --name Hooks --output ../plugin/plugintest --outpkg plugintest --case underscore --note 'Regenerate this file using `make plugin-mocks`.' $(GOBIN)/mockery --dir ./plugin --name Hooks --output ./plugin/plugintest --outpkg plugintest --case underscore --note 'Regenerate this file using `make plugin-mocks`.'
$(GOBIN)/mockery --dir ../plugin --name Driver --output ../plugin/plugintest --outpkg plugintest --case underscore --note 'Regenerate this file using `make plugin-mocks`.' $(GOBIN)/mockery --dir ./plugin --name Driver --output ./plugin/plugintest --outpkg plugintest --case underscore --note 'Regenerate this file using `make plugin-mocks`.'
einterfaces-mocks: ## Creates mock files for einterfaces. einterfaces-mocks: ## Creates mock files for einterfaces.
$(GO) install github.com/vektra/mockery/v2/...@v2.23.2 $(GO) install github.com/vektra/mockery/v2/...@v2.23.2
@ -380,7 +386,7 @@ platform-mocks: ## Creates mocks for platform interfaces.
$(GOBIN)/mockery --dir channels/app/platform --name SuiteIFace --output channels/app/platform/mocks --note 'Regenerate this file using `make platform-mocks`.' $(GOBIN)/mockery --dir channels/app/platform --name SuiteIFace --output channels/app/platform/mocks --note 'Regenerate this file using `make platform-mocks`.'
pluginapi: ## Generates api and hooks glue code for plugins pluginapi: ## Generates api and hooks glue code for plugins
$(GO) generate $(GOFLAGS) ../plugin $(GO) generate $(GOFLAGS) ./plugin
mocks: store-mocks telemetry-mocks filestore-mocks ldap-mocks plugin-mocks einterfaces-mocks searchengine-mocks sharedchannel-mocks misc-mocks email-mocks platform-mocks mocks: store-mocks telemetry-mocks filestore-mocks ldap-mocks plugin-mocks einterfaces-mocks searchengine-mocks sharedchannel-mocks misc-mocks email-mocks platform-mocks
@ -412,7 +418,7 @@ go-junit-report:
test-compile: ## Compile tests. test-compile: ## Compile tests.
@echo COMPILE TESTS @echo COMPILE TESTS
for package in $(TE_PACKAGES) $(BOARDS_PACKAGES) $(PLAYBOOKS_PACKAGES) $(EE_PACKAGES); do \ for package in $(SUITE_PACKAGES) $(EE_PACKAGES); do \
$(GO) test $(GOFLAGS) -c $$package; \ $(GO) test $(GOFLAGS) -c $$package; \
done done
@ -450,9 +456,9 @@ else
endif endif
test-server-race: test-server-pre test-server-race: test-server-pre
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic" ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic" ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic" ./scripts/test.sh "$(GO)" "-race $(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "atomic"
ifneq ($(IS_CI),true) ifneq ($(IS_CI),true)
ifneq ($(MM_NO_DOCKER),true) ifneq ($(MM_NO_DOCKER),true)
ifneq ($(TEMP_DOCKER_SERVICES),) ifneq ($(TEMP_DOCKER_SERVICES),)
@ -463,9 +469,7 @@ ifneq ($(IS_CI),true)
endif endif
test-server: test-server-pre test-server: test-server-pre
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(TE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count" ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(SUITE_PACKAGES) $(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "90m" "count"
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(BOARDS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(PLAYBOOKS_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "45m" "count"
ifneq ($(IS_CI),true) ifneq ($(IS_CI),true)
ifneq ($(MM_NO_DOCKER),true) ifneq ($(MM_NO_DOCKER),true)
ifneq ($(TEMP_DOCKER_SERVICES),) ifneq ($(TEMP_DOCKER_SERVICES),)
@ -477,19 +481,15 @@ endif
test-server-ee: check-prereqs-enterprise start-docker go-junit-report do-cover-file ## Runs EE tests. test-server-ee: check-prereqs-enterprise start-docker go-junit-report do-cover-file ## Runs EE tests.
@echo Running only EE tests @echo Running only EE tests
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "20m" "count" ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(EE_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" "$(GOBIN)" "20m" "count"
test-server-quick: check-prereqs-enterprise ## Runs only quick tests. test-server-quick: check-prereqs-enterprise ## Runs only quick tests.
ifeq ($(BUILD_ENTERPRISE_READY),true) ifeq ($(BUILD_ENTERPRISE_READY),true)
@echo Running all tests @echo Running all tests
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(TE_PACKAGES) $(EE_PACKAGES) $(GO) test $(GOFLAGS) -short $(SUITE_PACKAGES) $(EE_PACKAGES)
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
else else
@echo Running only TE tests @echo Running only TE tests
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(TE_PACKAGES) $(GO) test $(GOFLAGS) -short $(SUITE_PACKAGES)
MM_DISABLE_PLAYBOOKS=true MM_DISABLE_BOARDS=false $(GO) test $(GOFLAGS) -short $(BOARDS_PACKAGES)
MM_DISABLE_PLAYBOOKS=false MM_DISABLE_BOARDS=true $(GO) test $(GOFLAGS) -short $(PLAYBOOKS_PACKAGES)
endif endif
internal-test-web-client: ## Runs web client tests. internal-test-web-client: ## Runs web client tests.
@ -552,20 +552,20 @@ run-server: setup-go-work prepackaged-binaries validate-go-version start-docker
debug-server: start-docker ## Compile and start server using delve. debug-server: start-docker ## Compile and start server using delve.
mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files
$(DELVE) debug $(PLATFORM_FILES) --build-flags="-ldflags '\ $(DELVE) debug $(PLATFORM_FILES) --build-flags="-ldflags '\
-X github.com/mattermost/mattermost-server/v6/model.BuildNumber=$(BUILD_NUMBER)\ -X github.com/mattermost/mattermost-server/server/v8/model.BuildNumber=$(BUILD_NUMBER)\
-X \"github.com/mattermost/mattermost-server/v6/model.BuildDate=$(BUILD_DATE)\"\ -X \"github.com/mattermost/mattermost-server/server/v8/model.BuildDate=$(BUILD_DATE)\"\
-X github.com/mattermost/mattermost-server/v6/model.BuildHash=$(BUILD_HASH)\ -X github.com/mattermost/mattermost-server/server/v8/model.BuildHash=$(BUILD_HASH)\
-X github.com/mattermost/mattermost-server/v6/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)\ -X github.com/mattermost/mattermost-server/server/v8/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)\
-X github.com/mattermost/mattermost-server/v6/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'" -X github.com/mattermost/mattermost-server/server/v8/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'"
debug-server-headless: start-docker ## Debug server from within an IDE like VSCode or IntelliJ. debug-server-headless: start-docker ## Debug server from within an IDE like VSCode or IntelliJ.
mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files
$(DELVE) debug --headless --listen=:2345 --api-version=2 --accept-multiclient $(PLATFORM_FILES) --build-flags="-ldflags '\ $(DELVE) debug --headless --listen=:2345 --api-version=2 --accept-multiclient $(PLATFORM_FILES) --build-flags="-ldflags '\
-X github.com/mattermost/mattermost-server/v6/model.BuildNumber=$(BUILD_NUMBER)\ -X github.com/mattermost/mattermost-server/server/v8/model.BuildNumber=$(BUILD_NUMBER)\
-X \"github.com/mattermost/mattermost-server/v6/model.BuildDate=$(BUILD_DATE)\"\ -X \"github.com/mattermost/mattermost-server/server/v8/model.BuildDate=$(BUILD_DATE)\"\
-X github.com/mattermost/mattermost-server/v6/model.BuildHash=$(BUILD_HASH)\ -X github.com/mattermost/mattermost-server/server/v8/model.BuildHash=$(BUILD_HASH)\
-X github.com/mattermost/mattermost-server/v6/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)\ -X github.com/mattermost/mattermost-server/server/v8/model.BuildHashEnterprise=$(BUILD_HASH_ENTERPRISE)\
-X github.com/mattermost/mattermost-server/v6/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'" -X github.com/mattermost/mattermost-server/server/v8/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'"
run-cli: start-docker ## Runs CLI. run-cli: start-docker ## Runs CLI.
@echo Running mattermost for development @echo Running mattermost for development
@ -733,18 +733,18 @@ gen-serialized: ## Generates serialization methods for hot structs
# would be to temporarily move all the structs to the same file, # would be to temporarily move all the structs to the same file,
# but that involves a lot of manual work. # but that involves a lot of manual work.
$(GO) install github.com/tinylib/msgp@v1.1.6 $(GO) install github.com/tinylib/msgp@v1.1.6
$(GOBIN)/msgp -file=../model/session.go -tests=false -o=../model/session_serial_gen.go $(GOBIN)/msgp -file=./model/session.go -tests=false -o=./model/session_serial_gen.go
@echo "$$LICENSE_HEADER" > tmp.go @echo "$$LICENSE_HEADER" > tmp.go
@cat ../model/session_serial_gen.go >> tmp.go @cat ./model/session_serial_gen.go >> tmp.go
@mv tmp.go ../model/session_serial_gen.go @mv tmp.go ./model/session_serial_gen.go
$(GOBIN)/msgp -file=../model/user.go -tests=false -o=../model/user_serial_gen.go $(GOBIN)/msgp -file=./model/user.go -tests=false -o=./model/user_serial_gen.go
@echo "$$LICENSE_HEADER" > tmp.go @echo "$$LICENSE_HEADER" > tmp.go
@cat ../model/user_serial_gen.go >> tmp.go @cat ./model/user_serial_gen.go >> tmp.go
@mv tmp.go ../model/user_serial_gen.go @mv tmp.go ./model/user_serial_gen.go
$(GOBIN)/msgp -file=../model/team_member.go -tests=false -o=../model/team_member_serial_gen.go $(GOBIN)/msgp -file=./model/team_member.go -tests=false -o=./model/team_member_serial_gen.go
@echo "$$LICENSE_HEADER" > tmp.go @echo "$$LICENSE_HEADER" > tmp.go
@cat ../model/team_member_serial_gen.go >> tmp.go @cat ./model/team_member_serial_gen.go >> tmp.go
@mv tmp.go ../model/team_member_serial_gen.go @mv tmp.go ./model/team_member_serial_gen.go
todo: ## Display TODO and FIXME items in the source code. todo: ## Display TODO and FIXME items in the source code.
@! ag --ignore Makefile --ignore-dir runtime '(TODO|XXX|FIXME|"FIX ME")[: ]+' @! ag --ignore Makefile --ignore-dir runtime '(TODO|XXX|FIXME|"FIX ME")[: ]+'

View File

@ -11,10 +11,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
type AdminSetPasswordData struct { type AdminSetPasswordData struct {

View File

@ -12,12 +12,12 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/app" "github.com/mattermost/mattermost-server/server/v8/boards/app"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/boards/services/permissions" "github.com/mattermost/mattermost-server/server/v8/boards/services/permissions"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
const ( const (

View File

@ -13,8 +13,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
func TestErrorResponse(t *testing.T) { func TestErrorResponse(t *testing.T) {

View File

@ -10,11 +10,11 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
mm_model "github.com/mattermost/mattermost-server/v6/model" mm_model "github.com/mattermost/mattermost-server/server/v8/model"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
const ( const (

View File

@ -6,8 +6,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
) )
// makeAuditRecord creates an audit record pre-populated with data from the request. // makeAuditRecord creates an audit record pre-populated with data from the request.

View File

@ -13,12 +13,12 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/boards/services/auth" "github.com/mattermost/mattermost-server/server/v8/boards/services/auth"
"github.com/mattermost/mattermost-server/v6/server/boards/utils" "github.com/mattermost/mattermost-server/server/v8/boards/utils"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
func (a *API) registerAuthRoutes(r *mux.Router) { func (a *API) registerAuthRoutes(r *mux.Router) {

View File

@ -12,10 +12,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
func (a *API) registerBlocksRoutes(r *mux.Router) { func (a *API) registerBlocksRoutes(r *mux.Router) {
@ -72,7 +72,6 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query() query := r.URL.Query()
parentID := query.Get("parent_id") parentID := query.Get("parent_id")
blockType := query.Get("type") blockType := query.Get("type")
all := query.Get("all")
blockID := query.Get("block_id") blockID := query.Get("block_id")
boardID := mux.Vars(r)["boardID"] boardID := mux.Vars(r)["boardID"]
@ -122,18 +121,11 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("boardID", boardID) auditRec.AddMeta("boardID", boardID)
auditRec.AddMeta("parentID", parentID) auditRec.AddMeta("parentID", parentID)
auditRec.AddMeta("blockType", blockType) auditRec.AddMeta("blockType", blockType)
auditRec.AddMeta("all", all)
auditRec.AddMeta("blockID", blockID) auditRec.AddMeta("blockID", blockID)
var blocks []*model.Block var blocks []*model.Block
var block *model.Block var block *model.Block
switch { switch {
case all != "":
blocks, err = a.app.GetBlocksForBoard(boardID)
if err != nil {
a.errorResponse(w, r, err)
return
}
case blockID != "": case blockID != "":
block, err = a.app.GetBlockByID(blockID) block, err = a.app.GetBlockByID(blockID)
if err != nil { if err != nil {
@ -148,7 +140,12 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
blocks = append(blocks, block) blocks = append(blocks, block)
default: default:
blocks, err = a.app.GetBlocks(boardID, parentID, blockType) opts := model.QueryBlocksOptions{
BoardID: boardID,
ParentID: parentID,
BlockType: model.BlockType(blockType),
}
blocks, err = a.app.GetBlocks(opts)
if err != nil { if err != nil {
a.errorResponse(w, r, err) a.errorResponse(w, r, err)
return return
@ -307,7 +304,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
// this query param exists when creating template from board, or board from template // this query param exists when creating template from board, or board from template
sourceBoardID := r.URL.Query().Get("sourceBoardID") sourceBoardID := r.URL.Query().Get("sourceBoardID")
if sourceBoardID != "" { if sourceBoardID != "" {
if updateFileIDsErr := a.app.CopyCardFiles(sourceBoardID, blocks); updateFileIDsErr != nil { if updateFileIDsErr := a.app.CopyAndUpdateCardFiles(sourceBoardID, userID, blocks, false); updateFileIDsErr != nil {
a.errorResponse(w, r, updateFileIDsErr) a.errorResponse(w, r, updateFileIDsErr)
return return
} }

View File

@ -10,10 +10,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
func (a *API) registerBoardsRoutes(r *mux.Router) { func (a *API) registerBoardsRoutes(r *mux.Router) {

View File

@ -11,10 +11,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
func (a *API) registerBoardsAndBlocksRoutes(r *mux.Router) { func (a *API) registerBoardsAndBlocksRoutes(r *mux.Router) {

View File

@ -12,10 +12,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
const ( const (

View File

@ -11,8 +11,8 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
) )
func (a *API) registerCategoriesRoutes(r *mux.Router) { func (a *API) registerCategoriesRoutes(r *mux.Router) {

View File

@ -10,11 +10,11 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
mm_model "github.com/mattermost/mattermost-server/v6/model" mm_model "github.com/mattermost/mattermost-server/server/v8/model"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
func (a *API) registerChannelsRoutes(r *mux.Router) { func (a *API) registerChannelsRoutes(r *mux.Router) {

View File

@ -11,10 +11,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
mm_model "github.com/mattermost/mattermost-server/v6/model" mm_model "github.com/mattermost/mattermost-server/server/v8/model"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
) )
const ( const (

View File

@ -8,8 +8,8 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
) )
func (a *API) registerContentBlocksRoutes(r *mux.Router) { func (a *API) registerContentBlocksRoutes(r *mux.Router) {

View File

@ -11,17 +11,17 @@ import (
"strings" "strings"
"time" "time"
"github.com/mattermost/mattermost-server/v6/server/boards/app" "github.com/mattermost/mattermost-server/server/v8/boards/app"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
mm_model "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" mm_model "github.com/mattermost/mattermost-server/server/v8/model"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/web" "github.com/mattermost/mattermost-server/server/v8/platform/shared/web"
) )
// FileUploadResponse is the response to a file upload // FileUploadResponse is the response to a file upload
@ -312,7 +312,7 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("teamID", board.TeamID) auditRec.AddMeta("teamID", board.TeamID)
auditRec.AddMeta("filename", handle.Filename) auditRec.AddMeta("filename", handle.Filename)
fileID, err := a.app.SaveFile(file, board.TeamID, boardID, handle.Filename) fileID, err := a.app.SaveFile(file, board.TeamID, boardID, handle.Filename, board.IsTemplate)
if err != nil { if err != nil {
a.errorResponse(w, r, err) a.errorResponse(w, r, err)
return return

View File

@ -12,10 +12,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/v6/server/boards/model" "github.com/mattermost/mattermost-server/server/v8/boards/model"
"github.com/mattermost/mattermost-server/v6/server/boards/services/audit" "github.com/mattermost/mattermost-server/server/v8/boards/services/audit"
mm_model "github.com/mattermost/mattermost-server/v6/model" mm_model "github.com/mattermost/mattermost-server/server/v8/model"
) )
func (a *API) registerInsightsRoutes(r *mux.Router) { func (a *API) registerInsightsRoutes(r *mux.Router) {

Some files were not shown because too many files have changed in this diff Show More