Compare commits
222 Commits
v1.6.1
...
techdept/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e23aa63be | ||
|
|
4f23ebedb6 | ||
|
|
6e8ca6c067 | ||
|
|
6d6a31037e | ||
|
|
7814d59e0c | ||
|
|
cc2ff38320 | ||
|
|
a43de8ec29 | ||
|
|
a1bd914b48 | ||
|
|
e42ffa4ca2 | ||
|
|
8c33760c79 | ||
|
|
c5a9a0897e | ||
|
|
1e32a01be4 | ||
|
|
d44f489173 | ||
|
|
cfa67f1cbd | ||
|
|
97aa71e1fb | ||
|
|
c3e5cf6851 | ||
|
|
84d3d6b834 | ||
|
|
778f0cda64 | ||
|
|
504916608a | ||
|
|
33b99290c2 | ||
|
|
132da8cf02 | ||
|
|
3744541cd6 | ||
|
|
da98ab81d6 | ||
|
|
9a9b64f448 | ||
|
|
ed7e2fe1b9 | ||
|
|
634c9d7768 | ||
|
|
5988975324 | ||
|
|
ffd5bd784d | ||
|
|
44b23688f5 | ||
|
|
ef8c542289 | ||
|
|
c0d2154e46 | ||
|
|
c9d34ab6df | ||
|
|
1dc62207ba | ||
|
|
fe007ca1d3 | ||
|
|
887b1e46e7 | ||
|
|
f04fa03baa | ||
|
|
106bc7f47f | ||
|
|
4b5b4badd4 | ||
|
|
f353f1cdd4 | ||
|
|
1d7aa5d1f0 | ||
|
|
69ff35653e | ||
|
|
2c487a4375 | ||
|
|
6cc2ef684f | ||
|
|
fb1177fa76 | ||
|
|
6347fee2a1 | ||
|
|
e12b6658ec | ||
|
|
e23027094f | ||
|
|
89f31635c5 | ||
|
|
2cfb107167 | ||
|
|
5d562db408 | ||
|
|
9993a198b1 | ||
|
|
cf7c327816 | ||
|
|
f2c1b42811 | ||
|
|
b40f8609be | ||
|
|
c73be045c3 | ||
|
|
a7243f7573 | ||
|
|
ad7f4aa22b | ||
|
|
bc263b6da5 | ||
|
|
a3953344b5 | ||
|
|
99973b4501 | ||
|
|
4e31332fe4 | ||
|
|
51dffdacd6 | ||
|
|
054c5aaf8c | ||
|
|
4f49254cfe | ||
|
|
a11bf5f4ba | ||
|
|
9d3de1e576 | ||
|
|
b00c8a1196 | ||
|
|
ee3eb99cc7 | ||
|
|
b73462555b | ||
|
|
24c1687857 | ||
|
|
123a5b8b13 | ||
|
|
a048d40cd7 | ||
|
|
b26f61b9a3 | ||
|
|
bf7203210c | ||
|
|
53d70321da | ||
|
|
c070b18b1b | ||
|
|
3446c4aa4f | ||
|
|
19dad95c55 | ||
|
|
fc58528817 | ||
|
|
ced39c9501 | ||
|
|
ae27e431b2 | ||
|
|
d820d57661 | ||
|
|
adb90f2a51 | ||
|
|
c1e6f22fa1 | ||
|
|
c39d07db1a | ||
|
|
0ab5707c4f | ||
|
|
a47110d6f7 | ||
|
|
2d2671fd77 | ||
|
|
55cf2a6214 | ||
|
|
e488d42935 | ||
|
|
6d8a5bc956 | ||
|
|
81c908a59d | ||
|
|
84910d3d3e | ||
|
|
3e6ade718f | ||
|
|
b5ed3b122a | ||
|
|
3dfc33378d | ||
|
|
e4dbd9e385 | ||
|
|
61910290b9 | ||
|
|
bce79c596b | ||
|
|
6be3c3fe67 | ||
|
|
eaf6defe59 | ||
|
|
bfc8222e6f | ||
|
|
66fa241382 | ||
|
|
7b237d8cd8 | ||
|
|
5fc1aba9cd | ||
|
|
3900a15b4b | ||
|
|
cf8023855b | ||
|
|
ff41bbab7b | ||
|
|
bec07726a7 | ||
|
|
c24e72f161 | ||
|
|
6296ebb87c | ||
|
|
a96bb277a4 | ||
|
|
b58913e730 | ||
|
|
ccd5bce7ea | ||
|
|
f2b6934ac3 | ||
|
|
24c8b2f4aa | ||
|
|
a3959e3cfc | ||
|
|
fda8a03c43 | ||
|
|
5b30577df0 | ||
|
|
4561887348 | ||
|
|
e87c063076 | ||
|
|
202ea30090 | ||
|
|
c7a37ea425 | ||
|
|
19c609540b | ||
|
|
6714c89220 | ||
|
|
e01e4cf1a7 | ||
|
|
4138953208 | ||
|
|
39a927de18 | ||
|
|
c5d10dafb8 | ||
|
|
fd92fc3c4d | ||
|
|
eb8bf3f22b | ||
|
|
e28a47e9e0 | ||
|
|
48df98ce67 | ||
|
|
89028c74cb | ||
|
|
4e5537f204 | ||
|
|
f74bca8c43 | ||
|
|
30e4b43e46 | ||
|
|
596834853b | ||
|
|
eff3c94c6a | ||
|
|
8aa3edee58 | ||
|
|
d3404c7489 | ||
|
|
84bcd2e502 | ||
|
|
f645937b10 | ||
|
|
d9af04121d | ||
|
|
51bcbdb87d | ||
|
|
25dee609b5 | ||
|
|
14ab4597c5 | ||
|
|
d3146b4019 | ||
|
|
4b02be2028 | ||
|
|
476d607148 | ||
|
|
99f17823ec | ||
|
|
ad537162c8 | ||
|
|
3a243b1fc7 | ||
|
|
bfcbe0306e | ||
|
|
d060a842b4 | ||
|
|
90a3339e18 | ||
|
|
90287606c1 | ||
|
|
f65b3801cc | ||
|
|
b4f35bf2fd | ||
|
|
a857c63b35 | ||
|
|
5802a31e93 | ||
|
|
d459995df3 | ||
|
|
5be01e60fb | ||
|
|
8443549d74 | ||
|
|
23e532a9c2 | ||
|
|
2a41d98c6f | ||
|
|
4c4b8f3bed | ||
|
|
1806f0817b | ||
|
|
e855ef3414 | ||
|
|
82232e8890 | ||
|
|
99c880df18 | ||
|
|
144ca0d39d | ||
|
|
71fbdfeba5 | ||
|
|
101995598b | ||
|
|
f5c35729ca | ||
|
|
fc1983869b | ||
|
|
081b5119f5 | ||
|
|
a22e5f7719 | ||
|
|
8ef118ad0f | ||
|
|
86945d5030 | ||
|
|
c63423c25a | ||
|
|
8db48106b9 | ||
|
|
6d201a1f13 | ||
|
|
5425536fc0 | ||
|
|
92acaa0011 | ||
|
|
a5b76991b8 | ||
|
|
33f5af41c8 | ||
|
|
f5223d90a0 | ||
|
|
4a51335a28 | ||
|
|
e5ffe95c17 | ||
|
|
3096b701b6 | ||
|
|
caf2e688f7 | ||
|
|
3269845cfd | ||
|
|
5349fcc707 | ||
|
|
0f095e9b69 | ||
|
|
3affa7b5ec | ||
|
|
4ec57d337b | ||
|
|
6fd83258a0 | ||
|
|
901b8f2506 | ||
|
|
80388d1a88 | ||
|
|
f90c9602b8 | ||
|
|
f861f9e5fc | ||
|
|
bbfb155802 | ||
|
|
10ab8c8688 | ||
|
|
24a6d088ca | ||
|
|
00d386dcaf | ||
|
|
b71f91c439 | ||
|
|
7aa35bb728 | ||
|
|
800412237d | ||
|
|
ca411c6168 | ||
|
|
d414ffe937 | ||
|
|
ad483f3613 | ||
|
|
8311a13275 | ||
|
|
e2a7063772 | ||
|
|
1dbf36ae07 | ||
|
|
29278a51e5 | ||
|
|
31e48ce404 | ||
|
|
404a7eb412 | ||
|
|
a85a6db368 | ||
|
|
9464337036 | ||
|
|
87676b49dd | ||
|
|
6248089d8b |
4
.github/workflows/appbuild.yml
vendored
4
.github/workflows/appbuild.yml
vendored
@@ -14,13 +14,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
|
||||
151
.github/workflows/appstore-build-publish.yml
vendored
151
.github/workflows/appstore-build-publish.yml
vendored
@@ -1,151 +0,0 @@
|
||||
# This workflow is provided via the organization template repository
|
||||
#
|
||||
# https://github.com/nextcloud/.github
|
||||
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||
|
||||
name: Build and publish app release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
PHP_VERSION: 7.4
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Only allowed to be run on nextcloud-releases repositories
|
||||
if: ${{ github.repository_owner == 'nextcloud-releases' }}
|
||||
|
||||
steps:
|
||||
- name: Check actor permission
|
||||
uses: skjnldsv/check-actor-permission@v2
|
||||
with:
|
||||
require: write
|
||||
|
||||
- name: Set app env
|
||||
run: |
|
||||
# Split and keep last
|
||||
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ env.APP_NAME }}
|
||||
|
||||
- name: Get appinfo data
|
||||
id: appinfo
|
||||
uses: skjnldsv/xpath-action@master
|
||||
with:
|
||||
filename: ${{ env.APP_NAME }}/appinfo/info.xml
|
||||
expression: "//info//dependencies//nextcloud/@min-version"
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@v1.2
|
||||
id: versions
|
||||
# Continue if no package.json
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ${{ env.APP_NAME }}
|
||||
fallbackNode: "^12"
|
||||
fallbackNpm: "^6"
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
# Skip if no package.json
|
||||
if: ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||
|
||||
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
||||
# Skip if no package.json
|
||||
if: ${{ steps.versions.outputs.npmVersion }}
|
||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||
|
||||
- name: Set up php ${{ env.PHP_VERSION }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
coverage: none
|
||||
|
||||
- name: Check composer.json
|
||||
id: check_composer
|
||||
uses: andstor/file-existence-action@v1
|
||||
with:
|
||||
files: "${{ env.APP_NAME }}/composer.json"
|
||||
|
||||
- name: Install composer dependencies
|
||||
if: steps.check_composer.outputs.files_exists == 'true'
|
||||
run: |
|
||||
cd ${{ env.APP_NAME }}
|
||||
composer install --no-dev
|
||||
|
||||
- name: Build ${{ env.APP_NAME }}
|
||||
# Skip if no package.json
|
||||
if: ${{ steps.versions.outputs.nodeVersion }}
|
||||
run: |
|
||||
cd ${{ env.APP_NAME }}
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Install Krankerl
|
||||
run: |
|
||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.13.0/krankerl_0.13.0_amd64.deb
|
||||
sudo dpkg -i krankerl_0.13.0_amd64.deb
|
||||
|
||||
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }}
|
||||
# Try krankerl, fallback to makefile
|
||||
run: |
|
||||
cd ${{ env.APP_NAME }}
|
||||
krankerl package || make appstore
|
||||
|
||||
- name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
|
||||
continue-on-error: true
|
||||
id: server-checkout
|
||||
run: |
|
||||
NCVERSION=${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
|
||||
wget --quiet https://download.nextcloud.com/server/releases/latest-$NCVERSION.zip
|
||||
unzip latest-$NCVERSION.zip
|
||||
|
||||
- name: Checkout server master fallback
|
||||
uses: actions/checkout@v3
|
||||
if: ${{ steps.server-checkout.outcome != 'success' }}
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
path: nextcloud
|
||||
|
||||
- name: Sign app
|
||||
run: |
|
||||
# Extracting release
|
||||
cd ${{ env.APP_NAME }}/build/artifacts
|
||||
tar -xvf ${{ env.APP_NAME }}.tar.gz
|
||||
cd ../../../
|
||||
# Setting up keys
|
||||
echo "${{ secrets.APP_PRIVATE_KEY }}" > ${{ env.APP_NAME }}.key
|
||||
wget --quiet "https://github.com/nextcloud/app-certificate-requests/raw/master/${{ env.APP_NAME }}/${{ env.APP_NAME }}.crt"
|
||||
# Signing
|
||||
php nextcloud/occ integrity:sign-app --privateKey=../${{ env.APP_NAME }}.key --certificate=../${{ env.APP_NAME }}.crt --path=../${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}
|
||||
# Rebuilding archive
|
||||
cd ${{ env.APP_NAME }}/build/artifacts
|
||||
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
|
||||
|
||||
- name: Attach tarball to github release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
id: attach_to_release
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz
|
||||
asset_name: ${{ env.APP_NAME }}-${{ env.APP_VERSION }}.tar.gz
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
- name: Upload app to Nextcloud appstore
|
||||
uses: nextcloud-releases/nextcloud-appstore-push-action@v1
|
||||
with:
|
||||
app_name: ${{ env.APP_NAME }}
|
||||
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
|
||||
download_url: ${{ steps.attach_to_release.outputs.browser_download_url }}
|
||||
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
@@ -25,5 +25,5 @@ jobs:
|
||||
# Nextcloud bot approve and merge request
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
target: patch
|
||||
target: minor
|
||||
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
|
||||
|
||||
4
.github/workflows/integration.yml
vendored
4
.github/workflows/integration.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
matrix:
|
||||
php-versions: ['7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable23']
|
||||
server-versions: ['master']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
10
.github/workflows/lint.yml
vendored
10
.github/workflows/lint.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.2', '7.3', '7.4']
|
||||
php-versions: ['7.4', '8.0']
|
||||
|
||||
name: php${{ matrix.php-versions }} lint
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- name: Set up php${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- name: Use node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Set up node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
|
||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -19,13 +19,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
|
||||
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2.4.1
|
||||
uses: actions/setup-node@v2.5.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
|
||||
6
.github/workflows/phpunit.yml
vendored
6
.github/workflows/phpunit.yml
vendored
@@ -18,9 +18,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4']
|
||||
php-versions: ['7.4', '8.0']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable23']
|
||||
server-versions: ['master']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
4
.github/workflows/static-analysis.yml
vendored
4
.github/workflows/static-analysis.yml
vendored
@@ -12,13 +12,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ocp-version: [ 'dev-stable23' ]
|
||||
ocp-version: [ 'dev-master' ]
|
||||
name: Nextcloud ${{ matrix.ocp-version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.15.0
|
||||
uses: shivammathur/setup-php@2.17.0
|
||||
with:
|
||||
php-version: 7.4
|
||||
tools: composer:v1
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,26 +1,10 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.6.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Exclude deleted boards in the selection for target [#3523](https://api.github.com/repos/nextcloud/deck/pulls/3523)
|
||||
- CardApiController: Fix order of optional parameters [#3520](https://api.github.com/repos/nextcloud/deck/pulls/3520)
|
||||
- Fix cursor generation if no results are found [#3462](https://api.github.com/repos/nextcloud/deck/pulls/3462)
|
||||
- Fix CalDAV blocking and modernize circles API usage [#3526](https://api.github.com/repos/nextcloud/deck/pulls/3526)
|
||||
- Fix overview card listing [#3463](https://api.github.com/repos/nextcloud/deck/pulls/3463)
|
||||
- Generate fixed link for activity emails [#3626](https://api.github.com/repos/nextcloud/deck/pulls/3626)
|
||||
- return the selector for collections [#3618](https://api.github.com/repos/nextcloud/deck/pulls/3618)
|
||||
- Fix confusion between stackId and boardId in StackService [#3543](https://api.github.com/repos/nextcloud/deck/pulls/3543)
|
||||
- Fix talk integration [#3537](https://api.github.com/repos/nextcloud/deck/pulls/3537)
|
||||
- Make insert attachment buttom easy to click [#3614](https://api.github.com/repos/nextcloud/deck/pulls/3614)
|
||||
|
||||
## 1.6.0
|
||||
## 1.6.0-beta1
|
||||
|
||||
### Added
|
||||
|
||||
- #3449 Cache most frequent queries
|
||||
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
|
||||
- #2791 Open description links in new tab @fm-sys
|
||||
- #3344 Improve combined search @eneiluj
|
||||
@@ -29,11 +13,6 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3446 Switch to QBMapper in BoardMapper
|
||||
- #3433 Fix event name for updating the description
|
||||
- #3463 Fix overview card listing
|
||||
- #3440 Allow to download an attachment without navigating to the files app
|
||||
- #3462 Fix cursor generation if no results are found
|
||||
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
|
||||
- #3151 Always log generic exceptions @juliushaertl
|
||||
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
|
||||
|
||||
- 📥 Add your tasks to cards and put them in order
|
||||
- 📄 Write down additional notes in markdown
|
||||
- 📄 Write down additional notes in Markdown
|
||||
- 🔖 Assign labels for even better organization
|
||||
- 👥 Share with your team, friends or family
|
||||
- 📎 Attach files and embed them in your markdown description
|
||||
- 📎 Attach files and embed them in your Markdown description
|
||||
- 💬 Discuss with your team using comments
|
||||
- ⚡ Keep track of changes in the activity stream
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.6.1</version>
|
||||
<version>1.7.0-alpha1</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -31,11 +31,10 @@
|
||||
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-1.png</screenshot>
|
||||
<screenshot>https://download.bitgrid.net/nextcloud/deck/screenshots/1.0/Deck-2.png</screenshot>
|
||||
<dependencies>
|
||||
<php min-version="7.3"/>
|
||||
<database min-version="9.4">pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database min-version="5.5">mysql</database>
|
||||
<nextcloud min-version="23" max-version="23"/>
|
||||
<database min-version="8.0">mysql</database>
|
||||
<nextcloud min-version="24" max-version="24"/>
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||
@@ -44,6 +43,7 @@
|
||||
</background-jobs>
|
||||
<commands>
|
||||
<command>OCA\Deck\Command\UserExport</command>
|
||||
<command>OCA\Deck\Command\BoardImport</command>
|
||||
</commands>
|
||||
<activity>
|
||||
<settings>
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
||||
['name' => 'page#redirectToCard', 'url' => '/card/{cardId}', 'verb' => 'GET'],
|
||||
|
||||
// boards
|
||||
['name' => 'board#index', 'url' => '/boards', 'verb' => 'GET'],
|
||||
@@ -91,6 +90,10 @@ return [
|
||||
['name' => 'board_api#deleteAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#updateAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
|
||||
['name' => 'board_import_api#getAllowedSystems', 'url' => '/api/v{apiVersion}/boards/import/getSystems','verb' => 'GET'],
|
||||
['name' => 'board_import_api#getConfigSchema', 'url' => '/api/v{apiVersion}/boards/import/config/schema/{name}','verb' => 'GET'],
|
||||
['name' => 'board_import_api#import', 'url' => '/api/v{apiVersion}/boards/import','verb' => 'POST'],
|
||||
|
||||
|
||||
['name' => 'stack_api#index', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#getArchived', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
|
||||
|
||||
@@ -9,20 +9,27 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"cogpowered/finediff": "0.3.*"
|
||||
"cogpowered/finediff": "0.3.*",
|
||||
"justinrainbow/json-schema": "^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"christophwurst/nextcloud": "^21@dev",
|
||||
"phpunit/phpunit": "^9",
|
||||
"nextcloud/coding-standard": "^0.5.0",
|
||||
"nextcloud/coding-standard": "^1.0.0",
|
||||
"symfony/event-dispatcher": "^4.0",
|
||||
"vimeo/psalm": "^4.3",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true
|
||||
"classmap-authoritative": true,
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "7.4"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
|
||||
|
||||
1333
composer.lock
generated
1333
composer.lock
generated
File diff suppressed because it is too large
Load Diff
68
docs/API.md
68
docs/API.md
@@ -96,10 +96,27 @@ If available the ETag will also be part of JSON response objects as shown below
|
||||
|
||||
# Changelog
|
||||
|
||||
## 1.0.0 (unreleased)
|
||||
## API version 1.0
|
||||
|
||||
- Deck >=1.0.0: The maximum length of the card title has been extended from 100 to 255 characters
|
||||
- Deck >=1.0.0: The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
|
||||
|
||||
## API version 1.1
|
||||
|
||||
This API version has become available with **Deck 1.3.0**.
|
||||
|
||||
- The maximum length of the card title has been extended from 100 to 255 characters
|
||||
- The API will now return a 400 Bad request response if the length limitation of a board, stack or card title is exceeded
|
||||
- The attachments API endpoints will return other attachment types than deck_file
|
||||
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
|
||||
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
|
||||
|
||||
## API version 1.2 (unreleased)
|
||||
|
||||
- Endpoints for the new import functionality have been added:
|
||||
- [GET /boards/import/getSystems - Import a board](#get-boardsimportgetsystems-import-a-board)
|
||||
- [GET /boards/import/config/system/{schema} - Import a board](#get-boardsimportconfigsystemschema-import-a-board)
|
||||
- [POST /boards/import - Import a board](#post-boardsimport-import-a-board)
|
||||
|
||||
# Endpoints
|
||||
|
||||
@@ -927,7 +944,8 @@ The request can fail with a bad request response for the following reasons:
|
||||
| type | String | The type of the attachement |
|
||||
| file | Binary | File data to add as an attachment |
|
||||
|
||||
For now only `deck_file` is supported as an attachment type.
|
||||
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
|
||||
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -988,6 +1006,49 @@ For now only `deck_file` is supported as an attachment type.
|
||||
|
||||
##### 200 Success
|
||||
|
||||
### GET /boards/import/getSystems - Import a board
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | ------- | --------------------------------------------- |
|
||||
| system | Integer | The system name. Example: trello |
|
||||
|
||||
#### Response
|
||||
|
||||
Make a request to see the json schema of system
|
||||
|
||||
```json
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
### GET /boards/import/config/system/{schema} - Import a board
|
||||
|
||||
#### Request parameters
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
[
|
||||
"trello"
|
||||
]
|
||||
```
|
||||
|
||||
### POST /boards/import - Import a board
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | ------- | --------------------------------------------- |
|
||||
| system | string | The allowed name of system to import from |
|
||||
| config | Object | The config object (JSON) |
|
||||
| data | Object | The data object to import (JSON) |
|
||||
|
||||
#### Response
|
||||
|
||||
##### 200 Success
|
||||
|
||||
# OCS API
|
||||
|
||||
The following endpoints are available through the Nextcloud OCS endpoint, which is available at `/ocs/v2.php/apps/deck/api/v1.0/`.
|
||||
@@ -1004,6 +1065,7 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
| Config key | Description |
|
||||
| --- | --- |
|
||||
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
|
||||
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
|
||||
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
|
||||
|
||||
```
|
||||
@@ -1016,6 +1078,7 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
},
|
||||
"data": {
|
||||
"calendar": true,
|
||||
"cardDetailsInModal": true,
|
||||
"groupLimit": [
|
||||
{
|
||||
"id": "admin",
|
||||
@@ -1045,6 +1108,7 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
| --- | ----- |
|
||||
| notify-due | `off`, `assigned` or `all` |
|
||||
| calendar | Boolean |
|
||||
| cardDetailsInModal | Boolean |
|
||||
|
||||
#### Example request
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ Overall, Deck is easy to use. You can create boards, add users, share the Deck,
|
||||
3. [Handle cards options](#3-handle-cards-options)
|
||||
4. [Archive old tasks](#4-archive-old-tasks)
|
||||
5. [Manage your board](#5-manage-your-board)
|
||||
6. [Import boards](#6-import-boards)
|
||||
7. [Search](#7-search)
|
||||
|
||||
### 1. Create my first board
|
||||
In this example, we're going to create a board and share it with an other nextcloud user.
|
||||
@@ -69,14 +71,80 @@ The **sharing tab** allows you to add users or even groups to your boards.
|
||||
**Deleted objects** allows you to return previously deleted stacks or cards.
|
||||
The **Timeline** allows you to see everything that happened in your boards. Everything!
|
||||
|
||||
## Search
|
||||
### 6. Import boards
|
||||
|
||||
Importing can be done using the API or the `occ` `deck:import` command.
|
||||
|
||||
Comments with more than 1000 characters are placed as attached files to the card.
|
||||
|
||||
It is possible to import from the following sources:
|
||||
|
||||
#### Trello JSON
|
||||
|
||||
Steps:
|
||||
* Create the data file
|
||||
* Access Trello
|
||||
* go to the board you want to export
|
||||
* Follow the steps in [Trello documentation](https://help.trello.com/article/747-exporting-data-from-trello-1) and export as JSON
|
||||
* Create the configuration file
|
||||
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Limitations**:
|
||||
|
||||
Importing from a JSON file imports up to 1000 actions. To find out how many actions the board to be imported has, identify how many actions the JSON has.
|
||||
|
||||
#### Trello API
|
||||
|
||||
Import using API is recommended for boards with more than 1000 actions.
|
||||
|
||||
Trello makes it possible to attach links to a card. Deck does not have this feature. Attachments and attachment links are added in a markdown table at the end of the description for every imported card that has attachments in Trello.
|
||||
|
||||
* Get the API Key and API Token [here](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/#authentication-and-authorization)
|
||||
* Get the ID of the board you want to import by making a request to:
|
||||
https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&fields=id,name
|
||||
|
||||
This ID you will use in the configuration file in the `board` property
|
||||
* Create the configuration file
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"api": {
|
||||
"key": "0cc175b9c0f1b6a831c399e269772661",
|
||||
"token": "92eb5ffee6ae2fec3ad71c777531578f4a8a08f09d37b73795649038408b5f33"
|
||||
},
|
||||
"board": "8277e0910d750195b4487976",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Search
|
||||
|
||||
Deck provides a global search either through the unified search in the Nextcloud header or with the inline search next to the board controls.
|
||||
This search allows advanced filtering of cards across all board of the logged in user.
|
||||
|
||||
For example the search `project tag:ToDo assigned:alice assigned:bob` will return all cards where the card title or description contains project **and** the tag ToDo is set **and** the user alice is assigned **and** the user bob is assigned.
|
||||
|
||||
### Supported search filters
|
||||
#### Supported search filters
|
||||
|
||||
| Filter | Operators | Query |
|
||||
| ----------- | ----------------- | ------------------------------------------------------------ |
|
||||
|
||||
32
docs/implement-import.md
Normal file
32
docs/implement-import.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Implement import
|
||||
|
||||
* Create a new importer class extending `ABoardImportService`
|
||||
* Create a listener for event `BoardImportGetAllowedEvent` to enable your importer.
|
||||
> You can read more about listeners on [Nextcloud](https://docs.nextcloud.com/server/latest/developer_manual/basics/events.html?highlight=event#writing-a-listener) doc.
|
||||
|
||||
Example:
|
||||
|
||||
```php
|
||||
class YourCustomImporterListener {
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof BoardImportGetAllowedEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getService()->addAllowedImportSystem([
|
||||
'name' => YourCustomImporterService::$name,
|
||||
'class' => YourCustomImporterService::class,
|
||||
'internalName' => 'YourCustomImporter'
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
* Register your listener on your `Application` class like this:
|
||||
```php
|
||||
$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
|
||||
$dispatcher->registerEventListener(
|
||||
BoardImportGetAllowedEvent::class,
|
||||
YourCustomImporterListener::class
|
||||
);
|
||||
```
|
||||
* Use the `lib/Service/Importer/Systems/TrelloJsonService.php` class as inspiration
|
||||
7
docs/import-class-diagram.md
Normal file
7
docs/import-class-diagram.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Import class diagram
|
||||
|
||||
Importing boards to the Deck implements the class diagram below.
|
||||
|
||||
> **NOTE**: When making any changes to the structure of the classes or implementing import from other sources, edit the `BoardImport.yuml` file
|
||||
|
||||

|
||||
214
docs/resources/BoardImport.svg
Normal file
214
docs/resources/BoardImport.svg
Normal file
@@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: G Pages: 1 -->
|
||||
<svg width="417pt" height="830pt"
|
||||
viewBox="0.00 0.00 417.01 830.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 826)">
|
||||
<title>G</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-826 413.012,-826 413.012,4 -4,4"/>
|
||||
<!-- A0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>A0</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="165.909,-822 70.091,-822 70.091,-766 171.909,-766 171.909,-816 165.909,-822"/>
|
||||
<polyline fill="none" stroke="#000000" points="165.909,-822 165.909,-816 "/>
|
||||
<polyline fill="none" stroke="#000000" points="171.909,-816 165.909,-816 "/>
|
||||
<text text-anchor="middle" x="121" y="-809" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Classes used on</text>
|
||||
<text text-anchor="middle" x="121" y="-797" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">board import.</text>
|
||||
<text text-anchor="middle" x="121" y="-785" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Methods just to</text>
|
||||
<text text-anchor="middle" x="121" y="-773" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">illustrate.</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>A1</title>
|
||||
<polygon fill="none" stroke="#000000" points="108.7773,-680 23.2227,-680 23.2227,-644 108.7773,-644 108.7773,-680"/>
|
||||
<text text-anchor="middle" x="66" y="-659" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ApiController</text>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>A2</title>
|
||||
<polygon fill="none" stroke="#000000" points="0,-514 0,-546 132,-546 132,-514 0,-514"/>
|
||||
<text text-anchor="start" x="9.607" y="-527" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportApiController</text>
|
||||
<polygon fill="none" stroke="#000000" points="0,-458 0,-514 132,-514 132,-458 0,-458"/>
|
||||
<text text-anchor="start" x="45.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
|
||||
<text text-anchor="start" x="16.1335" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getAllowedSystems()</text>
|
||||
<text text-anchor="start" x="20.0185" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+getConfigSchema()</text>
|
||||
</g>
|
||||
<!-- A1->A2 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>A1->A2</title>
|
||||
<path fill="none" stroke="#000000" d="M66,-633.6693C66,-609.4424 66,-574.1663 66,-546.2238"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="66,-643.957 61.5001,-633.9569 66,-638.957 66.0001,-633.957 66.0001,-633.957 66.0001,-633.957 66,-638.957 70.5001,-633.957 66,-643.957 66,-643.957"/>
|
||||
</g>
|
||||
<!-- A3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>A3</title>
|
||||
<polygon fill="none" stroke="#000000" points="92,-364 92,-396 200,-396 200,-364 92,-364"/>
|
||||
<text text-anchor="start" x="101.828" y="-377" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportService</text>
|
||||
<polygon fill="none" stroke="#000000" points="92,-284 92,-364 200,-364 200,-284 92,-284"/>
|
||||
<text text-anchor="start" x="125.8645" y="-345" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
|
||||
<text text-anchor="start" x="118.9105" y="-333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
|
||||
<text text-anchor="start" x="105.857" y="-321" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
|
||||
<text text-anchor="start" x="108.218" y="-309" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
|
||||
<text text-anchor="start" x="112.107" y="-297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
|
||||
</g>
|
||||
<!-- A2->A3 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A2->A3</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M87.8604,-457.7328C95.8577,-441.5382 105.0823,-422.8583 113.7939,-405.2174"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="118.2935,-396.1057 117.9004,-407.0646 116.0795,-400.5889 113.8656,-405.072 113.8656,-405.072 113.8656,-405.072 116.0795,-400.5889 109.8308,-403.0795 118.2935,-396.1057 118.2935,-396.1057"/>
|
||||
<text text-anchor="middle" x="88.3076" y="-434.7378" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A7 -->
|
||||
<g id="node8" class="node">
|
||||
<title>A7</title>
|
||||
<polygon fill="none" stroke="#000000" points="37,-196 37,-228 129,-228 129,-196 37,-196"/>
|
||||
<text text-anchor="start" x="46.612" y="-209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloApiService</text>
|
||||
<polygon fill="none" stroke="#000000" points="37,-164 37,-196 129,-196 129,-164 37,-164"/>
|
||||
<text text-anchor="start" x="53.9655" y="-177" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
|
||||
</g>
|
||||
<!-- A3->A7 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>A3->A7</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M114.8609,-283.9135C107.8316,-268.5143 100.7854,-252.0928 95.0404,-237.6613"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="91.2872,-228.0253 99.1098,-235.7102 93.1019,-232.6844 94.9167,-237.3434 94.9167,-237.3434 94.9167,-237.3434 93.1019,-232.6844 90.7235,-238.9767 91.2872,-228.0253 91.2872,-228.0253"/>
|
||||
<text text-anchor="middle" x="99.6759" y="-267.8975" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A9 -->
|
||||
<g id="node10" class="node">
|
||||
<title>A9</title>
|
||||
<polygon fill="none" stroke="#000000" points="148,-202 148,-234 273,-234 273,-202 148,-202"/>
|
||||
<text text-anchor="start" x="170.7765" y="-215" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">TrelloJsonService</text>
|
||||
<polygon fill="none" stroke="#000000" points="148,-158 148,-202 273,-202 273,-158 148,-158"/>
|
||||
<text text-anchor="start" x="181.4655" y="-183" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+name:string</text>
|
||||
<text text-anchor="start" x="157.981" y="-171" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:true</text>
|
||||
</g>
|
||||
<!-- A3->A9 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>A3->A9</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M164.3261,-283.9135C170.0039,-270.5688 176.3462,-256.4563 182.4816,-243.5365"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="186.9002,-234.3677 186.6126,-245.3298 184.7295,-238.872 182.5588,-243.3762 182.5588,-243.3762 182.5588,-243.3762 184.7295,-238.872 178.505,-241.4226 186.9002,-234.3677 186.9002,-234.3677"/>
|
||||
<text text-anchor="middle" x="163.6874" y="-260.9237" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A10 -->
|
||||
<g id="node11" class="node">
|
||||
<title>A10</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="317.7872,-362 218.2128,-362 218.2128,-318 323.7872,-318 323.7872,-356 317.7872,-362"/>
|
||||
<polyline fill="none" stroke="#000000" points="317.7872,-362 317.7872,-356 "/>
|
||||
<polyline fill="none" stroke="#000000" points="323.7872,-356 317.7872,-356 "/>
|
||||
<text text-anchor="middle" x="271" y="-349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">validateSystem is</text>
|
||||
<text text-anchor="middle" x="271" y="-337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">public because is</text>
|
||||
<text text-anchor="middle" x="271" y="-325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">used on Api.</text>
|
||||
</g>
|
||||
<!-- A3->A10 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>A3->A10</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M200.1992,-340C206.1915,-340 212.1837,-340 218.176,-340"/>
|
||||
</g>
|
||||
<!-- A4 -->
|
||||
<g id="node5" class="node">
|
||||
<title>A4</title>
|
||||
<polygon fill="none" stroke="#000000" points="264.1131,-812 189.8869,-812 189.8869,-776 264.1131,-776 264.1131,-812"/>
|
||||
<text text-anchor="middle" x="227" y="-791" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Command</text>
|
||||
</g>
|
||||
<!-- A5 -->
|
||||
<g id="node6" class="node">
|
||||
<title>A5</title>
|
||||
<polygon fill="none" stroke="#000000" points="148,-684 148,-716 307,-716 307,-684 148,-684"/>
|
||||
<text text-anchor="start" x="199.9955" y="-697" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImport</text>
|
||||
<polygon fill="none" stroke="#000000" points="148,-652 148,-684 307,-684 307,-652 148,-652"/>
|
||||
<text text-anchor="start" x="157.907" y="-665" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+boardImportCommandService</text>
|
||||
<polygon fill="none" stroke="#000000" points="148,-608 148,-652 307,-652 307,-608 148,-608"/>
|
||||
<text text-anchor="start" x="200.8305" y="-633" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#configure()</text>
|
||||
<text text-anchor="start" x="177.76" y="-621" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#execute(input,output)</text>
|
||||
</g>
|
||||
<!-- A4->A5 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>A4->A5</title>
|
||||
<path fill="none" stroke="#000000" d="M227,-765.6356C227,-751.1554 227,-733.0451 227,-716.0324"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="227,-775.9227 222.5001,-765.9227 227,-770.9227 227.0001,-765.9227 227.0001,-765.9227 227.0001,-765.9227 227,-770.9227 231.5001,-765.9228 227,-775.9227 227,-775.9227"/>
|
||||
</g>
|
||||
<!-- A6 -->
|
||||
<g id="node7" class="node">
|
||||
<title>A6</title>
|
||||
<polygon fill="none" stroke="#000000" points="150,-526 150,-558 304,-558 304,-526 150,-526"/>
|
||||
<text text-anchor="start" x="159.7715" y="-539" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">BoardImportCommandService</text>
|
||||
<polygon fill="none" stroke="#000000" points="150,-446 150,-526 304,-526 304,-446 150,-446"/>
|
||||
<text text-anchor="start" x="199.9105" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+bootstrap()</text>
|
||||
<text text-anchor="start" x="206.8645" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+import()</text>
|
||||
<text text-anchor="start" x="186.857" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+validateSystem()</text>
|
||||
<text text-anchor="start" x="189.218" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateConfig()</text>
|
||||
<text text-anchor="start" x="193.107" y="-459" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#validateData()</text>
|
||||
</g>
|
||||
<!-- A5->A6 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>A5->A6</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M227,-607.8313C227,-595.0442 227,-581.2707 227,-568.0248"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="227,-558.0234 231.5001,-568.0234 227,-563.0234 227.0001,-568.0234 227.0001,-568.0234 227.0001,-568.0234 227,-563.0234 222.5001,-568.0235 227,-558.0234 227,-558.0234"/>
|
||||
<text text-anchor="middle" x="218.5476" y="-586.7051" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A6->A3 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>A6->A3</title>
|
||||
<path fill="none" stroke="#000000" d="M198.8975,-445.7949C192.3634,-432.7268 185.3528,-418.7057 178.6417,-405.2834"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="174.0529,-396.1057 182.55,-403.0375 176.289,-400.5779 178.5251,-405.05 178.5251,-405.05 178.5251,-405.05 176.289,-400.5779 174.5001,-407.0625 174.0529,-396.1057 174.0529,-396.1057"/>
|
||||
</g>
|
||||
<!-- A7->A3 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>A7->A3</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M102.735,-228.0253C109.5347,-241.763 117.1224,-258.3431 124.0627,-274.4849"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="128.0634,-283.9135 120.0148,-276.4657 126.1104,-279.3107 124.1573,-274.7079 124.1573,-274.7079 124.1573,-274.7079 126.1104,-279.3107 128.2998,-272.9502 128.0634,-283.9135 128.0634,-283.9135"/>
|
||||
<text text-anchor="middle" x="118.307" y="-237.5757" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A8 -->
|
||||
<g id="node9" class="node">
|
||||
<title>A8</title>
|
||||
<polygon fill="none" stroke="#000000" points="80,-64 80,-108 213,-108 213,-64 80,-64"/>
|
||||
<text text-anchor="start" x="117.04" y="-89" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<abstract>></text>
|
||||
<text text-anchor="start" x="98.9935" y="-77" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ABoardImportService</text>
|
||||
<polygon fill="none" stroke="#000000" points="80,-32 80,-64 213,-64 213,-32 80,-32"/>
|
||||
<text text-anchor="start" x="92.036" y="-45" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">#needValidateData:false</text>
|
||||
<polygon fill="none" stroke="#000000" points="80,0 80,-32 213,-32 213,0 80,0"/>
|
||||
<text text-anchor="start" x="89.677" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">+needValidateData():bool</text>
|
||||
</g>
|
||||
<!-- A7->A8 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>A7->A8</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M97.2957,-163.778C103.3956,-150.029 110.7371,-133.4813 117.8485,-117.4527"/>
|
||||
<polygon fill="none" stroke="#000000" points="121.1416,-118.6605 121.9978,-108.1003 114.743,-115.8216 121.1416,-118.6605"/>
|
||||
<text text-anchor="middle" x="96.9205" y="-140.7815" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
|
||||
</g>
|
||||
<!-- A9->A3 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>A9->A3</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M198.9952,-234.3677C194.0646,-246.7117 188.0483,-260.7568 181.8434,-274.4849"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="177.5286,-283.9135 177.598,-272.9478 179.6093,-279.367 181.6899,-274.8204 181.6899,-274.8204 181.6899,-274.8204 179.6093,-279.367 185.7818,-276.693 177.5286,-283.9135 177.5286,-283.9135"/>
|
||||
<text text-anchor="middle" x="200.0654" y="-251.3391" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">uses</text>
|
||||
</g>
|
||||
<!-- A9->A8 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A9->A8</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M192.8492,-157.9466C187.2535,-145.5313 180.8796,-131.389 174.6742,-117.6209"/>
|
||||
<polygon fill="none" stroke="#000000" points="177.7167,-115.8534 170.4168,-108.1747 171.3349,-118.7297 177.7167,-115.8534"/>
|
||||
<text text-anchor="middle" x="177.6953" y="-141.8944" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">implements</text>
|
||||
</g>
|
||||
<!-- A11 -->
|
||||
<g id="node12" class="node">
|
||||
<title>A11</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="403.024,-224 290.976,-224 290.976,-168 409.024,-168 409.024,-218 403.024,-224"/>
|
||||
<polyline fill="none" stroke="#000000" points="403.024,-224 403.024,-218 "/>
|
||||
<polyline fill="none" stroke="#000000" points="409.024,-218 403.024,-218 "/>
|
||||
<text text-anchor="middle" x="350" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">To create an import</text>
|
||||
<text text-anchor="middle" x="350" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to another system,</text>
|
||||
<text text-anchor="middle" x="350" y="-187" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create another class</text>
|
||||
<text text-anchor="middle" x="350" y="-175" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">similar to this.</text>
|
||||
</g>
|
||||
<!-- A9->A11 -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>A9->A11</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M272.6172,-196C278.6627,-196 284.7083,-196 290.7538,-196"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
24
docs/resources/BoardImport.yuml
Normal file
24
docs/resources/BoardImport.yuml
Normal file
@@ -0,0 +1,24 @@
|
||||
// Created using [yUML](https://github.com/jaime-olivares/vscode-yuml)
|
||||
|
||||
// {type:class}
|
||||
// {direction:topDown}
|
||||
// {generate:true}
|
||||
|
||||
[note: Classes used on board import. Methods just to illustrate. {bg:cornsilk}]
|
||||
|
||||
[ApiController]<-[BoardImportApiController|+import();+getAllowedSystems();+getConfigSchema()]
|
||||
[BoardImportApiController]uses-.->[BoardImportService|+import();+bootstrap();+validateSystem();#validateConfig();#validateData();]
|
||||
|
||||
[Command]<-[BoardImport|+boardImportCommandService|#configure();#execute(input,output)]
|
||||
[BoardImport]uses-.->[BoardImportCommandService|+bootstrap();+import();+validateSystem();#validateConfig();#validateData()]
|
||||
[BoardImportCommandService]->[BoardImportService]
|
||||
|
||||
[BoardImportService]uses-.->[TrelloApiService|+name:string]
|
||||
[TrelloApiService]uses-.->[BoardImportService]
|
||||
[TrelloApiService]implements-.-^[<<abstract>> ABoardImportService|#needValidateData:false|+needValidateData():bool]
|
||||
|
||||
[BoardImportService]uses-.->[TrelloJsonService|+name:string;#needValidateData:true]
|
||||
[TrelloJsonService]uses-.->[BoardImportService]
|
||||
[BoardImportService]-[note: validateSystem is public because is used on Api. {bg:cornsilk}]
|
||||
[TrelloJsonService]-[note: To create an import to another system, create another class similar to this. {bg:cornsilk}]
|
||||
[TrelloJsonService]implements-.-^[<<abstract>> ABoardImportService]
|
||||
@@ -35,7 +35,6 @@ use OCP\IConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCA\Deck\Service\CardService;
|
||||
|
||||
class DeckProvider implements IProvider {
|
||||
|
||||
@@ -53,10 +52,8 @@ class DeckProvider implements IProvider {
|
||||
private $l10nFactory;
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
/** @var CardService */
|
||||
private $cardService;
|
||||
|
||||
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId, CardService $cardService) {
|
||||
public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId) {
|
||||
$this->userId = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->activityManager = $activityManager;
|
||||
@@ -64,7 +61,6 @@ class DeckProvider implements IProvider {
|
||||
$this->userManager = $userManager;
|
||||
$this->l10nFactory = $l10n;
|
||||
$this->config = $config;
|
||||
$this->cardService = $cardService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +131,7 @@ class DeckProvider implements IProvider {
|
||||
|
||||
if (array_key_exists('board', $subjectParams)) {
|
||||
$archivedParam = $subjectParams['card']['archived'] ? 'archived/' : '';
|
||||
$card['link'] = $this->cardService->getRedirectUrlForCard($event->getObjectId());
|
||||
$card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . 'card/' . $event->getObjectId());
|
||||
}
|
||||
$params['card'] = $card;
|
||||
}
|
||||
|
||||
92
lib/Command/BoardImport.php
Normal file
92
lib/Command/BoardImport.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Command;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportCommandService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class BoardImport extends Command {
|
||||
/** @var BoardImportCommandService */
|
||||
private $boardImportCommandService;
|
||||
|
||||
public function __construct(
|
||||
BoardImportCommandService $boardImportCommandService
|
||||
) {
|
||||
$this->boardImportCommandService = $boardImportCommandService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure() {
|
||||
$allowedSystems = $this->boardImportCommandService->getAllowedImportSystems();
|
||||
$names = array_column($allowedSystems, 'name');
|
||||
$this
|
||||
->setName('deck:import')
|
||||
->setDescription('Import data')
|
||||
->addOption(
|
||||
'system',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Source system for import. Available options: ' . implode(', ', $names) . '.',
|
||||
null
|
||||
)
|
||||
->addOption(
|
||||
'config',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Configuration json file.',
|
||||
'config.json'
|
||||
)
|
||||
->addOption(
|
||||
'data',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Data file to import.',
|
||||
'data.json'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$this
|
||||
->boardImportCommandService
|
||||
->setInput($input)
|
||||
->setOutput($output)
|
||||
->setCommand($this)
|
||||
->import();
|
||||
$output->writeln('Done!');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
85
lib/Controller/BoardImportApiController.php
Normal file
85
lib/Controller/BoardImportApiController.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
|
||||
class BoardImportApiController extends OCSController {
|
||||
/** @var BoardImportService */
|
||||
private $boardImportService;
|
||||
/** @var string */
|
||||
private $userId;
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
BoardImportService $boardImportService,
|
||||
string $userId
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->boardImportService = $boardImportService;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function import(string $system, array $config, array $data): DataResponse {
|
||||
$this->boardImportService->setSystem($system);
|
||||
$config = json_decode(json_encode($config));
|
||||
$config->owner = $this->userId;
|
||||
$this->boardImportService->setConfigInstance($config);
|
||||
$this->boardImportService->setData(json_decode(json_encode($data)));
|
||||
$this->boardImportService->import();
|
||||
return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getAllowedSystems(): DataResponse {
|
||||
$allowedSystems = $this->boardImportService->getAllowedImportSystems();
|
||||
return new DataResponse($allowedSystems, Http::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getConfigSchema(string $name): DataResponse {
|
||||
$this->boardImportService->setSystem($name);
|
||||
$this->boardImportService->validateSystem();
|
||||
$jsonSchemaPath = json_decode(file_get_contents($this->boardImportService->getJsonSchemaPath()));
|
||||
return new DataResponse($jsonSchemaPath, Http::STATUS_OK);
|
||||
}
|
||||
}
|
||||
@@ -34,20 +34,12 @@ use OCP\IInitialStateService;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCP\IURLGenerator;
|
||||
use \OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Service\CardService;
|
||||
|
||||
class PageController extends Controller {
|
||||
private $permissionService;
|
||||
private $initialState;
|
||||
private $configService;
|
||||
private $eventDispatcher;
|
||||
private $cardMapper;
|
||||
private $urlGenerator;
|
||||
private $cardService;
|
||||
|
||||
public function __construct(
|
||||
$AppName,
|
||||
@@ -55,10 +47,7 @@ class PageController extends Controller {
|
||||
PermissionService $permissionService,
|
||||
IInitialStateService $initialStateService,
|
||||
ConfigService $configService,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
CardMapper $cardMapper,
|
||||
IURLGenerator $urlGenerator,
|
||||
CardService $cardService
|
||||
IEventDispatcher $eventDispatcher
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
|
||||
@@ -66,9 +55,6 @@ class PageController extends Controller {
|
||||
$this->initialState = $initialStateService;
|
||||
$this->configService = $configService;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->cardService = $cardService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,17 +85,4 @@ class PageController extends Controller {
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function redirectToCard($cardId): RedirectResponse {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
return new RedirectResponse($this->cardService->getCardUrl($cardId));
|
||||
} catch (\Exception $e) {
|
||||
return new RedirectResponse($this->urlGenerator->linkToRouteAbsolute('deck.page.index'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCP\BackgroundJob\Job;
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
|
||||
|
||||
@@ -24,13 +24,14 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
use OCP\BackgroundJob\IJob;
|
||||
|
||||
class DeleteCron extends Job {
|
||||
class DeleteCron extends TimedJob {
|
||||
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
@@ -43,6 +44,9 @@ class DeleteCron extends Job {
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
|
||||
$this->setInterval(60 * 60 * 24);
|
||||
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCP\BackgroundJob\Job;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
|
||||
@@ -25,23 +25,46 @@ namespace OCA\Deck\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
class AclMapper extends QBMapper implements IPermissionMapper {
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'deck_board_acl', Acl::class);
|
||||
}
|
||||
|
||||
public function find($id): Acl {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
|
||||
|
||||
return $this->findEntity($query);
|
||||
}
|
||||
|
||||
public function findAll($boardId, $limit = null, $offset = null) {
|
||||
$sql = 'SELECT id, board_id, type, participant, permission_edit, permission_share, permission_manage FROM `*PREFIX*deck_board_acl` WHERE `board_id` = ? ';
|
||||
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($query->expr()->eq('board_id', $query->createNamedParameter($boardId)))
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
return $this->findEntities($query);
|
||||
}
|
||||
|
||||
public function isOwner($userId, $aclId): bool {
|
||||
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_board_acl` WHERE id = ?)';
|
||||
$stmt = $this->execute($sql, [$aclId]);
|
||||
$row = $stmt->fetch();
|
||||
return ($row['owner'] === $userId);
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('owner')
|
||||
->from('deck_boards', 'b')
|
||||
->innerJoin('b', $this->getTableName(), 'a', $query->expr()->eq('b.id', 'a.board_id'))
|
||||
->where($query->expr()->eq('a.id', $query->createNamedParameter($aclId)));
|
||||
|
||||
$cursor = $query->execute();
|
||||
$row = $cursor->fetch();
|
||||
$cursor->closeCursor();
|
||||
|
||||
return is_array($row) && $row['owner'] === $userId;
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
@@ -53,8 +76,13 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function findByParticipant($type, $participant): array {
|
||||
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
|
||||
return $this->findEntities($sql, [$type, $participant]);
|
||||
public function findByParticipant(int $type, string $participant): array {
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($query->expr()->eq('type', $query->createNamedParameter($type)))
|
||||
->andWhere($query->expr()->eq('type', $query->createNamedParameter($participant)));
|
||||
|
||||
return $this->findEntities($query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
public function findOverdue() {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id','title','duedate','notified')
|
||||
$qb->select('id', 'title', 'duedate', 'notified')
|
||||
->from('deck_cards')
|
||||
->where($qb->expr()->lt('duedate', $qb->createFunction('NOW()')))
|
||||
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
@@ -276,7 +276,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
public function findUnexposedDescriptionChances() {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id','title','duedate','notified','description_prev','last_editor','description')
|
||||
$qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description')
|
||||
->from('deck_cards')
|
||||
->where($qb->expr()->isNotNull('last_editor'))
|
||||
->andWhere($qb->expr()->isNotNull('description_prev'));
|
||||
|
||||
44
lib/Event/ABoardImportGetAllowedEvent.php
Normal file
44
lib/Event/ABoardImportGetAllowedEvent.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCA\Deck\Event;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCP\EventDispatcher\Event;
|
||||
|
||||
abstract class ABoardImportGetAllowedEvent extends Event {
|
||||
private $service;
|
||||
|
||||
public function __construct(BoardImportService $service) {
|
||||
parent::__construct();
|
||||
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function getService(): BoardImportService {
|
||||
return $this->service;
|
||||
}
|
||||
}
|
||||
29
lib/Event/BoardImportGetAllowedEvent.php
Normal file
29
lib/Event/BoardImportGetAllowedEvent.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Deck\Event;
|
||||
|
||||
class BoardImportGetAllowedEvent extends ABoardImportGetAllowedEvent {
|
||||
}
|
||||
@@ -33,6 +33,6 @@ use OCP\Search\SearchResultEntry;
|
||||
|
||||
class CardSearchResultEntry extends SearchResultEntry {
|
||||
public function __construct(Board $board, Stack $stack, Card $card, $urlGenerator) {
|
||||
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle() , $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
|
||||
parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle(), $urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCP\IUserManager;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class BoardService {
|
||||
private $boardMapper;
|
||||
@@ -69,8 +68,8 @@ class BoardService {
|
||||
private $activityManager;
|
||||
private $eventDispatcher;
|
||||
private $changeHelper;
|
||||
|
||||
private $boardsCache = null;
|
||||
private $urlGenerator;
|
||||
|
||||
|
||||
public function __construct(
|
||||
@@ -88,7 +87,6 @@ class BoardService {
|
||||
ActivityManager $activityManager,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
ChangeHelper $changeHelper,
|
||||
IURLGenerator $urlGenerator,
|
||||
$userId
|
||||
) {
|
||||
$this->boardMapper = $boardMapper;
|
||||
@@ -106,7 +104,6 @@ class BoardService {
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->changeHelper = $changeHelper;
|
||||
$this->userId = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -700,8 +697,4 @@ class BoardService {
|
||||
}
|
||||
$board->setUsers(array_values($boardUsers));
|
||||
}
|
||||
|
||||
public function getBoardUrl($endpoint) {
|
||||
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#' . $endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ use OCA\Deck\BadRequestException;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class CardService {
|
||||
private $cardMapper;
|
||||
@@ -64,7 +63,6 @@ class CardService {
|
||||
private $changeHelper;
|
||||
private $eventDispatcher;
|
||||
private $userManager;
|
||||
private $urlGenerator;
|
||||
|
||||
public function __construct(
|
||||
CardMapper $cardMapper,
|
||||
@@ -81,7 +79,6 @@ class CardService {
|
||||
IUserManager $userManager,
|
||||
ChangeHelper $changeHelper,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
IURLGenerator $urlGenerator,
|
||||
$userId
|
||||
) {
|
||||
$this->cardMapper = $cardMapper;
|
||||
@@ -99,7 +96,6 @@ class CardService {
|
||||
$this->changeHelper = $changeHelper;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->currentUser = $userId;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function enrich($card) {
|
||||
@@ -606,14 +602,4 @@ class CardService {
|
||||
|
||||
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
|
||||
}
|
||||
|
||||
public function getCardUrl($cardId) {
|
||||
$boardId = $this->cardMapper->findBoardId($cardId);
|
||||
|
||||
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "#/board/$boardId/card/$cardId";
|
||||
}
|
||||
|
||||
public function getRedirectUrlForCard($cardId) {
|
||||
return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . "card/$cardId";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ class ConfigService {
|
||||
}
|
||||
|
||||
$data = [
|
||||
'calendar' => $this->isCalendarEnabled()
|
||||
'calendar' => $this->isCalendarEnabled(),
|
||||
'cardDetailsInModal' => $this->isCardDetailsInModal(),
|
||||
];
|
||||
if ($this->groupManager->isAdmin($this->getUserId())) {
|
||||
$data['groupLimit'] = $this->get('groupLimit');
|
||||
@@ -88,6 +89,11 @@ class ConfigService {
|
||||
return false;
|
||||
}
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
||||
case 'cardDetailsInModal':
|
||||
if ($this->getUserId() === null) {
|
||||
return false;
|
||||
}
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +102,8 @@ class ConfigService {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', true);
|
||||
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'calendar', 'yes') === 'yes';
|
||||
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', $appConfigState);
|
||||
if ($boardId === null) {
|
||||
return $defaultState;
|
||||
}
|
||||
@@ -104,6 +111,19 @@ class ConfigService {
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
|
||||
}
|
||||
|
||||
public function isCardDetailsInModal(int $boardId = null): bool {
|
||||
if ($this->getUserId() === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true);
|
||||
if ($boardId === null) {
|
||||
return $defaultState;
|
||||
}
|
||||
|
||||
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
|
||||
}
|
||||
|
||||
public function set($key, $value) {
|
||||
if ($this->getUserId() === null) {
|
||||
throw new NoPermissionException('Must be logged in to set user config');
|
||||
@@ -122,6 +142,10 @@ class ConfigService {
|
||||
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value);
|
||||
$result = $value;
|
||||
break;
|
||||
case 'cardDetailsInModal':
|
||||
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', (string)$value);
|
||||
$result = $value;
|
||||
break;
|
||||
case 'board':
|
||||
[$boardId, $boardConfigKey] = explode(':', $key);
|
||||
if ($boardConfigKey === 'notify-due' && !in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) {
|
||||
|
||||
@@ -86,7 +86,7 @@ class FileService implements IAttachmentService {
|
||||
* @return ISimpleFolder
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
private function getFolder(Attachment $attachment) {
|
||||
public function getFolder(Attachment $attachment) {
|
||||
$folderName = 'file-card-' . (int)$attachment->getCardId();
|
||||
try {
|
||||
$folder = $this->appData->getFolder($folderName);
|
||||
|
||||
136
lib/Service/Importer/ABoardImportService.php
Normal file
136
lib/Service/Importer/ABoardImportService.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\Comments\IComment;
|
||||
|
||||
abstract class ABoardImportService {
|
||||
/** @var string */
|
||||
public static $name = '';
|
||||
/** @var BoardImportService */
|
||||
private $boardImportService;
|
||||
/** @var bool */
|
||||
protected $needValidateData = true;
|
||||
/** @var Stack[] */
|
||||
protected $stacks = [];
|
||||
/** @var Label[] */
|
||||
protected $labels = [];
|
||||
/** @var Card[] */
|
||||
protected $cards = [];
|
||||
/** @var Acl[] */
|
||||
protected $acls = [];
|
||||
/** @var IComment[][] */
|
||||
protected $comments = [];
|
||||
/** @var Assignment[] */
|
||||
protected $assignments = [];
|
||||
/** @var string[][] */
|
||||
protected $labelCardAssignments = [];
|
||||
|
||||
/**
|
||||
* Configure import service
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function bootstrap(): void;
|
||||
|
||||
abstract public function getBoard(): ?Board;
|
||||
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
abstract public function getAclList(): array;
|
||||
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
abstract public function getStacks(): array;
|
||||
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
abstract public function getCards(): array;
|
||||
|
||||
abstract public function getCardAssignments(): array;
|
||||
|
||||
abstract public function getCardLabelAssignment(): array;
|
||||
|
||||
/**
|
||||
* @return IComment[][]|array
|
||||
*/
|
||||
abstract public function getComments(): array;
|
||||
|
||||
/** @return Label[] */
|
||||
abstract public function getLabels(): array;
|
||||
|
||||
abstract public function validateUsers(): void;
|
||||
|
||||
abstract public function getJsonSchemaPath(): string;
|
||||
|
||||
public function updateStack(string $id, Stack $stack): void {
|
||||
$this->stacks[$id] = $stack;
|
||||
}
|
||||
|
||||
public function updateCard(string $id, Card $card): void {
|
||||
$this->cards[$id] = $card;
|
||||
}
|
||||
|
||||
public function updateLabel(string $code, Label $label): void {
|
||||
$this->labels[$code] = $label;
|
||||
}
|
||||
|
||||
public function updateAcl(string $code, Acl $acl): void {
|
||||
$this->acls[$code] = $acl;
|
||||
}
|
||||
|
||||
public function updateComment(string $cardId, string $commentId, IComment $comment): void {
|
||||
$this->comments[$cardId][$commentId] = $comment;
|
||||
}
|
||||
|
||||
public function updateCardAssignment(string $cardId, string $assignmentId, Entity $assignment): void {
|
||||
$this->assignments[$cardId][$assignmentId] = $assignment;
|
||||
}
|
||||
|
||||
public function updateCardLabelsAssignment(string $cardId, string $assignmentId, string $assignment): void {
|
||||
$this->labelCardAssignments[$cardId][$assignmentId] = $assignment;
|
||||
}
|
||||
|
||||
public function setImportService(BoardImportService $service): void {
|
||||
$this->boardImportService = $service;
|
||||
}
|
||||
|
||||
public function getImportService(): BoardImportService {
|
||||
return $this->boardImportService;
|
||||
}
|
||||
|
||||
public function needValidateData(): bool {
|
||||
return $this->needValidateData;
|
||||
}
|
||||
}
|
||||
199
lib/Service/Importer/BoardImportCommandService.php
Normal file
199
lib/Service/Importer/BoardImportCommandService.php
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use OCA\Deck\Exceptions\ConflictException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
class BoardImportCommandService extends BoardImportService {
|
||||
/**
|
||||
* @var Command
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $command;
|
||||
/**
|
||||
* @var InputInterface
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $input;
|
||||
/**
|
||||
* @var OutputInterface
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $output;
|
||||
|
||||
public function setCommand(Command $command): self {
|
||||
$this->command = $command;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommand(): Command {
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function setInput(InputInterface $input): self {
|
||||
$this->input = $input;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInput(): InputInterface {
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
public function setOutput(OutputInterface $output): self {
|
||||
$this->output = $output;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOutput(): OutputInterface {
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
protected function validateConfig(): void {
|
||||
try {
|
||||
$config = $this->getInput()->getOption('config');
|
||||
if (is_string($config)) {
|
||||
if (!is_file($config)) {
|
||||
throw new NotFoundException('It\'s not a valid config file.');
|
||||
}
|
||||
$config = json_decode(file_get_contents($config));
|
||||
if (!$config instanceof \stdClass) {
|
||||
throw new NotFoundException('Failed to parse JSON.');
|
||||
}
|
||||
$this->setConfigInstance($config);
|
||||
}
|
||||
parent::validateConfig();
|
||||
return;
|
||||
} catch (NotFoundException $e) {
|
||||
$this->getOutput()->writeln('<error>' . $e->getMessage() . '</error>');
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new Question(
|
||||
"<info>You can get more info on https://deck.readthedocs.io/en/latest/User_documentation_en/#6-import-boards</info>\n" .
|
||||
'Please inform a valid config json file: ',
|
||||
'config.json'
|
||||
);
|
||||
$question->setValidator(function (string $answer) {
|
||||
if (!is_file($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'config file not found'
|
||||
);
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$configFile = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('config', $configFile);
|
||||
} catch (ConflictException $e) {
|
||||
$this->getOutput()->writeln('<error>Invalid config file</error>');
|
||||
$this->getOutput()->writeln(array_map(function (array $v): string {
|
||||
return $v['message'];
|
||||
}, $e->getData()));
|
||||
$this->getOutput()->writeln('Valid schema:');
|
||||
$this->getOutput()->writeln(print_r(file_get_contents($this->getJsonSchemaPath()), true));
|
||||
$this->getInput()->setOption('config', '');
|
||||
}
|
||||
$this->validateConfig();
|
||||
}
|
||||
|
||||
public function validateSystem(): void {
|
||||
try {
|
||||
parent::validateSystem();
|
||||
return;
|
||||
} catch (\Throwable $th) {
|
||||
}
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$allowedSystems = $this->getAllowedImportSystems();
|
||||
$names = array_column($allowedSystems, 'name');
|
||||
$question = new ChoiceQuestion(
|
||||
'Please inform a source system',
|
||||
$names,
|
||||
0
|
||||
);
|
||||
$question->setErrorMessage('System %s is invalid.');
|
||||
$selectedName = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$className = $allowedSystems[array_flip($names)[$selectedName]]['internalName'];
|
||||
$this->setSystem($className);
|
||||
return;
|
||||
}
|
||||
|
||||
protected function validateData(): void {
|
||||
if (!$this->getImportSystem()->needValidateData()) {
|
||||
return;
|
||||
}
|
||||
$data = $this->getInput()->getOption('data');
|
||||
if (is_string($data)) {
|
||||
$data = json_decode(file_get_contents($data));
|
||||
if ($data instanceof \stdClass) {
|
||||
$this->setData($data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$helper = $this->getCommand()->getHelper('question');
|
||||
$question = new Question(
|
||||
'Please provide a valid data json file: ',
|
||||
'data.json'
|
||||
);
|
||||
$question->setValidator(function (string $answer) {
|
||||
if (!is_file($answer)) {
|
||||
throw new \RuntimeException(
|
||||
'Data file not found'
|
||||
);
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
$data = $helper->ask($this->getInput(), $this->getOutput(), $question);
|
||||
$this->getInput()->setOption('data', $data);
|
||||
$this->validateData();
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->setSystem($this->getInput()->getOption('system'));
|
||||
parent::bootstrap();
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->getOutput()->writeln('Starting import...');
|
||||
$this->bootstrap();
|
||||
$this->getOutput()->writeln('Importing board...');
|
||||
$this->importBoard();
|
||||
$this->getOutput()->writeln('Assign users to board...');
|
||||
$this->importAcl();
|
||||
$this->getOutput()->writeln('Importing labels...');
|
||||
$this->importLabels();
|
||||
$this->getOutput()->writeln('Importing stacks...');
|
||||
$this->importStacks();
|
||||
$this->getOutput()->writeln('Importing cards...');
|
||||
$this->importCards();
|
||||
$this->getOutput()->writeln('Assign cards to labels...');
|
||||
$this->assignCardsToLabels();
|
||||
$this->getOutput()->writeln('Importing comments...');
|
||||
$this->importComments();
|
||||
$this->getOutput()->writeln('Importing participants...');
|
||||
$this->importCardAssignments();
|
||||
}
|
||||
}
|
||||
449
lib/Service/Importer/BoardImportService.php
Normal file
449
lib/Service/Importer/BoardImportService.php
Normal file
@@ -0,0 +1,449 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use JsonSchema\Constraints\Constraint;
|
||||
use JsonSchema\Validator;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Event\BoardImportGetAllowedEvent;
|
||||
use OCA\Deck\Exceptions\ConflictException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\Service\FileService;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloApiService;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\Comments\NotFoundException as CommentNotFoundException;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class BoardImportService {
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
/** @var AclMapper */
|
||||
private $aclMapper;
|
||||
/** @var LabelMapper */
|
||||
private $labelMapper;
|
||||
/** @var StackMapper */
|
||||
private $stackMapper;
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
/** @var AssignmentMapper */
|
||||
private $assignmentMapper;
|
||||
/** @var AttachmentMapper */
|
||||
private $attachmentMapper;
|
||||
/** @var ICommentsManager */
|
||||
private $commentsManager;
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
/** @var string */
|
||||
private $system = '';
|
||||
/** @var null|ABoardImportService */
|
||||
private $systemInstance;
|
||||
/** @var array */
|
||||
private $allowedSystems = [];
|
||||
/**
|
||||
* Data object created from config JSON
|
||||
*
|
||||
* @var \stdClass
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
public $config;
|
||||
/**
|
||||
* Data object created from JSON of origin system
|
||||
*
|
||||
* @var \stdClass
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
*/
|
||||
private $data;
|
||||
/**
|
||||
* @var Board
|
||||
*/
|
||||
private $board;
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
BoardMapper $boardMapper,
|
||||
AclMapper $aclMapper,
|
||||
LabelMapper $labelMapper,
|
||||
StackMapper $stackMapper,
|
||||
AssignmentMapper $assignmentMapper,
|
||||
AttachmentMapper $attachmentMapper,
|
||||
CardMapper $cardMapper,
|
||||
ICommentsManager $commentsManager,
|
||||
IEventDispatcher $eventDispatcher
|
||||
) {
|
||||
$this->userManager = $userManager;
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->aclMapper = $aclMapper;
|
||||
$this->labelMapper = $labelMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->assignmentMapper = $assignmentMapper;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->commentsManager = $commentsManager;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->board = new Board();
|
||||
$this->disableCommentsEvents();
|
||||
}
|
||||
|
||||
private function disableCommentsEvents(): void {
|
||||
if (defined('PHPUNIT_RUN')) {
|
||||
return;
|
||||
}
|
||||
$propertyEventHandlers = new \ReflectionProperty($this->commentsManager, 'eventHandlers');
|
||||
$propertyEventHandlers->setAccessible(true);
|
||||
$propertyEventHandlers->setValue($this->commentsManager, []);
|
||||
|
||||
$propertyEventHandlerClosures = new \ReflectionProperty($this->commentsManager, 'eventHandlerClosures');
|
||||
$propertyEventHandlerClosures->setAccessible(true);
|
||||
$propertyEventHandlerClosures->setValue($this->commentsManager, []);
|
||||
}
|
||||
|
||||
public function import(): void {
|
||||
$this->bootstrap();
|
||||
try {
|
||||
$this->importBoard();
|
||||
$this->importAcl();
|
||||
$this->importLabels();
|
||||
$this->importStacks();
|
||||
$this->importCards();
|
||||
$this->assignCardsToLabels();
|
||||
$this->importComments();
|
||||
$this->importCardAssignments();
|
||||
} catch (\Throwable $th) {
|
||||
throw new BadRequestException($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function validateSystem(): void {
|
||||
$allowedSystems = $this->getAllowedImportSystems();
|
||||
$allowedSystems = array_column($allowedSystems, 'internalName');
|
||||
if (!in_array($this->getSystem(), $allowedSystems)) {
|
||||
throw new NotFoundException('Invalid system');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $system
|
||||
* @return self
|
||||
*/
|
||||
public function setSystem($system): self {
|
||||
$this->system = $system;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSystem(): string {
|
||||
return $this->system;
|
||||
}
|
||||
|
||||
public function addAllowedImportSystem($system): self {
|
||||
$this->allowedSystems[] = $system;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllowedImportSystems(): array {
|
||||
if (!$this->allowedSystems) {
|
||||
$this->addAllowedImportSystem([
|
||||
'name' => TrelloApiService::$name,
|
||||
'class' => TrelloApiService::class,
|
||||
'internalName' => 'TrelloApi'
|
||||
]);
|
||||
$this->addAllowedImportSystem([
|
||||
'name' => TrelloJsonService::$name,
|
||||
'class' => TrelloJsonService::class,
|
||||
'internalName' => 'TrelloJson'
|
||||
]);
|
||||
}
|
||||
$this->eventDispatcher->dispatchTyped(new BoardImportGetAllowedEvent($this));
|
||||
return $this->allowedSystems;
|
||||
}
|
||||
|
||||
public function getImportSystem(): ABoardImportService {
|
||||
if (!$this->getSystem()) {
|
||||
throw new NotFoundException('System to import not found');
|
||||
}
|
||||
if (!is_object($this->systemInstance)) {
|
||||
$systemClass = 'OCA\\Deck\\Service\\Importer\\Systems\\' . ucfirst($this->getSystem()) . 'Service';
|
||||
$this->systemInstance = \OC::$server->get($systemClass);
|
||||
$this->systemInstance->setImportService($this);
|
||||
}
|
||||
return $this->systemInstance;
|
||||
}
|
||||
|
||||
public function setImportSystem(ABoardImportService $instance): void {
|
||||
$this->systemInstance = $instance;
|
||||
}
|
||||
|
||||
public function importBoard(): void {
|
||||
$board = $this->getImportSystem()->getBoard();
|
||||
if ($board) {
|
||||
$this->boardMapper->insert($board);
|
||||
$this->board = $board;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBoard(bool $reset = false): Board {
|
||||
if ($reset) {
|
||||
$this->board = new Board();
|
||||
}
|
||||
return $this->board;
|
||||
}
|
||||
|
||||
public function importAcl(): void {
|
||||
$aclList = $this->getImportSystem()->getAclList();
|
||||
foreach ($aclList as $code => $acl) {
|
||||
$this->aclMapper->insert($acl);
|
||||
$this->getImportSystem()->updateAcl($code, $acl);
|
||||
}
|
||||
$this->getBoard()->setAcl($aclList);
|
||||
}
|
||||
|
||||
public function importLabels(): void {
|
||||
$labels = $this->getImportSystem()->getLabels();
|
||||
foreach ($labels as $code => $label) {
|
||||
$this->labelMapper->insert($label);
|
||||
$this->getImportSystem()->updateLabel($code, $label);
|
||||
}
|
||||
$this->getBoard()->setLabels($labels);
|
||||
}
|
||||
|
||||
public function importStacks(): void {
|
||||
$stacks = $this->getImportSystem()->getStacks();
|
||||
foreach ($stacks as $code => $stack) {
|
||||
$this->stackMapper->insert($stack);
|
||||
$this->getImportSystem()->updateStack($code, $stack);
|
||||
}
|
||||
$this->getBoard()->setStacks(array_values($stacks));
|
||||
}
|
||||
|
||||
public function importCards(): void {
|
||||
$cards = $this->getImportSystem()->getCards();
|
||||
foreach ($cards as $code => $card) {
|
||||
$createdAt = $card->getCreatedAt();
|
||||
$lastModified = $card->getLastModified();
|
||||
$this->cardMapper->insert($card);
|
||||
$updateDate = false;
|
||||
if ($createdAt && $createdAt !== $card->getCreatedAt()) {
|
||||
$card->setCreatedAt($createdAt);
|
||||
$updateDate = true;
|
||||
}
|
||||
if ($lastModified && $lastModified !== $card->getLastModified()) {
|
||||
$card->setLastModified($lastModified);
|
||||
$updateDate = true;
|
||||
}
|
||||
if ($updateDate) {
|
||||
$this->cardMapper->update($card, false);
|
||||
}
|
||||
$this->getImportSystem()->updateCard($code, $card);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $cardId
|
||||
* @param mixed $labelId
|
||||
* @return self
|
||||
*/
|
||||
public function assignCardToLabel($cardId, $labelId): self {
|
||||
$this->cardMapper->assignLabel(
|
||||
$cardId,
|
||||
$labelId
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assignCardsToLabels(): void {
|
||||
$data = $this->getImportSystem()->getCardLabelAssignment();
|
||||
foreach ($data as $cardId => $assignemnt) {
|
||||
foreach ($assignemnt as $assignmentId => $labelId) {
|
||||
$this->assignCardToLabel(
|
||||
$cardId,
|
||||
$labelId
|
||||
);
|
||||
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function importComments(): void {
|
||||
$allComments = $this->getImportSystem()->getComments();
|
||||
foreach ($allComments as $cardId => $comments) {
|
||||
foreach ($comments as $commentId => $comment) {
|
||||
$this->insertComment($cardId, $comment);
|
||||
$this->getImportSystem()->updateComment($cardId, $commentId, $comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function insertComment(string $cardId, IComment $comment): void {
|
||||
$comment->setObject('deckCard', $cardId);
|
||||
$comment->setVerb('comment');
|
||||
// Check if parent is a comment on the same card
|
||||
if ($comment->getParentId() !== '0') {
|
||||
try {
|
||||
$parent = $this->commentsManager->get($comment->getParentId());
|
||||
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $parent->getObjectId() !== $cardId) {
|
||||
throw new CommentNotFoundException();
|
||||
}
|
||||
} catch (CommentNotFoundException $e) {
|
||||
throw new BadRequestException('Invalid parent id: The parent comment was not found or belongs to a different card');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->commentsManager->save($comment);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new BadRequestException('Invalid input values');
|
||||
} catch (CommentNotFoundException $e) {
|
||||
throw new NotFoundException('Could not create comment.');
|
||||
}
|
||||
}
|
||||
|
||||
public function importCardAssignments(): void {
|
||||
$allAssignments = $this->getImportSystem()->getCardAssignments();
|
||||
foreach ($allAssignments as $cardId => $assignments) {
|
||||
foreach ($assignments as $assignmentId => $assignment) {
|
||||
$this->assignmentMapper->insert($assignment);
|
||||
$this->getImportSystem()->updateCardAssignment($cardId, $assignmentId, $assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function insertAttachment(Attachment $attachment, string $content): Attachment {
|
||||
$service = \OC::$server->get(FileService::class);
|
||||
$folder = $service->getFolder($attachment);
|
||||
|
||||
if ($folder->fileExists($attachment->getData())) {
|
||||
$attachment = $this->attachmentMapper->findByData($attachment->getCardId(), $attachment->getData());
|
||||
throw new ConflictException('File already exists.', $attachment);
|
||||
}
|
||||
|
||||
$target = $folder->newFile($attachment->getData());
|
||||
$target->putContent($content);
|
||||
|
||||
$attachment = $this->attachmentMapper->insert($attachment);
|
||||
|
||||
$service->extendData($attachment);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function setData(\stdClass $data): void {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData(): \stdClass {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a config
|
||||
*
|
||||
* @param string $configName
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function setConfig(string $configName, $value): void {
|
||||
if (empty((array) $this->config)) {
|
||||
$this->setConfigInstance(new \stdClass);
|
||||
}
|
||||
$this->config->$configName = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a config
|
||||
*
|
||||
* @param string $configName config name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $configName) {
|
||||
if (!property_exists($this->config, $configName)) {
|
||||
return;
|
||||
}
|
||||
return $this->config->$configName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $config
|
||||
* @return self
|
||||
*/
|
||||
public function setConfigInstance($config): self {
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConfigInstance(): \stdClass {
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function validateConfig(): void {
|
||||
$config = $this->getConfigInstance();
|
||||
$schemaPath = $this->getJsonSchemaPath();
|
||||
$validator = new Validator();
|
||||
$newConfig = clone $config;
|
||||
$validator->validate(
|
||||
$newConfig,
|
||||
(object)['$ref' => 'file://' . realpath($schemaPath)],
|
||||
Constraint::CHECK_MODE_APPLY_DEFAULTS
|
||||
);
|
||||
if (!$validator->isValid()) {
|
||||
throw new ConflictException('Invalid config file', $validator->getErrors());
|
||||
}
|
||||
$this->setConfigInstance($newConfig);
|
||||
$this->validateOwner();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return $this->getImportSystem()->getJsonSchemaPath();
|
||||
}
|
||||
|
||||
public function validateOwner(): void {
|
||||
$owner = $this->userManager->get($this->getConfig('owner'));
|
||||
if (!$owner) {
|
||||
throw new \LogicException('Owner "' . $this->getConfig('owner')->getUID() . '" not found on Nextcloud. Check setting json.');
|
||||
}
|
||||
$this->setConfig('owner', $owner);
|
||||
}
|
||||
|
||||
protected function validateData(): void {
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->validateSystem();
|
||||
$this->validateConfig();
|
||||
$this->validateData();
|
||||
$this->getImportSystem()->bootstrap();
|
||||
}
|
||||
}
|
||||
214
lib/Service/Importer/Systems/TrelloApiService.php
Normal file
214
lib/Service/Importer/Systems/TrelloApiService.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class TrelloApiService extends TrelloJsonService {
|
||||
/** @var string */
|
||||
public static $name = 'Trello API';
|
||||
protected $needValidateData = false;
|
||||
/** @var IClient */
|
||||
private $httpClient;
|
||||
/** @var LoggerInterface */
|
||||
protected $logger;
|
||||
/** @var string */
|
||||
private $baseApiUrl = 'https://api.trello.com/1';
|
||||
/** @var ?\stdClass[] */
|
||||
private $boards;
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
IURLGenerator $urlGenerator,
|
||||
IL10N $l10n,
|
||||
LoggerInterface $logger,
|
||||
IClientService $httpClientService
|
||||
) {
|
||||
parent::__construct($userManager, $urlGenerator, $l10n);
|
||||
$this->logger = $logger;
|
||||
$this->httpClient = $httpClientService->newClient();
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->populateBoard();
|
||||
$this->populateMembers();
|
||||
$this->populateLabels();
|
||||
$this->populateLists();
|
||||
$this->populateCheckLists();
|
||||
$this->populateCards();
|
||||
$this->populateActions();
|
||||
parent::bootstrap();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
__DIR__,
|
||||
'..',
|
||||
'fixtures',
|
||||
'config-trelloApi-schema.json',
|
||||
]);
|
||||
}
|
||||
|
||||
private function populateActions(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->actions = $this->doRequest(
|
||||
'/boards/' . $data->id . '/actions',
|
||||
[
|
||||
'filter' => 'commentCard,createCard',
|
||||
'fields=memberCreator,type,data,date',
|
||||
'memberCreator_fields' => 'username',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateCards(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->cards = $this->doRequest(
|
||||
'/boards/' . $data->id . '/cards',
|
||||
[
|
||||
'fields' => 'id,idMembers,dateLastActivity,closed,idChecklists,name,idList,pos,desc,due,labels',
|
||||
'attachments' => true,
|
||||
'attachment_fields' => 'name,url,date',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateCheckLists(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->checklists = $this->doRequest(
|
||||
'/boards/' . $data->id . '/checkLists',
|
||||
[
|
||||
'fields' => 'id,idCard,name',
|
||||
'checkItem_fields' => 'id,state,name',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateLists(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->lists = $this->doRequest(
|
||||
'/boards/' . $data->id . '/lists',
|
||||
[
|
||||
'fields' => 'id,name,closed',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateLabels(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->labels = $this->doRequest(
|
||||
'/boards/' . $data->id . '/labels',
|
||||
[
|
||||
'fields' => 'id,color,name',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateMembers(): void {
|
||||
$data = $this->getImportService()->getData();
|
||||
$data->members = $this->doRequest(
|
||||
'/boards/' . $data->id . '/members',
|
||||
[
|
||||
'fields' => 'username',
|
||||
'limit' => 1000
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function populateBoard(): void {
|
||||
$toImport = $this->getImportService()->getConfig('board');
|
||||
$board = $this->doRequest(
|
||||
'/boards/' . $toImport,
|
||||
['fields' => 'id,name']
|
||||
);
|
||||
if ($board instanceof \stdClass) {
|
||||
$this->getImportService()->setData($board);
|
||||
return;
|
||||
}
|
||||
throw new \Exception('Invalid board id to import');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\stdClass
|
||||
*/
|
||||
private function doRequest(string $path = '', array $queryString = []) {
|
||||
$target = $this->baseApiUrl . $path;
|
||||
try {
|
||||
$result = $this->httpClient
|
||||
->get($target, $this->getQueryString($queryString))
|
||||
->getBody();
|
||||
if (is_string($result)) {
|
||||
$data = json_decode($result);
|
||||
if (is_array($data)) {
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$this->paginate($path, $queryString, $data)
|
||||
);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
throw new \Exception('Invalid return of api');
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->critical(
|
||||
$e->getMessage(),
|
||||
['app' => 'deck']
|
||||
);
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function paginate(string $path = '', array $queryString = [], array $data = []): array {
|
||||
if (empty($queryString['limit'])) {
|
||||
return [];
|
||||
}
|
||||
if (count($data) < $queryString['limit']) {
|
||||
return [];
|
||||
}
|
||||
$queryString['before'] = end($data)->id;
|
||||
$return = $this->doRequest($path, $queryString);
|
||||
if (is_array($return)) {
|
||||
return $return;
|
||||
}
|
||||
throw new \Exception('Invalid return of api');
|
||||
}
|
||||
|
||||
private function getQueryString(array $params = []): array {
|
||||
$apiSettings = $this->getImportService()->getConfig('api');
|
||||
$params['key'] = $apiSettings->key;
|
||||
$params['token'] = $apiSettings->token;
|
||||
return [
|
||||
'query' => $params
|
||||
];
|
||||
}
|
||||
}
|
||||
400
lib/Service/Importer/Systems/TrelloJsonService.php
Normal file
400
lib/Service/Importer/Systems/TrelloJsonService.php
Normal file
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OC\Comments\Comment;
|
||||
use OCA\Deck\BadRequestException;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Service\Importer\ABoardImportService;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class TrelloJsonService extends ABoardImportService {
|
||||
/** @var string */
|
||||
public static $name = 'Trello JSON';
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
/** @var IUser[] */
|
||||
private $members = [];
|
||||
|
||||
public function __construct(
|
||||
IUserManager $userManager,
|
||||
IURLGenerator $urlGenerator,
|
||||
IL10N $l10n
|
||||
) {
|
||||
$this->userManager = $userManager;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function bootstrap(): void {
|
||||
$this->validateUsers();
|
||||
}
|
||||
|
||||
public function getJsonSchemaPath(): string {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
__DIR__,
|
||||
'..',
|
||||
'fixtures',
|
||||
'config-trelloJson-schema.json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function validateUsers(): void {
|
||||
if (empty($this->getImportService()->getConfig('uidRelation'))) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->getImportService()->getConfig('uidRelation') as $trelloUid => $nextcloudUid) {
|
||||
$user = array_filter($this->getImportService()->getData()->members, function (\stdClass $u) use ($trelloUid) {
|
||||
return $u->username === $trelloUid;
|
||||
});
|
||||
if (!$user) {
|
||||
throw new \LogicException('Trello user ' . $trelloUid . ' not found in property "members" of json data');
|
||||
}
|
||||
if (!is_string($nextcloudUid) && !is_numeric($nextcloudUid)) {
|
||||
throw new \LogicException('User on setting uidRelation is invalid');
|
||||
}
|
||||
$nextcloudUid = (string) $nextcloudUid;
|
||||
$this->getImportService()->getConfig('uidRelation')->$trelloUid = $this->userManager->get($nextcloudUid);
|
||||
if (!$this->getImportService()->getConfig('uidRelation')->$trelloUid) {
|
||||
throw new \LogicException('User on setting uidRelation not found: ' . $nextcloudUid);
|
||||
}
|
||||
$user = current($user);
|
||||
$this->members[$user->id] = $this->getImportService()->getConfig('uidRelation')->$trelloUid;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCardAssignments(): array {
|
||||
$assignments = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
foreach ($trelloCard->idMembers as $idMember) {
|
||||
if (empty($this->members[$idMember])) {
|
||||
continue;
|
||||
}
|
||||
$assignment = new Assignment();
|
||||
$assignment->setCardId($this->cards[$trelloCard->id]->getId());
|
||||
$assignment->setParticipant($this->members[$idMember]->getUID());
|
||||
$assignment->setType(Assignment::TYPE_USER);
|
||||
$assignments[$trelloCard->id][] = $assignment;
|
||||
}
|
||||
}
|
||||
return $assignments;
|
||||
}
|
||||
|
||||
public function getComments(): array {
|
||||
$comments = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
$values = array_filter(
|
||||
$this->getImportService()->getData()->actions,
|
||||
function (\stdClass $a) use ($trelloCard) {
|
||||
return $a->type === 'commentCard' && $a->data->card->id === $trelloCard->id;
|
||||
}
|
||||
);
|
||||
$keys = array_map(function (\stdClass $c): string {
|
||||
return $c->id;
|
||||
}, $values);
|
||||
$trelloComments = array_combine($keys, $values);
|
||||
$trelloComments = $this->sortComments($trelloComments);
|
||||
foreach ($trelloComments as $commentId => $trelloComment) {
|
||||
$cardId = $this->cards[$trelloCard->id]->getId();
|
||||
$comment = new Comment();
|
||||
if (!empty($this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username})) {
|
||||
$actor = $this->getImportService()->getConfig('uidRelation')->{$trelloComment->memberCreator->username}->getUID();
|
||||
} else {
|
||||
$actor = $this->getImportService()->getConfig('owner')->getUID();
|
||||
}
|
||||
$message = $this->replaceUsernames($trelloComment->data->text);
|
||||
if (mb_strlen($message, 'UTF-8') > IComment::MAX_MESSAGE_LENGTH) {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setCardId($cardId);
|
||||
$attachment->setType('deck_file');
|
||||
$attachment->setCreatedBy($actor);
|
||||
$attachment->setLastModified(time());
|
||||
$attachment->setCreatedAt(time());
|
||||
$attachment->setData('comment_' . $commentId . '.md');
|
||||
$attachment = $this->getImportService()->insertAttachment($attachment, $message);
|
||||
|
||||
$urlToDownloadAttachment = $this->urlGenerator->linkToRouteAbsolute(
|
||||
'deck.attachment.display',
|
||||
[
|
||||
'cardId' => $cardId,
|
||||
'attachmentId' => $attachment->getId()
|
||||
]
|
||||
);
|
||||
$message = $this->l10n->t(
|
||||
"This comment has more than %s characters.\n" .
|
||||
"Added as an attachment to the card with name %s.\n" .
|
||||
"Accessible on URL: %s.",
|
||||
[
|
||||
IComment::MAX_MESSAGE_LENGTH,
|
||||
'comment_' . $commentId . '.md',
|
||||
$urlToDownloadAttachment
|
||||
]
|
||||
);
|
||||
}
|
||||
$comment
|
||||
->setActor('users', $actor)
|
||||
->setMessage($message)
|
||||
->setCreationDateTime(
|
||||
\DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloComment->date)
|
||||
);
|
||||
$comments[$cardId][$commentId] = $comment;
|
||||
}
|
||||
}
|
||||
return $comments;
|
||||
}
|
||||
|
||||
private function sortComments(array $comments): array {
|
||||
$comparison = function (\stdClass $a, \stdClass $b): int {
|
||||
if ($a->date == $b->date) {
|
||||
return 0;
|
||||
}
|
||||
return ($a->date < $b->date) ? -1 : 1;
|
||||
};
|
||||
|
||||
usort($comments, $comparison);
|
||||
return $comments;
|
||||
}
|
||||
|
||||
public function getCardLabelAssignment(): array {
|
||||
$cardsLabels = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
foreach ($trelloCard->labels as $label) {
|
||||
$cardId = $this->cards[$trelloCard->id]->getId();
|
||||
$labelId = $this->labels[$label->id]->getId();
|
||||
$cardsLabels[$cardId][] = $labelId;
|
||||
}
|
||||
}
|
||||
return $cardsLabels;
|
||||
}
|
||||
|
||||
public function getBoard(): Board {
|
||||
$board = $this->getImportService()->getBoard();
|
||||
if (empty($this->getImportService()->getData()->name)) {
|
||||
throw new BadRequestException('Invalid name of board');
|
||||
}
|
||||
$board->setTitle($this->getImportService()->getData()->name);
|
||||
$board->setOwner($this->getImportService()->getConfig('owner')->getUID());
|
||||
$board->setColor($this->getImportService()->getConfig('color'));
|
||||
return $board;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Label[]
|
||||
*/
|
||||
public function getLabels(): array {
|
||||
foreach ($this->getImportService()->getData()->labels as $trelloLabel) {
|
||||
$label = new Label();
|
||||
if (empty($trelloLabel->name)) {
|
||||
$label->setTitle('Unnamed ' . $trelloLabel->color . ' label');
|
||||
} else {
|
||||
$label->setTitle($trelloLabel->name);
|
||||
}
|
||||
$label->setColor($this->translateColor($trelloLabel->color));
|
||||
$label->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$this->labels[$trelloLabel->id] = $label;
|
||||
}
|
||||
return $this->labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Stack[]
|
||||
*/
|
||||
public function getStacks(): array {
|
||||
$return = [];
|
||||
foreach ($this->getImportService()->getData()->lists as $order => $list) {
|
||||
$stack = new Stack();
|
||||
if ($list->closed) {
|
||||
$stack->setDeletedAt(time());
|
||||
}
|
||||
$stack->setTitle($list->name);
|
||||
$stack->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$stack->setOrder($order + 1);
|
||||
$return[$list->id] = $stack;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Card[]
|
||||
*/
|
||||
public function getCards(): array {
|
||||
$checklists = [];
|
||||
foreach ($this->getImportService()->getData()->checklists as $checklist) {
|
||||
$checklists[$checklist->idCard][$checklist->id] = $this->formulateChecklistText($checklist);
|
||||
}
|
||||
$this->getImportService()->getData()->checklists = $checklists;
|
||||
|
||||
$cards = [];
|
||||
foreach ($this->getImportService()->getData()->cards as $trelloCard) {
|
||||
$card = new Card();
|
||||
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
|
||||
$card->setLastModified($lastModified->format('Y-m-d H:i:s'));
|
||||
if ($trelloCard->closed) {
|
||||
$card->setArchived(true);
|
||||
}
|
||||
if ((count($trelloCard->idChecklists) !== 0)) {
|
||||
foreach ($this->getImportService()->getData()->checklists[$trelloCard->id] as $checklist) {
|
||||
$trelloCard->desc .= "\n" . $checklist;
|
||||
}
|
||||
}
|
||||
$this->appendAttachmentsToDescription($trelloCard);
|
||||
|
||||
$card->setTitle($trelloCard->name);
|
||||
$card->setStackId($this->stacks[$trelloCard->idList]->getId());
|
||||
$cardsOnStack = $this->stacks[$trelloCard->idList]->getCards();
|
||||
$cardsOnStack[] = $card;
|
||||
$this->stacks[$trelloCard->idList]->setCards($cardsOnStack);
|
||||
$card->setType('plain');
|
||||
$card->setOrder($trelloCard->pos);
|
||||
$card->setOwner($this->getImportService()->getConfig('owner')->getUID());
|
||||
|
||||
$lastModified = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->dateLastActivity);
|
||||
$card->setLastModified($lastModified->format('U'));
|
||||
|
||||
$createCardDate = array_filter(
|
||||
$this->getImportService()->getData()->actions,
|
||||
function (\stdClass $a) use ($trelloCard) {
|
||||
return $a->type === 'createCard' && $a->data->card->id === $trelloCard->id;
|
||||
}
|
||||
);
|
||||
$createCardDate = current($createCardDate);
|
||||
$createCardDate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $createCardDate->date);
|
||||
if ($createCardDate) {
|
||||
$card->setCreatedAt($createCardDate->format('U'));
|
||||
} else {
|
||||
$card->setCreatedAt($lastModified->format('U'));
|
||||
}
|
||||
|
||||
$card->setDescription($trelloCard->desc);
|
||||
if ($trelloCard->due) {
|
||||
$duedate = \DateTime::createFromFormat('Y-m-d\TH:i:s.v\Z', $trelloCard->due)
|
||||
->format('Y-m-d H:i:s');
|
||||
$card->setDuedate($duedate);
|
||||
}
|
||||
$cards[$trelloCard->id] = $card;
|
||||
}
|
||||
return $cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Acl[]
|
||||
*/
|
||||
public function getAclList(): array {
|
||||
$return = [];
|
||||
foreach ($this->members as $member) {
|
||||
if ($member->getUID() === $this->getImportService()->getConfig('owner')->getUID()) {
|
||||
continue;
|
||||
}
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId($this->getImportService()->getBoard()->getId());
|
||||
$acl->setType(Acl::PERMISSION_TYPE_USER);
|
||||
$acl->setParticipant($member->getUID());
|
||||
$acl->setPermissionEdit(false);
|
||||
$acl->setPermissionShare(false);
|
||||
$acl->setPermissionManage(false);
|
||||
$return[] = $acl;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function translateColor(string $color): string {
|
||||
switch ($color) {
|
||||
case 'red':
|
||||
return 'ff0000';
|
||||
case 'yellow':
|
||||
return 'ffff00';
|
||||
case 'orange':
|
||||
return 'ff6600';
|
||||
case 'green':
|
||||
return '00ff00';
|
||||
case 'purple':
|
||||
return '9900ff';
|
||||
case 'blue':
|
||||
return '0000ff';
|
||||
case 'sky':
|
||||
return '00ccff';
|
||||
case 'lime':
|
||||
return '00ff99';
|
||||
case 'pink':
|
||||
return 'ff66cc';
|
||||
case 'black':
|
||||
return '000000';
|
||||
default:
|
||||
return 'ffffff';
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceUsernames(string $text): string {
|
||||
foreach ($this->getImportService()->getConfig('uidRelation') as $trello => $nextcloud) {
|
||||
$text = str_replace($trello, $nextcloud->getUID(), $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function checklistItem(\stdClass $item): string {
|
||||
if (($item->state == 'incomplete')) {
|
||||
$string_start = '- [ ]';
|
||||
} else {
|
||||
$string_start = '- [x]';
|
||||
}
|
||||
$check_item_string = $string_start . ' ' . $item->name . "\n";
|
||||
return $check_item_string;
|
||||
}
|
||||
|
||||
private function formulateChecklistText(\stdClass $checklist): string {
|
||||
$checklist_string = "\n\n## {$checklist->name}\n";
|
||||
foreach ($checklist->checkItems as $item) {
|
||||
$checklist_item_string = $this->checklistItem($item);
|
||||
$checklist_string = $checklist_string . "\n" . $checklist_item_string;
|
||||
}
|
||||
return $checklist_string;
|
||||
}
|
||||
|
||||
private function appendAttachmentsToDescription(\stdClass $trelloCard): void {
|
||||
if (empty($trelloCard->attachments)) {
|
||||
return;
|
||||
}
|
||||
$trelloCard->desc .= "\n\n## {$this->l10n->t('Attachments')}\n";
|
||||
$trelloCard->desc .= "| {$this->l10n->t('File')} | {$this->l10n->t('date')} |\n";
|
||||
$trelloCard->desc .= "|---|---\n";
|
||||
foreach ($trelloCard->attachments as $attachment) {
|
||||
$name = mb_strlen($attachment->name, 'UTF-8') ? $attachment->name : $attachment->url;
|
||||
$trelloCard->desc .= "| [{$name}]({$attachment->url}) | {$attachment->date} |\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
41
lib/Service/Importer/fixtures/config-trelloApi-schema.json
Normal file
41
lib/Service/Importer/fixtures/config-trelloApi-schema.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-fA-F]{32}$"
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-fA-F]{64}$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"type": "string",
|
||||
"pattern": "^\\w{1,}$"
|
||||
},
|
||||
"uidRelation": {
|
||||
"type": "object",
|
||||
"comment": "Relationship between Trello and Nextcloud usernames",
|
||||
"example": {
|
||||
"johndoe": "admin"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"comment": "Nextcloud owner username"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"pattern": "^[0-9a-fA-F]{6}$",
|
||||
"comment": "Default color for the board. If you don't inform, the default color will be used.",
|
||||
"default": "0800fd"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
lib/Service/Importer/fixtures/config-trelloJson-schema.json
Normal file
24
lib/Service/Importer/fixtures/config-trelloJson-schema.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uidRelation": {
|
||||
"type": "object",
|
||||
"comment": "Relationship between Trello and Nextcloud usernames",
|
||||
"example": {
|
||||
"johndoe": "admin"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"comment": "Nextcloud owner username"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"pattern": "^[0-9a-fA-F]{6}$",
|
||||
"comment": "Default color for the board. If you don't inform, the default color will be used.",
|
||||
"default": "0800fd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class OverviewService {
|
||||
private function findAllBoardsFromUser(string $userId): array {
|
||||
$userInfo = $this->getBoardPrerequisites($userId);
|
||||
$userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null);
|
||||
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null);
|
||||
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'], null, null);
|
||||
$circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null);
|
||||
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
}
|
||||
|
||||
@@ -500,7 +500,7 @@ class DeckShareProvider implements \OCP\Share\IShareProvider {
|
||||
);
|
||||
}
|
||||
|
||||
$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
|
||||
$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
|
||||
$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
|
||||
|
||||
$qb->orderBy('s.id');
|
||||
|
||||
@@ -11,4 +11,6 @@ pages:
|
||||
- Nextcloud API: API-Nextcloud.md
|
||||
- Developer documentation:
|
||||
- Data structure: structure.md
|
||||
|
||||
- Import documentation:
|
||||
- Implement import: implement-import.md
|
||||
- Class diagram: import-class-diagram.md
|
||||
|
||||
8159
package-lock.json
generated
8159
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deck",
|
||||
"description": "",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-beta1",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
@@ -29,10 +29,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@juliushaertl/vue-richtext": "^1.0.1",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.7.0",
|
||||
"@nextcloud/axios": "^1.9.0",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
@@ -40,14 +40,14 @@
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/moment": "^1.1.1",
|
||||
"@nextcloud/router": "^2.0.0",
|
||||
"@nextcloud/vue": "^4.2.0",
|
||||
"@nextcloud/vue": "^5.0.0",
|
||||
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"dompurify": "^2.3.3",
|
||||
"dompurify": "^2.3.6",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^12.2.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"markdown-it-link-attributes": "^3.0.0",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"p-queue": "^6.6.2",
|
||||
@@ -66,18 +66,18 @@
|
||||
"extends @nextcloud/browserslist-config"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
"node": "^14.0.0",
|
||||
"npm": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.2.0",
|
||||
"@nextcloud/eslint-config": "^6.1.0",
|
||||
"@nextcloud/stylelint-config": "^1.0.0-beta.0",
|
||||
"@nextcloud/webpack-vue-config": "^4.1.2",
|
||||
"@relative-ci/agent": "^3.0.0",
|
||||
"@vue/test-utils": "^1.2.2",
|
||||
"jest": "^27.3.1",
|
||||
"@nextcloud/eslint-config": "^6.1.2",
|
||||
"@nextcloud/stylelint-config": "^2.1.2",
|
||||
"@nextcloud/webpack-vue-config": "^5.0.0",
|
||||
"@relative-ci/agent": "^3.1.1",
|
||||
"@vue/test-utils": "^1.3.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"vue-jest": "^3.0.7"
|
||||
},
|
||||
@@ -97,4 +97,4 @@
|
||||
"<rootDir>/node_modules/jest-serializer-vue"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ export default {
|
||||
navShown: state => state.navShown,
|
||||
sidebarShownState: state => state.sidebarShown,
|
||||
currentBoard: state => state.currentBoard,
|
||||
cardDetailsInModal: state => state.cardDetailsInModal,
|
||||
}),
|
||||
// TODO: properly handle sidebar showing for route subview and board sidebar
|
||||
sidebarRouterView() {
|
||||
@@ -98,6 +97,14 @@ export default {
|
||||
sidebarShown() {
|
||||
return this.sidebarRouterView || this.sidebarShownState
|
||||
},
|
||||
cardDetailsInModal: {
|
||||
get() {
|
||||
return this.$store.getters.config('cardDetailsInModal')
|
||||
},
|
||||
set(newValue) {
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$store.dispatch('loadBoards')
|
||||
|
||||
@@ -239,6 +239,7 @@ export default {
|
||||
isAddStackVisible: false,
|
||||
filter: { tags: [], users: [], due: '', unassigned: false },
|
||||
showAddCardModal: false,
|
||||
defaultPageTitle: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -266,11 +267,17 @@ export default {
|
||||
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.setPageTitle('')
|
||||
},
|
||||
watch: {
|
||||
board(current, previous) {
|
||||
if (current?.id !== previous?.id) {
|
||||
this.clearFilter()
|
||||
}
|
||||
if (current) {
|
||||
this.setPageTitle(current.title)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -330,6 +337,22 @@ export default {
|
||||
clickHideAddCardModel() {
|
||||
this.showAddCardModal = false
|
||||
},
|
||||
setPageTitle(title) {
|
||||
if (this.defaultPageTitle === false) {
|
||||
this.defaultPageTitle = window.document.title
|
||||
if (this.defaultPageTitle.indexOf(' - Deck - ') !== -1) {
|
||||
this.defaultPageTitle = this.defaultPageTitle.substring(this.defaultPageTitle.indexOf(' - Deck - ') + 3)
|
||||
}
|
||||
if (this.defaultPageTitle.indexOf('Deck - ') !== 0) {
|
||||
this.defaultPageTitle = 'Deck - ' + this.defaultPageTitle
|
||||
}
|
||||
}
|
||||
let newTitle = this.defaultPageTitle
|
||||
if (title !== '') {
|
||||
newTitle = `${title} - ${newTitle}`
|
||||
}
|
||||
window.document.title = newTitle
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -162,7 +162,6 @@ export default {
|
||||
]),
|
||||
...mapState({
|
||||
showArchived: state => state.showArchived,
|
||||
cardDetailsInModal: state => state.cardDetailsInModal,
|
||||
}),
|
||||
cardsByStack() {
|
||||
return this.$store.getters.cardsByStack(this.stack.id).filter((card) => {
|
||||
@@ -175,6 +174,14 @@ export default {
|
||||
dragHandleSelector() {
|
||||
return this.canEdit ? null : '.no-drag'
|
||||
},
|
||||
cardDetailsInModal: {
|
||||
get() {
|
||||
return this.$store.getters.config('cardDetailsInModal')
|
||||
},
|
||||
set(newValue) {
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
@@ -25,13 +25,14 @@
|
||||
:active="tabId"
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:subtitle-tooltip="subtitleTooltip"
|
||||
:title-editable="titleEditable"
|
||||
@update:titleEditable="handleUpdateTitleEditable"
|
||||
@update:title="handleUpdateTitle"
|
||||
@submit-title="handleSubmitTitle"
|
||||
@close="closeSidebar">
|
||||
<template #secondary-actions>
|
||||
<ActionButton v-if="cardDetailsInModal" icon="icon-menu-sidebar" @click.stop="showModal()">
|
||||
<ActionButton v-if="cardDetailsInModal" icon="icon-menu-sidebar" @click.stop="closeModal()">
|
||||
{{ t('deck', 'Open in sidebar view') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-else icon="icon-external" @click.stop="showModal()">
|
||||
@@ -88,8 +89,10 @@ import CardSidebarTabAttachments from './CardSidebarTabAttachments'
|
||||
import CardSidebarTabComments from './CardSidebarTabComments'
|
||||
import CardSidebarTabActivity from './CardSidebarTabActivity'
|
||||
import relativeDate from '../../mixins/relativeDate'
|
||||
import moment from '@nextcloud/moment'
|
||||
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { getLocale } from '@nextcloud/l10n'
|
||||
|
||||
const capabilities = window.OC.getCapabilities()
|
||||
|
||||
@@ -126,12 +129,12 @@ export default {
|
||||
titleEditable: false,
|
||||
titleEditing: '',
|
||||
hasActivity: capabilities && capabilities.activity,
|
||||
locale: getLocale(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentBoard: state => state.currentBoard,
|
||||
cardDetailsInModal: state => state.cardDetailsInModal,
|
||||
}),
|
||||
...mapGetters(['canEdit', 'assignables', 'cardActions', 'stackById']),
|
||||
title() {
|
||||
@@ -143,6 +146,9 @@ export default {
|
||||
subtitle() {
|
||||
return t('deck', 'Modified') + ': ' + this.relativeDate(this.currentCard.lastModified * 1000) + ' ' + t('deck', 'Created') + ': ' + this.relativeDate(this.currentCard.createdAt * 1000)
|
||||
},
|
||||
subtitleTooltip() {
|
||||
return t('deck', 'Modified') + ': ' + this.formatDate(this.currentCard.lastModified) + '\n' + t('deck', 'Created') + ': ' + this.formatDate(this.currentCard.createdAt)
|
||||
},
|
||||
cardRichObject() {
|
||||
return {
|
||||
id: '' + this.currentCard.id,
|
||||
@@ -152,6 +158,14 @@ export default {
|
||||
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `#/board/${this.currentBoard.id}/card/${this.currentCard.id}`,
|
||||
}
|
||||
},
|
||||
cardDetailsInModal: {
|
||||
get() {
|
||||
return this.$store.getters.config('cardDetailsInModal')
|
||||
},
|
||||
set(newValue) {
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleUpdateTitleEditable(value) {
|
||||
@@ -177,7 +191,13 @@ export default {
|
||||
},
|
||||
|
||||
showModal() {
|
||||
this.$store.dispatch('setCardDetailsInModal', true)
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
|
||||
},
|
||||
closeModal() {
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
|
||||
},
|
||||
formatDate(timestamp) {
|
||||
return moment.unix(timestamp).locale(this.locale).format('LLLL')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -224,7 +224,6 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
currentBoard: state => state.currentBoard,
|
||||
cardDetailsInModal: state => state.cardDetailsInModal,
|
||||
}),
|
||||
...mapGetters(['canEdit', 'assignables']),
|
||||
formatedAssignables() {
|
||||
@@ -250,6 +249,14 @@ export default {
|
||||
return assignable
|
||||
})
|
||||
},
|
||||
cardDetailsInModal: {
|
||||
get() {
|
||||
return this.$store.getters.config('cardDetailsInModal')
|
||||
},
|
||||
set(newValue) {
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
|
||||
},
|
||||
},
|
||||
duedate: {
|
||||
get() {
|
||||
return this.card.duedate ? new Date(this.card.duedate) : null
|
||||
|
||||
@@ -132,7 +132,6 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
currentBoard: state => state.currentBoard,
|
||||
cardDetailsInModal: state => state.cardDetailsInModal,
|
||||
}),
|
||||
...mapGetters(['canEdit']),
|
||||
attachments() {
|
||||
@@ -251,6 +250,8 @@ export default {
|
||||
|
||||
&::v-deep .attachment-list {
|
||||
flex-shrink: 1;
|
||||
overflow: scroll;
|
||||
max-height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +262,8 @@ export default {
|
||||
|
||||
#description-preview {
|
||||
min-height: 100px;
|
||||
width: auto;
|
||||
overflow-x: auto;
|
||||
|
||||
&::v-deep {
|
||||
@import './../../css/markdown';
|
||||
@@ -331,4 +334,15 @@ h5 {
|
||||
#app-sidebar .app-sidebar-header__desc h4 {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.vue-easymde .cm-s-easymde .cm-link {
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
|
||||
.vue-easymde .cm-s-easymde .cm-string.cm-url,
|
||||
.vue-easymde .cm-s-easymde .cm-formatting.cm-link,
|
||||
.vue-easymde .cm-s-easymde .cm-formatting.cm-url,
|
||||
.vue-easymde .cm-s-easymde .cm-formatting.cm-image {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
:placeholder="t('deck', 'Select a list')"
|
||||
:options="stacksFromBoard"
|
||||
:max-height="100"
|
||||
label="title" />
|
||||
label="title">
|
||||
<span slot="noOptions">
|
||||
{{ t('deck', 'List is empty') }}
|
||||
</span>
|
||||
</Multiselect>
|
||||
|
||||
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
|
||||
{{ t('deck', 'Move card') }}
|
||||
|
||||
@@ -144,10 +144,10 @@ export default {
|
||||
},
|
||||
cardDetailsInModal: {
|
||||
get() {
|
||||
return this.$store.getters.cardDetailsInModal
|
||||
return this.$store.getters.config('cardDetailsInModal')
|
||||
},
|
||||
set(newValue) {
|
||||
this.$store.dispatch('setCardDetailsInModal', newValue)
|
||||
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
|
||||
},
|
||||
},
|
||||
configCalendar: {
|
||||
|
||||
@@ -45,7 +45,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
window.OCP.Collaboration.registerType('deck', {
|
||||
action: () => {
|
||||
const BoardSelector = () => import('./BoardSelector')
|
||||
return buildSelector(BoardSelector)
|
||||
buildSelector(BoardSelector)
|
||||
},
|
||||
typeString: t('deck', 'Link to a board'),
|
||||
typeIconClass: 'icon-deck',
|
||||
@@ -54,7 +54,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
window.OCP.Collaboration.registerType('deck-card', {
|
||||
action: () => {
|
||||
const CardSelector = () => import('./CardSelector')
|
||||
return buildSelector(CardSelector)
|
||||
buildSelector(CardSelector)
|
||||
},
|
||||
typeString: t('deck', 'Link to a card'),
|
||||
typeIconClass: 'icon-deck',
|
||||
|
||||
@@ -62,7 +62,6 @@ export default new Vuex.Store({
|
||||
showArchived: false,
|
||||
navShown: localStorage.getItem('deck.navShown') === 'true',
|
||||
compactMode: localStorage.getItem('deck.compactMode') === 'true',
|
||||
cardDetailsInModal: localStorage.getItem('deck.cardDetailsInModal') === 'true',
|
||||
sidebarShown: false,
|
||||
currentBoard: null,
|
||||
currentCard: null,
|
||||
@@ -79,9 +78,6 @@ export default new Vuex.Store({
|
||||
config: state => (key) => {
|
||||
return state.config[key]
|
||||
},
|
||||
cardDetailsInModal: state => {
|
||||
return state.cardDetailsInModal
|
||||
},
|
||||
getSearchQuery: state => {
|
||||
return state.searchQuery
|
||||
},
|
||||
@@ -233,10 +229,6 @@ export default new Vuex.Store({
|
||||
state.compactMode = !state.compactMode
|
||||
localStorage.setItem('deck.compactMode', state.compactMode)
|
||||
},
|
||||
setCardDetailsInModal(state) {
|
||||
state.cardDetailsInModal = !state.cardDetailsInModal
|
||||
localStorage.setItem('deck.cardDetailsInModal', state.cardDetailsInModal)
|
||||
},
|
||||
setBoards(state, boards) {
|
||||
state.boards = boards
|
||||
},
|
||||
@@ -441,9 +433,6 @@ export default new Vuex.Store({
|
||||
toggleCompactMode({ commit }) {
|
||||
commit('toggleCompactMode')
|
||||
},
|
||||
setCardDetailsInModal({ commit }, show) {
|
||||
commit('setCardDetailsInModal', show)
|
||||
},
|
||||
setCurrentBoard({ commit }, board) {
|
||||
commit('setCurrentBoard', board)
|
||||
},
|
||||
|
||||
7
tests/data/config-trelloJson.json
Normal file
7
tests/data/config-trelloJson.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"owner": "admin",
|
||||
"color": "0800fd",
|
||||
"uidRelation": {
|
||||
"johndoe": "johndoe"
|
||||
}
|
||||
}
|
||||
582
tests/data/data-trelloJson.json
Normal file
582
tests/data/data-trelloJson.json
Normal file
@@ -0,0 +1,582 @@
|
||||
{
|
||||
"id": "fakeboardidhash",
|
||||
"name": "Test Board Name",
|
||||
"desc": "",
|
||||
"descData": null,
|
||||
"closed": false,
|
||||
"dateClosed": null,
|
||||
"idOrganization": null,
|
||||
"shortLink": "qwerty",
|
||||
"powerUps": [],
|
||||
"dateLastActivity": "2021-07-10T17:01:58.633Z",
|
||||
"idTags": [],
|
||||
"datePluginDisable": null,
|
||||
"creationMethod": null,
|
||||
"idBoardSource": null,
|
||||
"idMemberCreator": "fakeidmemberhash",
|
||||
"idEnterprise": null,
|
||||
"pinned": false,
|
||||
"starred": false,
|
||||
"url": "https://trello.com/b/qwerty/fakeboardurl",
|
||||
"prefs": {
|
||||
"permissionLevel": "private",
|
||||
"hideVotes": false,
|
||||
"voting": "disabled",
|
||||
"comments": "members",
|
||||
"invitations": "members",
|
||||
"selfJoin": false,
|
||||
"cardCovers": true,
|
||||
"isTemplate": false,
|
||||
"cardAging": "regular",
|
||||
"calendarFeedEnabled": false,
|
||||
"background": "blue",
|
||||
"backgroundImage": null,
|
||||
"backgroundImageScaled": null,
|
||||
"backgroundTile": false,
|
||||
"backgroundBrightness": "dark",
|
||||
"backgroundColor": "#0079BF",
|
||||
"backgroundBottomColor": "#0079BF",
|
||||
"backgroundTopColor": "#0079BF",
|
||||
"canBePublic": true,
|
||||
"canBeEnterprise": true,
|
||||
"canBeOrg": true,
|
||||
"canBePrivate": true,
|
||||
"canInvite": true
|
||||
},
|
||||
"shortUrl": "https://trello.com/b/qwerty",
|
||||
"premiumFeatures": [],
|
||||
"enterpriseOwned": false,
|
||||
"ixUpdate": "67",
|
||||
"limits": {
|
||||
"attachments": {
|
||||
"perBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 36000,
|
||||
"warnAt": 32400
|
||||
},
|
||||
"perCard": {
|
||||
"status": "ok",
|
||||
"disableAt": 1000,
|
||||
"warnAt": 900
|
||||
}
|
||||
},
|
||||
"boards": {
|
||||
"totalMembersPerBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 1600,
|
||||
"warnAt": 1440
|
||||
}
|
||||
},
|
||||
"cards": {
|
||||
"openPerBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 5000,
|
||||
"warnAt": 4500
|
||||
},
|
||||
"openPerList": {
|
||||
"status": "ok",
|
||||
"disableAt": 5000,
|
||||
"warnAt": 4500
|
||||
},
|
||||
"totalPerBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 2000000,
|
||||
"warnAt": 1800000
|
||||
},
|
||||
"totalPerList": {
|
||||
"status": "ok",
|
||||
"disableAt": 1000000,
|
||||
"warnAt": 900000
|
||||
}
|
||||
},
|
||||
"checklists": {
|
||||
"perBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 2000000,
|
||||
"warnAt": 1800000
|
||||
},
|
||||
"perCard": {
|
||||
"status": "ok",
|
||||
"disableAt": 500,
|
||||
"warnAt": 450
|
||||
}
|
||||
},
|
||||
"checkItems": {
|
||||
"perChecklist": {
|
||||
"status": "ok",
|
||||
"disableAt": 200,
|
||||
"warnAt": 180
|
||||
}
|
||||
},
|
||||
"customFields": {
|
||||
"perBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 50,
|
||||
"warnAt": 45
|
||||
}
|
||||
},
|
||||
"customFieldOptions": {
|
||||
"perField": {
|
||||
"status": "ok",
|
||||
"disableAt": 50,
|
||||
"warnAt": 45
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"perBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 1000,
|
||||
"warnAt": 900
|
||||
}
|
||||
},
|
||||
"lists": {
|
||||
"openPerBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 500,
|
||||
"warnAt": 450
|
||||
},
|
||||
"totalPerBoard": {
|
||||
"status": "ok",
|
||||
"disableAt": 3000,
|
||||
"warnAt": 2700
|
||||
}
|
||||
},
|
||||
"stickers": {
|
||||
"perCard": {
|
||||
"status": "ok",
|
||||
"disableAt": 70,
|
||||
"warnAt": 63
|
||||
}
|
||||
},
|
||||
"reactions": {
|
||||
"perAction": {
|
||||
"status": "ok",
|
||||
"disableAt": 1000,
|
||||
"warnAt": 900
|
||||
},
|
||||
"uniquePerAction": {
|
||||
"status": "ok",
|
||||
"disableAt": 17,
|
||||
"warnAt": 16
|
||||
}
|
||||
}
|
||||
},
|
||||
"subscribed": false,
|
||||
"templateGallery": null,
|
||||
"dateLastView": "2021-07-10T17:01:58.665Z",
|
||||
"labelNames": {
|
||||
"green": "",
|
||||
"yellow": "",
|
||||
"orange": "",
|
||||
"red": "",
|
||||
"purple": "",
|
||||
"blue": "",
|
||||
"sky": "",
|
||||
"lime": "",
|
||||
"pink": "",
|
||||
"black": ""
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"id": "60e9d2869efe2e1141be2798",
|
||||
"idMemberCreator": "fakeidmemberhash",
|
||||
"data": {
|
||||
"idMember": "fakeidmemberhash",
|
||||
"deactivated": false,
|
||||
"card": {
|
||||
"id": "hashcard7",
|
||||
"name": "Name Card 7",
|
||||
"idShort": 7,
|
||||
"shortLink": "fakeshortlinkcard7"
|
||||
},
|
||||
"board": {
|
||||
"id": "fakeboardidhash",
|
||||
"name": "Test Board Name",
|
||||
"shortLink": "qwerty"
|
||||
},
|
||||
"member": {
|
||||
"id": "fakeidmemberhash",
|
||||
"name": "John Doe"
|
||||
}
|
||||
},
|
||||
"type": "removeMemberFromCard",
|
||||
"date": "2021-07-10T17:01:58.636Z",
|
||||
"appCreator": null,
|
||||
"limits": {},
|
||||
"member": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
},
|
||||
"memberCreator": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "60e9d1832ff82d10c0cea6ba",
|
||||
"idMemberCreator": "fakeidmemberhash",
|
||||
"data": {
|
||||
"idMember": "fakeidmemberhash",
|
||||
"card": {
|
||||
"id": "hashcard7",
|
||||
"name": "Name Card 7",
|
||||
"idShort": 7,
|
||||
"shortLink": "fakeshortlinkcard7"
|
||||
},
|
||||
"board": {
|
||||
"id": "fakeboardidhash",
|
||||
"name": "Test Board Name",
|
||||
"shortLink": "qwerty"
|
||||
},
|
||||
"member": {
|
||||
"id": "fakeidmemberhash",
|
||||
"name": "John Doe"
|
||||
}
|
||||
},
|
||||
"type": "addMemberToCard",
|
||||
"date": "2021-07-10T16:57:39.999Z",
|
||||
"appCreator": null,
|
||||
"limits": {},
|
||||
"member": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
},
|
||||
"memberCreator": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "59bbfc4bf36aa0270d6bfd43",
|
||||
"idMemberCreator": "fakeidmemberhash",
|
||||
"data": {
|
||||
"board": {
|
||||
"shortLink": "qwerty",
|
||||
"name": "Test Board Name",
|
||||
"id": "fakeboardidhash"
|
||||
},
|
||||
"list": {
|
||||
"name": "TODO",
|
||||
"id": "hashlisttodo"
|
||||
},
|
||||
"card": {
|
||||
"shortLink": "fakeshortlinkcard7",
|
||||
"idShort": 7,
|
||||
"name": "Name Card 7",
|
||||
"id": "hashcard7"
|
||||
}
|
||||
},
|
||||
"type": "createCard",
|
||||
"date": "2017-09-15T16:14:03.187Z",
|
||||
"appCreator": null,
|
||||
"limits": {},
|
||||
"memberCreator": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "59bbfb8e4a6f8ca35be9b82a",
|
||||
"idMemberCreator": "fakeidmemberhash",
|
||||
"data": {
|
||||
"board": {
|
||||
"shortLink": "qwerty",
|
||||
"name": "Test Board Name",
|
||||
"id": "fakeboardidhash"
|
||||
},
|
||||
"list": {
|
||||
"name": "TODO",
|
||||
"id": "hashlisttodo"
|
||||
}
|
||||
},
|
||||
"type": "createList",
|
||||
"date": "2017-09-15T16:10:54.714Z",
|
||||
"appCreator": null,
|
||||
"limits": {},
|
||||
"memberCreator": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "59bbfb88973b76e586edec5e",
|
||||
"idMemberCreator": "fakeidmemberhash",
|
||||
"data": {
|
||||
"board": {
|
||||
"shortLink": "qwerty",
|
||||
"name": "Test Board Name",
|
||||
"id": "fakeboardidhash"
|
||||
}
|
||||
},
|
||||
"type": "createBoard",
|
||||
"date": "2017-09-15T16:10:48.069Z",
|
||||
"appCreator": null,
|
||||
"limits": {},
|
||||
"memberCreator": {
|
||||
"id": "fakeidmemberhash",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idMemberReferrer": null,
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"id": "hashcard7",
|
||||
"address": null,
|
||||
"checkItemStates": null,
|
||||
"closed": false,
|
||||
"coordinates": null,
|
||||
"creationMethod": null,
|
||||
"dateLastActivity": "2021-07-10T17:01:58.633Z",
|
||||
"desc": "",
|
||||
"descData": null,
|
||||
"dueReminder": null,
|
||||
"idBoard": "fakeboardidhash",
|
||||
"idLabels": [],
|
||||
"idList": "hashlisttodo",
|
||||
"idMembersVoted": [],
|
||||
"idShort": 7,
|
||||
"idAttachmentCover": null,
|
||||
"locationName": null,
|
||||
"manualCoverAttachment": false,
|
||||
"name": "Name Card 7",
|
||||
"pos": 65535,
|
||||
"shortLink": "fakeshortlinkcard7",
|
||||
"isTemplate": false,
|
||||
"cardRole": null,
|
||||
"badges": {
|
||||
"attachmentsByType": {
|
||||
"trello": {
|
||||
"board": 0,
|
||||
"card": 0
|
||||
}
|
||||
},
|
||||
"location": false,
|
||||
"votes": 0,
|
||||
"viewingMemberVoted": false,
|
||||
"subscribed": false,
|
||||
"fogbugz": "",
|
||||
"checkItems": 0,
|
||||
"checkItemsChecked": 0,
|
||||
"checkItemsEarliestDue": null,
|
||||
"comments": 0,
|
||||
"attachments": 0,
|
||||
"description": false,
|
||||
"due": null,
|
||||
"dueComplete": false,
|
||||
"start": null
|
||||
},
|
||||
"dueComplete": false,
|
||||
"due": null,
|
||||
"email": "johndoe+card7@boards.trello.com",
|
||||
"idChecklists": [],
|
||||
"idMembers": [],
|
||||
"labels": [],
|
||||
"limits": {
|
||||
"attachments": {
|
||||
"perCard": {
|
||||
"status": "ok",
|
||||
"disableAt": 1000,
|
||||
"warnAt": 900
|
||||
}
|
||||
},
|
||||
"checklists": {
|
||||
"perCard": {
|
||||
"status": "ok",
|
||||
"disableAt": 500,
|
||||
"warnAt": 450
|
||||
}
|
||||
},
|
||||
"stickers": {
|
||||
"perCard": {
|
||||
"status": "ok",
|
||||
"disableAt": 70,
|
||||
"warnAt": 63
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortUrl": "https://trello.com/c/fakeshortlinkcard7",
|
||||
"start": null,
|
||||
"subscribed": false,
|
||||
"url": "https://trello.com/c/fakeshortlinkcard7/7-name-card-7",
|
||||
"cover": {
|
||||
"idAttachment": null,
|
||||
"color": null,
|
||||
"idUploadedBackground": null,
|
||||
"size": "normal",
|
||||
"brightness": "dark",
|
||||
"idPlugin": null
|
||||
},
|
||||
"attachments": [],
|
||||
"pluginData": [],
|
||||
"customFieldItems": []
|
||||
}
|
||||
],
|
||||
"labels": [
|
||||
{
|
||||
"id": "59bbfb881314a339999eb855",
|
||||
"idBoard": "fakeboardidhash",
|
||||
"name": "",
|
||||
"color": "yellow"
|
||||
}
|
||||
],
|
||||
"lists": [
|
||||
{
|
||||
"id": "hashlisttodo",
|
||||
"name": "TODO",
|
||||
"closed": false,
|
||||
"pos": 65535,
|
||||
"softLimit": null,
|
||||
"creationMethod": null,
|
||||
"idBoard": "fakeboardidhash",
|
||||
"limits": {
|
||||
"cards": {
|
||||
"openPerList": {
|
||||
"status": "ok",
|
||||
"disableAt": 5000,
|
||||
"warnAt": 4500
|
||||
},
|
||||
"totalPerList": {
|
||||
"status": "ok",
|
||||
"disableAt": 1000000,
|
||||
"warnAt": 900000
|
||||
}
|
||||
}
|
||||
},
|
||||
"subscribed": false
|
||||
}
|
||||
],
|
||||
"members": [
|
||||
{
|
||||
"id": "fakeidmemberhash",
|
||||
"bio": "",
|
||||
"bioData": {
|
||||
"emoji": {}
|
||||
},
|
||||
"confirmed": true,
|
||||
"memberType": "normal",
|
||||
"username": "johndoe",
|
||||
"activityBlocked": false,
|
||||
"avatarHash": "fakeavatarhash",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"fullName": "John Doe",
|
||||
"idEnterprise": null,
|
||||
"idEnterprisesDeactivated": [],
|
||||
"idMemberReferrer": null,
|
||||
"idPremOrgsAdmin": [],
|
||||
"initials": "JD",
|
||||
"nonPublic": {
|
||||
"fullName": "John Doe",
|
||||
"initials": "JD",
|
||||
"avatarUrl": "https://trello-members.s3.amazonaws.com/fakeidmemberhash/fakeavatarhash",
|
||||
"avatarHash": "fakeavatarhash"
|
||||
},
|
||||
"nonPublicAvailable": true,
|
||||
"products": [],
|
||||
"url": "https://trello.com/johndoe",
|
||||
"status": "disconnected"
|
||||
}
|
||||
],
|
||||
"checklists": [],
|
||||
"customFields": [],
|
||||
"memberships": [
|
||||
{
|
||||
"id": "59bbfb88973b76e586edec5d",
|
||||
"idMember": "fakeidmemberhash",
|
||||
"memberType": "admin",
|
||||
"unconfirmed": false,
|
||||
"deactivated": false
|
||||
}
|
||||
],
|
||||
"pluginData": []
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class BoardDatabaseTest extends \Test\TestCase {
|
||||
$board->setOwner(self::TEST_USER1);
|
||||
$board->setColor('000000');
|
||||
$board->setLabels([]);
|
||||
$created = $this->boardService->create('Test', self::TEST_USER1, '000000');
|
||||
$created = $this->boardService->create('Test', self::TEST_USER1, '000000');
|
||||
$id = $created->getId();
|
||||
$actual = $this->boardService->find($id);
|
||||
$this->assertEquals($actual->getTitle(), $board->getTitle());
|
||||
|
||||
@@ -38,7 +38,6 @@ use OCP\L10N\IFactory;
|
||||
use OCP\RichObjectStrings\IValidator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
||||
use OCA\Deck\Service\CardService;
|
||||
|
||||
class DeckProviderTest extends TestCase {
|
||||
|
||||
@@ -57,9 +56,6 @@ class DeckProviderTest extends TestCase {
|
||||
/** @var ICommentsManager|MockObject */
|
||||
private $commentsManager;
|
||||
|
||||
/** @var CardService|MockObject */
|
||||
private $cardService;
|
||||
|
||||
/** @var string */
|
||||
private $userId = 'admin';
|
||||
|
||||
@@ -71,9 +67,7 @@ class DeckProviderTest extends TestCase {
|
||||
$this->commentsManager = $this->createMock(ICommentsManager::class);
|
||||
$this->l10nFactory = $this->createMock(IFactory::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->cardService = $this->createMock(CardService::class);
|
||||
$this->provider = new DeckProvider($this->urlGenerator, $this->activityManager, $this->userManager, $this->commentsManager, $this->l10nFactory, $this->config, $this->userId, $this->cardService);
|
||||
$this->provider = new DeckProvider($this->urlGenerator, $this->activityManager, $this->userManager, $this->commentsManager, $this->l10nFactory, $this->config, $this->userId);
|
||||
}
|
||||
|
||||
private function mockEvent($objectType, $objectId, $objectName, $subject, $subjectParameters = []) {
|
||||
|
||||
77
tests/unit/Command/BoardImportTest.php
Normal file
77
tests/unit/Command/BoardImportTest.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Command;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportCommandService;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class BoardImportTest extends \Test\TestCase {
|
||||
/** @var BoardImportCommandService */
|
||||
private $boardImportCommandService;
|
||||
/** @var BoardImport */
|
||||
private $boardImport;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->boardImportCommandService = $this->createMock(BoardImportCommandService::class);
|
||||
$this->boardImport = new BoardImport(
|
||||
$this->boardImportCommandService
|
||||
);
|
||||
$questionHelper = new QuestionHelper();
|
||||
$this->boardImport->setHelperSet(
|
||||
new HelperSet([
|
||||
$questionHelper
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
public function testExecuteWithSuccess() {
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$input
|
||||
->method('getOption')
|
||||
->withConsecutive(
|
||||
['system'],
|
||||
['config']
|
||||
)
|
||||
->will($this->returnValueMap([
|
||||
['system', 'trelloJson'],
|
||||
['config', null]
|
||||
]));
|
||||
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
|
||||
$output
|
||||
->expects($this->once())
|
||||
->method('writeLn')
|
||||
->with('Done!');
|
||||
|
||||
$actual = $this->invokePrivate($this->boardImport, 'interact', [$input, $output]);
|
||||
$this->assertNull($actual);
|
||||
$actual = $this->invokePrivate($this->boardImport, 'execute', [$input, $output]);
|
||||
$this->assertEquals(0, $actual);
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,10 @@ class AclMapperTest extends MapperTestUtility {
|
||||
$this->boardMapper->insert($this->getBoard('MyBoard 3', 'user3'))
|
||||
];
|
||||
$this->acls = [
|
||||
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[1]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user','user2', true, false, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user','user3', true, true, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[2]->getId()))
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[1]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user2', true, false, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user3', true, true, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[2]->getId()))
|
||||
];
|
||||
|
||||
foreach ($this->acls as $acl) {
|
||||
|
||||
@@ -76,10 +76,10 @@ class BoardMapperTest extends MapperTestUtility {
|
||||
$this->boardMapper->insert($this->getBoard('MyBoard 3', 'user3'))
|
||||
];
|
||||
$this->acls = [
|
||||
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[1]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user','user2', true, false, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user','user3', true, true, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user','user1', false, false, false, $this->boards[2]->getId()))
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[1]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user2', true, false, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user3', true, true, false, $this->boards[0]->getId())),
|
||||
$this->aclMapper->insert($this->getAcl('user', 'user1', false, false, false, $this->boards[2]->getId()))
|
||||
];
|
||||
|
||||
foreach ($this->acls as $acl) {
|
||||
|
||||
@@ -176,9 +176,9 @@ class AttachmentServiceTest extends TestCase {
|
||||
public function testFindAll() {
|
||||
$this->mockPermission(Acl::PERMISSION_READ);
|
||||
$attachments = [
|
||||
$this->createAttachment('deck_file','file1'),
|
||||
$this->createAttachment('deck_file','file2'),
|
||||
$this->createAttachment('deck_file_invalid','file3'),
|
||||
$this->createAttachment('deck_file', 'file1'),
|
||||
$this->createAttachment('deck_file', 'file2'),
|
||||
$this->createAttachment('deck_file_invalid', 'file3'),
|
||||
];
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
@@ -197,14 +197,14 @@ class AttachmentServiceTest extends TestCase {
|
||||
public function testFindAllWithDeleted() {
|
||||
$this->mockPermission(Acl::PERMISSION_READ);
|
||||
$attachments = [
|
||||
$this->createAttachment('deck_file','file1'),
|
||||
$this->createAttachment('deck_file','file2'),
|
||||
$this->createAttachment('deck_file_invalid','file3'),
|
||||
$this->createAttachment('deck_file', 'file1'),
|
||||
$this->createAttachment('deck_file', 'file2'),
|
||||
$this->createAttachment('deck_file_invalid', 'file3'),
|
||||
];
|
||||
$attachmentsDeleted = [
|
||||
$this->createAttachment('deck_file','file4'),
|
||||
$this->createAttachment('deck_file','file5'),
|
||||
$this->createAttachment('deck_file_invalid','file6'),
|
||||
$this->createAttachment('deck_file', 'file4'),
|
||||
$this->createAttachment('deck_file', 'file5'),
|
||||
$this->createAttachment('deck_file_invalid', 'file6'),
|
||||
];
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
|
||||
@@ -42,7 +42,6 @@ use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IGroupManager;
|
||||
use \Test\TestCase;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class BoardServiceTest extends TestCase {
|
||||
|
||||
@@ -75,8 +74,6 @@ class BoardServiceTest extends TestCase {
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
private $userId = 'admin';
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
@@ -94,7 +91,6 @@ class BoardServiceTest extends TestCase {
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
$this->changeHelper = $this->createMock(ChangeHelper::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
|
||||
$this->service = new BoardService(
|
||||
$this->boardMapper,
|
||||
@@ -111,7 +107,6 @@ class BoardServiceTest extends TestCase {
|
||||
$this->activityManager,
|
||||
$this->eventDispatcher,
|
||||
$this->changeHelper,
|
||||
$this->urlGenerator,
|
||||
$this->userId
|
||||
);
|
||||
|
||||
@@ -396,7 +391,7 @@ class BoardServiceTest extends TestCase {
|
||||
$this->aclMapper->expects($this->once())
|
||||
->method('delete')
|
||||
->with($acl)
|
||||
->willReturn(true);
|
||||
$this->assertTrue($this->service->deleteAcl(123));
|
||||
->willReturn($acl);
|
||||
$this->assertEquals($acl, $this->service->deleteAcl(123));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Test\TestCase;
|
||||
use OCP\IURLGenerator;
|
||||
|
||||
class CardServiceTest extends TestCase {
|
||||
|
||||
@@ -77,9 +76,6 @@ class CardServiceTest extends TestCase {
|
||||
/** @var ChangeHelper|MockObject */
|
||||
private $changeHelper;
|
||||
|
||||
/** @var IURLGenerator|MockObject */
|
||||
private $urlGenerator;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
@@ -96,7 +92,6 @@ class CardServiceTest extends TestCase {
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->changeHelper = $this->createMock(ChangeHelper::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->cardService = new CardService(
|
||||
$this->cardMapper,
|
||||
$this->stackMapper,
|
||||
@@ -112,7 +107,6 @@ class CardServiceTest extends TestCase {
|
||||
$this->userManager,
|
||||
$this->changeHelper,
|
||||
$this->eventDispatcher,
|
||||
$this->urlGenerator,
|
||||
'user1'
|
||||
);
|
||||
}
|
||||
|
||||
199
tests/unit/Service/Importer/BoardImportServiceTest.php
Normal file
199
tests/unit/Service/Importer/BoardImportServiceTest.php
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Deck\Service\Importer;
|
||||
|
||||
use OC\Comments\Comment;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\Assignment;
|
||||
use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Event\BoardImportGetAllowedEvent;
|
||||
use OCA\Deck\Service\Importer\Systems\TrelloJsonService;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class BoardImportServiceTest extends \Test\TestCase {
|
||||
/** @var IDBConnection|MockObject */
|
||||
protected $dbConn;
|
||||
/** @var IUserManager|MockObject */
|
||||
private $userManager;
|
||||
/** @var BoardMapper|MockObject */
|
||||
private $boardMapper;
|
||||
/** @var AclMapper|MockObject */
|
||||
private $aclMapper;
|
||||
/** @var LabelMapper|MockObject */
|
||||
private $labelMapper;
|
||||
/** @var StackMapper|MockObject */
|
||||
private $stackMapper;
|
||||
/** @var CardMapper|MockObject */
|
||||
private $cardMapper;
|
||||
/** @var AssignmentMapper|MockObject */
|
||||
private $assignmentMapper;
|
||||
/** @var AttachmentMapper|MockObject */
|
||||
private $attachmentMapper;
|
||||
/** @var ICommentsManager|MockObject */
|
||||
private $commentsManager;
|
||||
/** @var IEventDispatcher|MockObject */
|
||||
private $eventDispatcher;
|
||||
/** @var TrelloJsonService|MockObject */
|
||||
private $trelloJsonService;
|
||||
/** @var BoardImportService|MockObject */
|
||||
private $boardImportService;
|
||||
public function setUp(): void {
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->boardMapper = $this->createMock(BoardMapper::class);
|
||||
$this->aclMapper = $this->createMock(AclMapper::class);
|
||||
$this->labelMapper = $this->createMock(LabelMapper::class);
|
||||
$this->stackMapper = $this->createMock(StackMapper::class);
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
$this->assignmentMapper = $this->createMock(AssignmentMapper::class);
|
||||
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
|
||||
$this->commentsManager = $this->createMock(ICommentsManager::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->boardImportService = new BoardImportService(
|
||||
$this->userManager,
|
||||
$this->boardMapper,
|
||||
$this->aclMapper,
|
||||
$this->labelMapper,
|
||||
$this->stackMapper,
|
||||
$this->assignmentMapper,
|
||||
$this->attachmentMapper,
|
||||
$this->cardMapper,
|
||||
$this->commentsManager,
|
||||
$this->eventDispatcher
|
||||
);
|
||||
|
||||
$this->boardImportService->setSystem('trelloJson');
|
||||
|
||||
$this->eventDispatcher
|
||||
->method('dispatchTyped')
|
||||
->willReturnCallback(function (BoardImportGetAllowedEvent $event) {
|
||||
$event->getService()->addAllowedImportSystem([
|
||||
'name' => TrelloJsonService::$name,
|
||||
'class' => TrelloJsonService::class,
|
||||
'internalName' => 'trelloJson'
|
||||
]);
|
||||
});
|
||||
|
||||
$data = json_decode(file_get_contents(__DIR__ . '/../../../data/data-trelloJson.json'));
|
||||
$this->boardImportService->setData($data);
|
||||
|
||||
$configFile = __DIR__ . '/../../../data/config-trelloJson.json';
|
||||
$configInstance = json_decode(file_get_contents($configFile));
|
||||
$this->boardImportService->setConfigInstance($configInstance);
|
||||
|
||||
$this->trelloJsonService = $this->createMock(TrelloJsonService::class);
|
||||
$this->trelloJsonService
|
||||
->method('getJsonSchemaPath')
|
||||
->willReturn($configFile);
|
||||
$this->boardImportService->setImportSystem($this->trelloJsonService);
|
||||
|
||||
$owner = $this->createMock(IUser::class);
|
||||
$owner
|
||||
->method('getUID')
|
||||
->willReturn('admin');
|
||||
|
||||
$johndoe = $this->createMock(IUser::class);
|
||||
$johndoe
|
||||
->method('getUID')
|
||||
->willReturn('johndoe');
|
||||
$this->userManager
|
||||
->method('get')
|
||||
->withConsecutive(
|
||||
['admin'],
|
||||
['johndoe']
|
||||
)
|
||||
->willReturnonConsecutiveCalls(
|
||||
$owner,
|
||||
$johndoe
|
||||
);
|
||||
}
|
||||
|
||||
public function testImportSuccess() {
|
||||
$this->boardMapper
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
|
||||
$this->trelloJsonService
|
||||
->method('getAclList')
|
||||
->willReturn([new Acl()]);
|
||||
$this->aclMapper
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
|
||||
$this->trelloJsonService
|
||||
->method('getLabels')
|
||||
->willReturn([new Label()]);
|
||||
$this->labelMapper
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
|
||||
$this->trelloJsonService
|
||||
->method('getStacks')
|
||||
->willReturn([new Stack()]);
|
||||
$this->stackMapper
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
|
||||
$this->trelloJsonService
|
||||
->method('getCards')
|
||||
->willReturn([new Card()]);
|
||||
$this->cardMapper
|
||||
->expects($this->any())
|
||||
->method('insert');
|
||||
|
||||
$this->trelloJsonService
|
||||
->method('getComments')
|
||||
->willReturn([
|
||||
'fakecardid' => [new Comment()]
|
||||
]);
|
||||
$this->commentsManager
|
||||
->expects($this->once())
|
||||
->method('save');
|
||||
|
||||
$this->trelloJsonService
|
||||
->method('getCardAssignments')
|
||||
->willReturn([
|
||||
'fakecardid' => [new Assignment()]
|
||||
]);
|
||||
$this->assignmentMapper
|
||||
->expects($this->once())
|
||||
->method('insert');
|
||||
|
||||
$actual = $this->boardImportService->import();
|
||||
|
||||
$this->assertNull($actual);
|
||||
}
|
||||
}
|
||||
151
tests/unit/Service/Importer/Systems/TrelloJsonServiceTest.php
Normal file
151
tests/unit/Service/Importer/Systems/TrelloJsonServiceTest.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Deck\Service\Importer\Systems;
|
||||
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class TrelloJsonServiceTest extends \Test\TestCase {
|
||||
/** @var TrelloJsonService */
|
||||
private $service;
|
||||
/** @var IURLGenerator|MockObject */
|
||||
private $urlGenerator;
|
||||
/** @var IUserManager|MockObject */
|
||||
private $userManager;
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
public function setUp(): void {
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->service = new TrelloJsonService(
|
||||
$this->userManager,
|
||||
$this->urlGenerator,
|
||||
$this->l10n
|
||||
);
|
||||
}
|
||||
|
||||
public function testValidateUsersWithoutUsers() {
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$this->service->setImportService($importService);
|
||||
$actual = $this->service->validateUsers();
|
||||
$this->assertNull($actual);
|
||||
}
|
||||
|
||||
public function testValidateUsersWithInvalidUser() {
|
||||
$this->expectErrorMessage('Trello user trello_user not found in property "members" of json data');
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$importService
|
||||
->method('getConfig')
|
||||
->willReturn([
|
||||
'trello_user' => 'nextcloud_user'
|
||||
]);
|
||||
$importService
|
||||
->method('getData')
|
||||
->willReturn(json_decode('{"members": [{"username": "othre_trello_user"}]}'));
|
||||
$this->service->setImportService($importService);
|
||||
$actual = $this->service->validateUsers();
|
||||
$this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual);
|
||||
}
|
||||
|
||||
public function testValidateUsersWithNotStringNextcloud() {
|
||||
$this->expectErrorMessage('User on setting uidRelation is invalid');
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$importService
|
||||
->method('getConfig')
|
||||
->willReturn([
|
||||
'trello_user' => []
|
||||
]);
|
||||
$importService
|
||||
->method('getData')
|
||||
->willReturn(json_decode('{"members": [{"username": "trello_user"}]}'));
|
||||
$this->service->setImportService($importService);
|
||||
$actual = $this->service->validateUsers();
|
||||
$this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual);
|
||||
}
|
||||
|
||||
public function testValidateUsersWithNotFoundUser() {
|
||||
$this->expectErrorMessage('User on setting uidRelation not found: nextcloud_user');
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$importService
|
||||
->method('getConfig')
|
||||
->willReturn(json_decode('{"trello_user": "nextcloud_user"}'));
|
||||
$importService
|
||||
->method('getData')
|
||||
->willReturn(json_decode('{"members": [{"username": "trello_user"}]}'));
|
||||
$this->service->setImportService($importService);
|
||||
$actual = $this->service->validateUsers();
|
||||
$this->assertInstanceOf(BoardImportTrelloJsonService::class, $actual);
|
||||
}
|
||||
|
||||
public function testValidateUsersWithValidUsers() {
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$importService
|
||||
->method('getConfig')
|
||||
->willReturn(json_decode('{"trello_user": "nextcloud_user"}'));
|
||||
$importService
|
||||
->method('getData')
|
||||
->willReturn(json_decode('{"members": [{"id": "fakeid", "username": "trello_user"}]}'));
|
||||
$fakeUser = $this->createMock(IUser::class);
|
||||
$this->userManager
|
||||
->method('get')
|
||||
->with('nextcloud_user')
|
||||
->willReturn($fakeUser);
|
||||
$this->service->setImportService($importService);
|
||||
$actual = $this->service->validateUsers();
|
||||
$this->assertNull($actual);
|
||||
}
|
||||
|
||||
public function testGetBoardWithNoName() {
|
||||
$this->expectErrorMessage('Invalid name of board');
|
||||
$importService = $this->createMock(BoardImportService::class);
|
||||
$this->service->setImportService($importService);
|
||||
$this->service->getBoard();
|
||||
}
|
||||
|
||||
public function testGetBoardWithSuccess() {
|
||||
$importService = \OC::$server->get(BoardImportService::class);
|
||||
|
||||
$data = json_decode(file_get_contents(__DIR__ . '/../../../../data/data-trelloJson.json'));
|
||||
$importService->setData($data);
|
||||
|
||||
$configInstance = json_decode(file_get_contents(__DIR__ . '/../../../../data/config-trelloJson.json'));
|
||||
$importService->setConfigInstance($configInstance);
|
||||
|
||||
$owner = $this->createMock(IUser::class);
|
||||
$owner
|
||||
->method('getUID')
|
||||
->willReturn('owner');
|
||||
$importService->setConfig('owner', $owner);
|
||||
|
||||
$this->service->setImportService($importService);
|
||||
$actual = $this->service->getBoard();
|
||||
$this->assertEquals('Test Board Name', $actual->getTitle());
|
||||
$this->assertEquals('owner', $actual->getOwner());
|
||||
$this->assertEquals('0800fd', $actual->getColor());
|
||||
}
|
||||
}
|
||||
81
tests/unit/controller/BoardImportApiControllerTest.php
Normal file
81
tests/unit/controller/BoardImportApiControllerTest.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @author Vitor Mattos <vitor@php.rio>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IRequest;
|
||||
use OCA\Deck\Service\Importer\BoardImportService;
|
||||
|
||||
class BoardImportApiControllerTest extends \Test\TestCase {
|
||||
private $appName = 'deck';
|
||||
private $userId = 'admin';
|
||||
/** @var BoardImportApiController */
|
||||
private $controller;
|
||||
/** @var BoardImportService|MockObject */
|
||||
private $boardImportService;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->boardImportService = $this->createMock(BoardImportService::class);
|
||||
|
||||
$this->controller = new BoardImportApiController(
|
||||
$this->appName,
|
||||
$this->request,
|
||||
$this->boardImportService,
|
||||
$this->userId
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetAllowedSystems() {
|
||||
$allowedSystems = [
|
||||
[
|
||||
'name' => '',
|
||||
'class' => '',
|
||||
'internalName' => 'trelloJson'
|
||||
]
|
||||
];
|
||||
$this->boardImportService
|
||||
->method('getAllowedImportSystems')
|
||||
->willReturn($allowedSystems);
|
||||
$actual = $this->controller->getAllowedSystems();
|
||||
$expected = new DataResponse($allowedSystems, HTTP::STATUS_OK);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testImport() {
|
||||
$system = 'trelloJson';
|
||||
$config = [
|
||||
'owner' => 'test'
|
||||
];
|
||||
$data = [
|
||||
'name' => 'test'
|
||||
];
|
||||
$actual = $this->controller->import($system, $config, $data);
|
||||
$board = $this->createMock(Board::class);
|
||||
$this->assertInstanceOf(Board::class, $board);
|
||||
$this->assertEquals(HTTP::STATUS_OK, $actual->getStatus());
|
||||
}
|
||||
}
|
||||
@@ -98,11 +98,11 @@ class CardControllerTest extends TestCase {
|
||||
}
|
||||
public function testAssignLabel() {
|
||||
$this->cardService->expects($this->once())->method('assignLabel');
|
||||
$this->controller->assignLabel(1,2);
|
||||
$this->controller->assignLabel(1, 2);
|
||||
}
|
||||
public function testRemoveLabel() {
|
||||
$this->cardService->expects($this->once())->method('removeLabel');
|
||||
$this->controller->removeLabel(1,2);
|
||||
$this->controller->removeLabel(1, 2);
|
||||
}
|
||||
|
||||
public function testReorder() {
|
||||
|
||||
@@ -24,56 +24,32 @@
|
||||
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Service\CardService;
|
||||
use OCA\Deck\Service\ConfigService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PageControllerTest extends TestCase {
|
||||
class PageControllerTest extends \Test\TestCase {
|
||||
private $controller;
|
||||
private $request;
|
||||
private $l10n;
|
||||
private $permissionService;
|
||||
private $initialState;
|
||||
private $configService;
|
||||
private $eventDispatcher;
|
||||
/**
|
||||
* @var mixed|CardMapper|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $cardMapper;
|
||||
/**
|
||||
* @var mixed|IURLGenerator|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $urlGenerator;
|
||||
/**
|
||||
* @var mixed|CardService|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $cardService;
|
||||
|
||||
public function setUp(): void {
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->configService = $this->createMock(ConfigService::class);
|
||||
$this->initialState = $this->createMock(IInitialStateService::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->cardService = $this->createMock(CardService::class);
|
||||
|
||||
$this->controller = new PageController(
|
||||
'deck',
|
||||
$this->request,
|
||||
$this->permissionService,
|
||||
$this->initialState,
|
||||
$this->configService,
|
||||
$this->eventDispatcher,
|
||||
$this->cardMapper,
|
||||
$this->urlGenerator,
|
||||
$this->cardService
|
||||
'deck', $this->request, $this->permissionService, $this->initialState, $this->configService, $this->eventDispatcher
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user