mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 15:38:15 +00:00
request reviewer api (#2472)
This commit is contained in:
258
frontend/package-lock.json
generated
258
frontend/package-lock.json
generated
@@ -4,6 +4,92 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
|
||||
"integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz",
|
||||
"integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==",
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.10",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@emotion/babel-utils": {
|
||||
"version": "0.6.10",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.10.tgz",
|
||||
"integrity": "sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow==",
|
||||
"requires": {
|
||||
"@emotion/hash": "^0.6.6",
|
||||
"@emotion/memoize": "^0.6.6",
|
||||
"@emotion/serialize": "^0.9.1",
|
||||
"convert-source-map": "^1.5.1",
|
||||
"find-root": "^1.1.0",
|
||||
"source-map": "^0.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@emotion/hash": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz",
|
||||
"integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ=="
|
||||
},
|
||||
"@emotion/memoize": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
|
||||
"integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ=="
|
||||
},
|
||||
"@emotion/serialize": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.9.1.tgz",
|
||||
"integrity": "sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ==",
|
||||
"requires": {
|
||||
"@emotion/hash": "^0.6.6",
|
||||
"@emotion/memoize": "^0.6.6",
|
||||
"@emotion/unitless": "^0.6.7",
|
||||
"@emotion/utils": "^0.8.2"
|
||||
}
|
||||
},
|
||||
"@emotion/stylis": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz",
|
||||
"integrity": "sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ=="
|
||||
},
|
||||
"@emotion/unitless": {
|
||||
"version": "0.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.7.tgz",
|
||||
"integrity": "sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg=="
|
||||
},
|
||||
"@emotion/utils": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz",
|
||||
"integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
|
||||
},
|
||||
"@reach/router": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@reach/router/-/router-1.2.1.tgz",
|
||||
@@ -172,6 +258,11 @@
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
|
||||
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
|
||||
@@ -869,6 +960,32 @@
|
||||
"babel-types": "^6.26.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-emotion": {
|
||||
"version": "9.2.11",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz",
|
||||
"integrity": "sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ==",
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"@emotion/babel-utils": "^0.6.4",
|
||||
"@emotion/hash": "^0.6.2",
|
||||
"@emotion/memoize": "^0.6.1",
|
||||
"@emotion/stylis": "^0.7.0",
|
||||
"babel-plugin-macros": "^2.0.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"convert-source-map": "^1.5.0",
|
||||
"find-root": "^1.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"source-map": "^0.5.7",
|
||||
"touch": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-plugin-istanbul": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz",
|
||||
@@ -884,6 +1001,58 @@
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz",
|
||||
"integrity": "sha1-r+3IU70/jcNUjqZx++adA8wsF2c="
|
||||
},
|
||||
"babel-plugin-macros": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.4.2.tgz",
|
||||
"integrity": "sha512-NBVpEWN4OQ/bHnu1fyDaAaTPAjnhXCEPqr1RwqxrU7b6tZ2hypp+zX4hlNfmVGfClD5c3Sl6Hfj5TJNF5VG5aA==",
|
||||
"requires": {
|
||||
"cosmiconfig": "^5.0.5",
|
||||
"resolve": "^1.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cosmiconfig": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz",
|
||||
"integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==",
|
||||
"requires": {
|
||||
"is-directory": "^0.3.1",
|
||||
"js-yaml": "^3.9.0",
|
||||
"parse-json": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
|
||||
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
|
||||
"requires": {
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-better-errors": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
|
||||
"integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
|
||||
"requires": {
|
||||
"path-parse": "^1.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-plugin-react-transform": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.2.tgz",
|
||||
@@ -962,8 +1131,7 @@
|
||||
"babel-plugin-syntax-jsx": {
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
|
||||
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=",
|
||||
"dev": true
|
||||
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
|
||||
},
|
||||
"babel-plugin-syntax-object-rest-spread": {
|
||||
"version": "6.13.0",
|
||||
@@ -2557,6 +2725,20 @@
|
||||
"elliptic": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"create-emotion": {
|
||||
"version": "9.2.12",
|
||||
"resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.12.tgz",
|
||||
"integrity": "sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA==",
|
||||
"requires": {
|
||||
"@emotion/hash": "^0.6.2",
|
||||
"@emotion/memoize": "^0.6.1",
|
||||
"@emotion/stylis": "^0.7.0",
|
||||
"@emotion/unitless": "^0.6.2",
|
||||
"csstype": "^2.5.2",
|
||||
"stylis": "^3.5.0",
|
||||
"stylis-rule-sheet": "^0.0.10"
|
||||
}
|
||||
},
|
||||
"create-error-class": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
|
||||
@@ -2860,6 +3042,11 @@
|
||||
"cssom": "0.3.x"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "2.5.7",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz",
|
||||
"integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw=="
|
||||
},
|
||||
"currently-unhandled": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||
@@ -3240,6 +3427,15 @@
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
|
||||
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
|
||||
},
|
||||
"emotion": {
|
||||
"version": "9.2.12",
|
||||
"resolved": "https://registry.npmjs.org/emotion/-/emotion-9.2.12.tgz",
|
||||
"integrity": "sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ==",
|
||||
"requires": {
|
||||
"babel-plugin-emotion": "^9.2.11",
|
||||
"create-emotion": "^9.2.12"
|
||||
}
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
@@ -4185,6 +4381,11 @@
|
||||
"pkg-dir": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
|
||||
},
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
@@ -6672,6 +6873,11 @@
|
||||
"integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==",
|
||||
"dev": true
|
||||
},
|
||||
"json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
@@ -7426,6 +7632,14 @@
|
||||
"which": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
|
||||
"integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
|
||||
"requires": {
|
||||
"abbrev": "1"
|
||||
}
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
||||
@@ -9400,6 +9614,14 @@
|
||||
"resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz",
|
||||
"integrity": "sha1-Aj1vObsVyXwHHp5g0A0TbqxfoLQ="
|
||||
},
|
||||
"react-input-autosize": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz",
|
||||
"integrity": "sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
@@ -9437,6 +9659,20 @@
|
||||
"babel-runtime": "^6.23.0"
|
||||
}
|
||||
},
|
||||
"react-select": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-2.1.1.tgz",
|
||||
"integrity": "sha512-ukie2LJStNfJEJ7wtqA+crAfzYpkpPr86urvmJGisECwsWJob9boCM4zjmKCi5QR7G8uY9+v7ZoliJpeCz/4xw==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.5",
|
||||
"emotion": "^9.1.2",
|
||||
"memoize-one": "^4.0.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"raf": "^3.4.0",
|
||||
"react-input-autosize": "^2.2.1",
|
||||
"react-transition-group": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"react-transform-catch-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-transform-catch-errors/-/react-transform-catch-errors-1.0.2.tgz",
|
||||
@@ -10879,6 +11115,16 @@
|
||||
"schema-utils": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"stylis": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.3.tgz",
|
||||
"integrity": "sha512-TxU0aAscJghF9I3V9q601xcK3Uw1JbXvpsBGj/HULqexKOKlOEzzlIpLFRbKkCK990ccuxfXUqmPbIIo7Fq/cQ=="
|
||||
},
|
||||
"stylis-rule-sheet": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
|
||||
"integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
|
||||
@@ -11092,6 +11338,14 @@
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.6.tgz",
|
||||
"integrity": "sha1-wxdI5V0hDv/AD9zcfW5o19e7nOw="
|
||||
},
|
||||
"touch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-2.0.2.tgz",
|
||||
"integrity": "sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A==",
|
||||
"requires": {
|
||||
"nopt": "~1.0.10"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
||||
|
@@ -24,6 +24,7 @@
|
||||
"react-cookies": "^0.1.0",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-moment": "^0.7.9",
|
||||
"react-select": "^2.1.1",
|
||||
"reactstrap": "^6.4.0",
|
||||
"seafile-js": "^0.2.31",
|
||||
"seafile-ui": "^0.1.10",
|
||||
|
138
frontend/src/components/dialog/add-reviewer-dialog.js
Normal file
138
frontend/src/components/dialog/add-reviewer-dialog.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import React from 'react';
|
||||
import AsyncSelect from 'react-select/lib/Async';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import '../../css/add-reviewer-dialog.css';
|
||||
|
||||
const propTypes = {
|
||||
showReviewerDialog: PropTypes.bool.isRequired,
|
||||
reviewID: PropTypes.string.isRequired,
|
||||
toggleAddReviewerDialog: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class AddReviewerDialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
reviewers: [],
|
||||
selectedOption: null,
|
||||
errorMsg: [],
|
||||
};
|
||||
this.Options = [];
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
seafileAPI.listReviewers(this.props.reviewID).then((res) => {
|
||||
this.setState({
|
||||
reviewers: res.data.reviewers
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({
|
||||
selectedOption: option,
|
||||
});
|
||||
this.Options = [];
|
||||
}
|
||||
|
||||
loadOptions = (value, callback) => {
|
||||
if (value.trim().length > 0) {
|
||||
this.Options = [];
|
||||
let that = this;
|
||||
seafileAPI.searchUsers(value.trim()).then((res) => {
|
||||
for (let i = 0 ; i < res.data.users.length; i++) {
|
||||
let obj = {};
|
||||
obj.value = res.data.users[i].name;
|
||||
obj.email = res.data.users[i].email;
|
||||
obj.label =
|
||||
<div>
|
||||
<img src={res.data.users[i].avatar_url} className="avatar reviewer-select-avatar" alt=""/>
|
||||
<span className='reviewer-select-name'>{res.data.users[i].name}</span>
|
||||
</div>;
|
||||
that.Options.push(obj);
|
||||
}
|
||||
callback(this.Options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addReviewers = () => {
|
||||
if (this.state.selectedOption.length > 0 ) {
|
||||
let reviewers = [];
|
||||
for (let i = 0; i < this.state.selectedOption.length; i ++) {
|
||||
reviewers[i] = this.state.selectedOption[i].email;
|
||||
}
|
||||
seafileAPI.addReviewers(this.props.reviewID, reviewers).then((res) => {
|
||||
if (res.data.failed.length > 0) {
|
||||
let errorMsg = [];
|
||||
for (let i = 0 ; i < res.data.failed.length ; i++) {
|
||||
errorMsg[i] = res.data.failed[i];
|
||||
}
|
||||
this.setState({
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
let that = this;
|
||||
setTimeout(() => {
|
||||
that.setState({
|
||||
errorMsg: []
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
this.setState({
|
||||
selectedOption: null
|
||||
});
|
||||
if (res.data.success.length > 0) {
|
||||
this.listReviewers();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal isOpen={this.props.showReviewerDialog}>
|
||||
<ModalHeader>{gettext('Request a review')}</ModalHeader>
|
||||
<ModalBody >
|
||||
<p>{gettext('Add new reviewer')}</p>
|
||||
<AsyncSelect
|
||||
className='reviewer-select' isMulti isFocused
|
||||
loadOptions={this.loadOptions}
|
||||
placeholder={gettext('Please enter 1 or more character')}
|
||||
onChange={this.handleSelectChange}
|
||||
/>
|
||||
{this.state.errorMsg.length > 0 &&
|
||||
this.state.errorMsg.map((item, index = 0, arr) => {
|
||||
return (
|
||||
<p className="error" key={index}>{this.state.errorMsg[index].email}
|
||||
{':'}{this.state.errorMsg[index].error_msg}</p>
|
||||
);
|
||||
})
|
||||
}
|
||||
{ this.state.reviewers.length > 0 &&
|
||||
this.state.reviewers.map((item, index = 0, arr) => {
|
||||
return (
|
||||
<div className="reviewer-select-info" key={index}>
|
||||
<img className="avatar reviewer-select-avatar" src={item.avatar_url} alt=""/>
|
||||
<span className="reviewer-select-name">{item.user_name}</span>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="primary" onClick={this.addReviewers}>{gettext('Submit')}</Button>
|
||||
<Button color="secondary" onClick={this.props.toggleAddReviewerDialog}>
|
||||
{gettext('Cancel')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddReviewerDialog.propTypes = propTypes;
|
||||
|
||||
export default AddReviewerDialog;
|
13
frontend/src/css/add-reviewer-dialog.css
Normal file
13
frontend/src/css/add-reviewer-dialog.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.reviewer-select-info {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.reviewer-select-avatar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.reviewer-select-name {
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
}
|
||||
.reviewer-select>div>div:nth-child(2) {
|
||||
display: none;
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header .common-list-btn {
|
||||
.header .common-list-btn, .header .add-reviewer-btn {
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import Loading from './components/loading';
|
||||
import Toast from './components/toast';
|
||||
import ReviewComments from './components/review-list-view/review-comments';
|
||||
import { Button, Tooltip } from 'reactstrap';
|
||||
import AddReviewerDialog from './components/dialog/add-reviewer-dialog.js';
|
||||
|
||||
import 'seafile-ui';
|
||||
import './assets/css/fa-solid.css';
|
||||
@@ -37,6 +38,7 @@ class DraftReview extends React.Component {
|
||||
commentWidth: 30,
|
||||
isShowDiff: true,
|
||||
showDiffTip: false,
|
||||
showReviewerDialog: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +155,12 @@ class DraftReview extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
toggleAddReviewerDialog = () => {
|
||||
this.setState({
|
||||
showReviewerDialog: !this.state.showReviewerDialog
|
||||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.getCommentsNumber();
|
||||
}
|
||||
@@ -187,6 +195,8 @@ class DraftReview extends React.Component {
|
||||
target="toggle-diff" toggle={this.toggleDiffTip}>
|
||||
{gettext('View diff')}</Tooltip>
|
||||
</div>
|
||||
<button className="btn btn-primary add-reviewer-btn" onClick={this.toggleAddReviewerDialog}>
|
||||
{gettext('Add reviewer')}</button>
|
||||
<button className="btn btn-icon btn-secondary btn-active common-list-btn"
|
||||
id="commentsNumber" type="button" data-active="false"
|
||||
onMouseDown={this.toggleCommentList}>
|
||||
@@ -244,6 +254,13 @@ class DraftReview extends React.Component {
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.showReviewerDialog &&
|
||||
<AddReviewerDialog
|
||||
showReviewerDialog={this.state.showReviewerDialog}
|
||||
toggleAddReviewerDialog={this.toggleAddReviewerDialog}
|
||||
reviewID={reviewID}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -41,5 +41,6 @@ export const draftFileName = window.draftReview ? window.draftReview.config.draf
|
||||
export const reviewID = window.draftReview ? window.draftReview.config.reviewID : '';
|
||||
export const draftID = window.draftReview ? window.draftReview.config.draftID : '';
|
||||
export const opStatus = window.draftReview ? window.draftReview.config.opStatus : '';
|
||||
export const reviewPerm = window.draftReview ? window.draftReview.config.perm : '';
|
||||
export const publishFileVersion = window.draftReview ? window.draftReview.config.publishFileVersion : '';
|
||||
export const originFileVersion = window.draftReview ? window.draftReview.config.originFileVersion : '';
|
||||
|
118
seahub/api2/endpoints/draft_review_reviewer.py
Normal file
118
seahub/api2/endpoints/draft_review_reviewer.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from seaserv import seafile_api
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error, user_to_dict
|
||||
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.base.accounts import User
|
||||
from seahub.views import check_folder_permission
|
||||
from seahub.utils import is_valid_username
|
||||
from seahub.drafts.models import DraftReview, ReviewReviewer
|
||||
from seahub.drafts.signals import request_reviewer_successful
|
||||
|
||||
|
||||
class DraftReviewReviewerView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, )
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
def get(self, request, pk, format=None):
|
||||
try:
|
||||
r = DraftReview.objects.get(pk=pk)
|
||||
except DraftReview.DoesNotExist:
|
||||
return api_error(status.HTTP_404_NOT_FOUND,
|
||||
'Review %s not found' % pk)
|
||||
|
||||
# format user result
|
||||
try:
|
||||
avatar_size = int(request.GET.get('avatar_size', 32))
|
||||
except ValueError:
|
||||
avatar_size = 32
|
||||
|
||||
# get reviewer list
|
||||
reviewers = []
|
||||
for x in r.reviewreviewer_set.all():
|
||||
reviewer = user_to_dict(x.reviewer, request=request, avatar_size=avatar_size)
|
||||
reviewers.append(reviewer)
|
||||
|
||||
return Response({'reviewers': reviewers})
|
||||
|
||||
def post(self, request, pk, format=None):
|
||||
"""Create a draft review
|
||||
"""
|
||||
try:
|
||||
r = DraftReview.objects.get(pk=pk)
|
||||
except DraftReview.DoesNotExist:
|
||||
return api_error(status.HTTP_404_NOT_FOUND,
|
||||
'Review %s not found' % pk)
|
||||
|
||||
result = {}
|
||||
result['failed'] = []
|
||||
result['success'] = []
|
||||
|
||||
reviewers = request.data.getlist('reviewer')
|
||||
for reviewer in reviewers:
|
||||
if not is_valid_username(reviewer):
|
||||
result['failed'].append({
|
||||
'email': reviewer,
|
||||
'error_msg': _(u'username invalid.')
|
||||
})
|
||||
continue
|
||||
|
||||
try:
|
||||
User.objects.get(email=reviewer)
|
||||
except User.DoesNotExist:
|
||||
result['failed'].append({
|
||||
'email': reviewer,
|
||||
'error_msg': _(u'User %s not found.') % reviewer
|
||||
})
|
||||
continue
|
||||
|
||||
# can't share to owner
|
||||
if reviewer == r.creator:
|
||||
error_msg = _(u'Draft review can not be asked owner to review.')
|
||||
result['failed'].append({
|
||||
'email': reviewer,
|
||||
'error_msg': error_msg
|
||||
})
|
||||
continue
|
||||
|
||||
# check perm
|
||||
if seafile_api.check_permission_by_path(r.origin_repo_id, r.origin_file_path, reviewer) != 'rw':
|
||||
error_msg = _(u'Permission denied.')
|
||||
result['failed'].append({
|
||||
'email': reviewer,
|
||||
'error_msg': error_msg
|
||||
})
|
||||
continue
|
||||
|
||||
if ReviewReviewer.objects.filter(review_id=r, reviewer=reviewer):
|
||||
error_msg = _(u'Reviewer %s has existed.') % reviewer
|
||||
result['failed'].append({
|
||||
'email': reviewer,
|
||||
'error_msg': error_msg
|
||||
})
|
||||
continue
|
||||
|
||||
result['success'].append({
|
||||
"user_info": {
|
||||
"name": reviewer,
|
||||
"nickname": email2nickname(reviewer)
|
||||
}
|
||||
})
|
||||
|
||||
ReviewReviewer.objects.add(reviewer, r)
|
||||
|
||||
request_reviewer_successful.send(sender=None, from_user=r.creator,
|
||||
to_user=reviewer, review_id=r.id)
|
||||
|
||||
return Response(result)
|
@@ -13,9 +13,12 @@ from seaserv import seafile_api
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.constants import PERMISSION_READ_WRITE
|
||||
from seahub.views import check_folder_permission
|
||||
|
||||
from seahub.drafts.models import Draft, DraftReview, DraftReviewExist, \
|
||||
DraftFileConflict
|
||||
DraftFileConflict, ReviewReviewer
|
||||
from seahub.drafts.signals import update_review_successful
|
||||
|
||||
|
||||
class DraftReviewsView(APIView):
|
||||
@@ -28,10 +31,10 @@ class DraftReviewsView(APIView):
|
||||
"""
|
||||
username = request.user.username
|
||||
data = [x.to_dict() for x in DraftReview.objects.filter(creator=username)]
|
||||
data += [x.review_id.to_dict() for x in ReviewReviewer.objects.filter(reviewer=username)]
|
||||
|
||||
return Response({'data': data})
|
||||
|
||||
|
||||
def post(self, request, format=None):
|
||||
"""Create a draft review
|
||||
"""
|
||||
@@ -74,10 +77,22 @@ class DraftReviewView(APIView):
|
||||
return api_error(status.HTTP_404_NOT_FOUND,
|
||||
'Review %s not found' % pk)
|
||||
|
||||
perm = check_folder_permission(request, r.origin_repo_id, r.origin_file_path)
|
||||
|
||||
# Review owner and 'rw' perm on the original file to close review
|
||||
if st == 'closed':
|
||||
if perm != PERMISSION_READ_WRITE or request.user.username != r.creator:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
r.status = st
|
||||
r.save()
|
||||
|
||||
# Only 'rw' perm on original file can publish review
|
||||
if st == 'finished':
|
||||
if perm != PERMISSION_READ_WRITE:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
try:
|
||||
d = Draft.objects.get(pk=r.draft_id_id)
|
||||
@@ -119,9 +134,26 @@ class DraftReviewView(APIView):
|
||||
# get draft published version
|
||||
file_id = seafile_api.get_file_id_by_path(r.origin_repo_id, origin_file_path)
|
||||
r.publish_file_version = file_id
|
||||
r.status = st
|
||||
r.save()
|
||||
d.delete()
|
||||
|
||||
reviewers = ReviewReviewer.objects.filter(review_id=r)
|
||||
# send notice to other reviewers if has
|
||||
if reviewers:
|
||||
for i in reviewers:
|
||||
# If it is a reviewer operation, exclude it.
|
||||
if i.reviewer == request.user.username:
|
||||
continue
|
||||
|
||||
update_review_successful.send(sender=None, from_user=request.user.username,
|
||||
to_user=i.reviewer, review_id=r.id, status=st)
|
||||
|
||||
# send notice to review owner
|
||||
if request.user.username != r.creator:
|
||||
update_review_successful.send(sender=None, from_user=request.user.username,
|
||||
to_user=r.creator, review_id=r.id, status=st)
|
||||
|
||||
result = r.to_dict()
|
||||
|
||||
return Response(result)
|
||||
|
@@ -21,7 +21,6 @@ from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.endpoints.utils import add_org_context
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.constants import PERMISSION_READ_WRITE
|
||||
from seahub.drafts.models import Draft, DraftFileExist, DraftFileConflict
|
||||
from seahub.views import check_folder_permission
|
||||
from seahub.utils import gen_file_get_url
|
||||
@@ -63,7 +62,7 @@ class DraftsView(APIView):
|
||||
|
||||
# perm check
|
||||
perm = check_folder_permission(request, repo.id, file_path)
|
||||
if perm != PERMISSION_READ_WRITE:
|
||||
if perm is None:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
|
31
seahub/drafts/migrations/0004_reviewreviewer.py
Normal file
31
seahub/drafts/migrations/0004_reviewreviewer.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.15 on 2018-10-23 08:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import seahub.base.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('drafts', '0003_reviewcomment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ReviewReviewer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('reviewer', seahub.base.fields.LowerCaseCharField(db_index=True, max_length=255)),
|
||||
('review_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='drafts.DraftReview')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at', '-updated_at'],
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
27
seahub/drafts/migrations/0005_auto_20181024_1009.py
Normal file
27
seahub/drafts/migrations/0005_auto_20181024_1009.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.15 on 2018-10-24 10:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('drafts', '0004_reviewreviewer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='reviewreviewer',
|
||||
options={},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='reviewreviewer',
|
||||
name='created_at',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='reviewreviewer',
|
||||
name='updated_at',
|
||||
),
|
||||
]
|
@@ -243,3 +243,27 @@ class ReviewComment(TimestampedModel):
|
||||
'resolved': self.resolved,
|
||||
'detail': self.detail,
|
||||
}
|
||||
|
||||
|
||||
class ReviewReviewerManager(models.Manager):
|
||||
def add(self, reviewer, review_id):
|
||||
review_reviewer = self.model(reviewer=reviewer, review_id=review_id)
|
||||
review_reviewer.save(using=self._db)
|
||||
|
||||
return review_reviewer
|
||||
|
||||
|
||||
class ReviewReviewer(models.Model):
|
||||
"""
|
||||
Model used to record review reviewer.
|
||||
"""
|
||||
reviewer = LowerCaseCharField(max_length=255, db_index=True)
|
||||
review_id = models.ForeignKey('DraftReview', on_delete=models.CASCADE)
|
||||
|
||||
objects = ReviewReviewerManager()
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'nickname': email2nickname(self.reviewer),
|
||||
'name': self.reviewer,
|
||||
}
|
||||
|
@@ -2,3 +2,5 @@
|
||||
import django.dispatch
|
||||
|
||||
comment_review_successful = django.dispatch.Signal(providing_args=["review", "comment", "author"])
|
||||
request_reviewer_successful = django.dispatch.Signal(providing_args=["from_user", "to_user", "review_id"])
|
||||
update_review_successful = django.dispatch.Signal(providing_args=["from_user", "to_user", "review_id", "status"])
|
||||
|
@@ -45,5 +45,6 @@ def review(request, pk):
|
||||
"draft_file_name": draft_file_name,
|
||||
"origin_file_version": d_r.origin_file_version,
|
||||
"publish_file_version": d_r.publish_file_version,
|
||||
"status": d_r.status
|
||||
"status": d_r.status,
|
||||
"permission": permission
|
||||
})
|
||||
|
@@ -54,6 +54,8 @@ MSG_TYPE_REPO_SHARE_TO_GROUP = 'repo_share_to_group'
|
||||
MSG_TYPE_USER_MESSAGE = 'user_message'
|
||||
MSG_TYPE_FILE_COMMENT = 'file_comment'
|
||||
MSG_TYPE_REVIEW_COMMENT = 'review_comment'
|
||||
MSG_TYPE_UPDATE_REVIEW = 'update_review'
|
||||
MSG_TYPE_REQUEST_REVIEWER = 'request_reviewer'
|
||||
MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'
|
||||
|
||||
USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_'
|
||||
@@ -98,6 +100,17 @@ def review_comment_msg_to_json(review_id, author, comment):
|
||||
'author': author,
|
||||
'comment': comment})
|
||||
|
||||
def request_reviewer_msg_to_json(review_id, from_user, to_user):
|
||||
return json.dumps({'review_id': review_id,
|
||||
'from_user': from_user,
|
||||
'to_user': to_user})
|
||||
|
||||
def update_review_msg_to_json(review_id, from_user, to_user, status):
|
||||
return json.dumps({'review_id': review_id,
|
||||
'from_user': from_user,
|
||||
'to_user': to_user,
|
||||
'status': status})
|
||||
|
||||
def guest_invitation_accepted_msg_to_json(invitation_id):
|
||||
return json.dumps({'invitation_id': invitation_id})
|
||||
|
||||
@@ -298,6 +311,16 @@ class UserNotificationManager(models.Manager):
|
||||
"""
|
||||
return self._add_user_notification(to_user, MSG_TYPE_REVIEW_COMMENT, detail)
|
||||
|
||||
def add_request_reviewer_msg(self, to_user, detail):
|
||||
"""Notify ``to_user`` that reviewer
|
||||
"""
|
||||
return self._add_user_notification(to_user, MSG_TYPE_REQUEST_REVIEWER, detail)
|
||||
|
||||
def add_update_review_msg(self, to_user, detail):
|
||||
"""Notify ``to_user`` that reviewer and owner
|
||||
"""
|
||||
return self._add_user_notification(to_user, MSG_TYPE_UPDATE_REVIEW, detail)
|
||||
|
||||
def add_guest_invitation_accepted_msg(self, to_user, detail):
|
||||
"""Nofity ``to_user`` that a guest has accpeted an invitation.
|
||||
"""
|
||||
@@ -399,6 +422,12 @@ class UserNotification(models.Model):
|
||||
def is_review_comment_msg(self):
|
||||
return self.msg_type == MSG_TYPE_REVIEW_COMMENT
|
||||
|
||||
def is_request_reviewer_msg(self):
|
||||
return self.msg_type == MSG_TYPE_REQUEST_REVIEWER
|
||||
|
||||
def is_update_review_msg(self):
|
||||
return self.msg_type == MSG_TYPE_UPDATE_REVIEW
|
||||
|
||||
def is_guest_invitation_accepted_msg(self):
|
||||
return self.msg_type == MSG_TYPE_GUEST_INVITATION_ACCEPTED
|
||||
|
||||
@@ -760,6 +789,49 @@ class UserNotification(models.Model):
|
||||
}
|
||||
return msg
|
||||
|
||||
def format_request_reviewer_msg(self):
|
||||
try:
|
||||
d = json.loads(self.detail)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return _(u"Internal error")
|
||||
|
||||
review_id = d['review_id']
|
||||
from_user = d['from_user']
|
||||
|
||||
msg = _("%(from_user)s has sent you a request for review <a href='%(file_url)s'>%(review_id)s</a>") % {
|
||||
'review_id': review_id,
|
||||
'file_url': reverse('drafts:review', args=[review_id]),
|
||||
'from_user': escape(email2nickname(from_user))
|
||||
}
|
||||
return msg
|
||||
|
||||
def format_update_review_msg(self):
|
||||
try:
|
||||
d = json.loads(self.detail)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return _(u"Internal error")
|
||||
|
||||
review_id = d['review_id']
|
||||
from_user = d['from_user']
|
||||
status = d['status']
|
||||
|
||||
if status == 'closed':
|
||||
msg = _("%(from_user)s has closed review <a href='%(file_url)s'>%(review_id)s</a>") % {
|
||||
'review_id': review_id,
|
||||
'file_url': reverse('drafts:review', args=[review_id]),
|
||||
'from_user': escape(email2nickname(from_user))
|
||||
}
|
||||
|
||||
if status == 'finished':
|
||||
msg = _("%(from_user)s has published review <a href='%(file_url)s'>%(review_id)s</a>") % {
|
||||
'review_id': review_id,
|
||||
'file_url': reverse('drafts:review', args=[review_id]),
|
||||
'from_user': escape(email2nickname(from_user))
|
||||
}
|
||||
return msg
|
||||
|
||||
def format_guest_invitation_accepted_msg(self):
|
||||
try:
|
||||
d = json.loads(self.detail)
|
||||
@@ -794,7 +866,8 @@ from seahub.group.signals import grpmsg_added, group_join_request, add_user_to_g
|
||||
from seahub.share.signals import share_repo_to_user_successful, \
|
||||
share_repo_to_group_successful
|
||||
from seahub.invitations.signals import accept_guest_invitation_successful
|
||||
from seahub.drafts.signals import comment_review_successful
|
||||
from seahub.drafts.signals import comment_review_successful, \
|
||||
request_reviewer_successful, update_review_successful
|
||||
|
||||
@receiver(upload_file_successful)
|
||||
def add_upload_file_msg_cb(sender, **kwargs):
|
||||
@@ -908,6 +981,26 @@ def comment_review_successful_cb(sender, **kwargs):
|
||||
detail = review_comment_msg_to_json(review.id, author, comment)
|
||||
UserNotification.objects.add_review_comment_msg(review.creator, detail)
|
||||
|
||||
@receiver(request_reviewer_successful)
|
||||
def requeset_reviewer_successful_cb(sender, **kwargs):
|
||||
from_user = kwargs['from_user']
|
||||
review_id = kwargs['review_id']
|
||||
to_user = kwargs['to_user']
|
||||
|
||||
detail = request_reviewer_msg_to_json(review_id, from_user, to_user)
|
||||
|
||||
UserNotification.objects.add_request_reviewer_msg(to_user, detail)
|
||||
|
||||
@receiver(update_review_successful)
|
||||
def update_review_successful_cb(sender, **kwargs):
|
||||
from_user = kwargs['from_user']
|
||||
review_id = kwargs['review_id']
|
||||
to_user = kwargs['to_user']
|
||||
status = kwargs['status']
|
||||
|
||||
detail = update_review_msg_to_json(review_id, from_user, to_user, status)
|
||||
|
||||
UserNotification.objects.add_update_review_msg(to_user, detail)
|
||||
|
||||
@receiver(accept_guest_invitation_successful)
|
||||
def accept_guest_invitation_successful_cb(sender, **kwargs):
|
||||
|
@@ -38,6 +38,12 @@
|
||||
{% elif notice.is_review_comment_msg %}
|
||||
<p class="brief">{{ notice.format_review_comment_msg|safe }}</p>
|
||||
|
||||
{% elif notice.is_update_review_msg %}
|
||||
<p class="brief">{{ notice.format_update_review_msg|safe }}</p>
|
||||
|
||||
{% elif notice.is_request_reviewer_msg %}
|
||||
<p class="brief">{{ notice.format_request_reviewer_msg|safe }}</p>
|
||||
|
||||
{% elif notice.is_guest_invitation_accepted_msg %}
|
||||
<p class="brief">{{ notice.format_guest_invitation_accepted_msg|safe }}</p>
|
||||
|
||||
|
@@ -192,5 +192,18 @@ def add_notice_from_info(notices):
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
elif notice.is_request_reviewer_msg():
|
||||
try:
|
||||
d = json.loads(notice.detail)
|
||||
notice.msg_from = d['from_user']
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
elif notice.is_update_review_msg():
|
||||
try:
|
||||
d = json.loads(notice.detail)
|
||||
notice.msg_from = d['from_user']
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
return notices
|
||||
|
@@ -13,6 +13,7 @@
|
||||
draftOriginRepoID: '{{ draft_origin_repo_id }}',
|
||||
draftFileName: '{{ draft_file_name }}',
|
||||
opStatus: '{{ status }}',
|
||||
perm: '{{ permission }}',
|
||||
publishFileVersion: '{{ publish_file_version }}',
|
||||
originFileVersion: '{{ origin_file_version }}',
|
||||
}
|
||||
|
@@ -40,6 +40,12 @@
|
||||
{% elif notice.is_review_comment_msg %}
|
||||
<p class="brief">{{ notice.format_review_comment_msg|safe }}</p>
|
||||
|
||||
{% elif notice.is_update_review_msg %}
|
||||
<p class="brief">{{ notice.format_update_review_msg|safe }}</p>
|
||||
|
||||
{% elif notice.is_request_reviewer_msg %}
|
||||
<p class="brief">{{ notice.format_request_reviewer_msg|safe }}</p>
|
||||
|
||||
{% elif notice.is_guest_invitation_accepted_msg %}
|
||||
<p class="brief">{{ notice.format_guest_invitation_accepted_msg|safe }}</p>
|
||||
|
||||
|
@@ -68,6 +68,7 @@ from seahub.api2.endpoints.user_avatar import UserAvatarView
|
||||
from seahub.api2.endpoints.wikis import WikisView, WikiView
|
||||
from seahub.api2.endpoints.drafts import DraftsView, DraftView
|
||||
from seahub.api2.endpoints.draft_reviews import DraftReviewsView, DraftReviewView
|
||||
from seahub.api2.endpoints.draft_review_reviewer import DraftReviewReviewerView
|
||||
from seahub.api2.endpoints.activities import ActivitiesView
|
||||
from seahub.api2.endpoints.wiki_pages import WikiPageView, WikiPagesView, WikiPagesDirView, WikiPageContentView
|
||||
from seahub.api2.endpoints.revision_tag import TaggedItemsView, TagNamesView
|
||||
@@ -340,6 +341,7 @@ urlpatterns = [
|
||||
## user::reviews
|
||||
url(r'^api/v2.1/reviews/$', DraftReviewsView.as_view(), name='api-v2.1-draft-reviews'),
|
||||
url(r'^api/v2.1/review/(?P<pk>\d+)/$', DraftReviewView.as_view(), name='api-v2.1-draft-review'),
|
||||
url(r'^api/v2.1/review/(?P<pk>\d+)/reviewer/$', DraftReviewReviewerView.as_view(), name='api-v2.1-draft-review-reviewer'),
|
||||
|
||||
## user::activities
|
||||
url(r'^api/v2.1/activities/$', ActivitiesView.as_view(), name='api-v2.1-acitvity'),
|
||||
|
Reference in New Issue
Block a user