Merged develop into master

This commit is contained in:
Ramon Wenger 2019-12-19 15:49:35 +00:00
commit 9e75f1ce67
31 changed files with 797 additions and 299 deletions

254
client/package-lock.json generated
View File

@ -2460,7 +2460,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -2492,7 +2492,7 @@
},
"onetime": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
@ -2757,12 +2757,6 @@
"@types/yargs": "^12.0.9"
}
},
"@types/async": {
"version": "2.0.49",
"resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.49.tgz",
"integrity": "sha512-Benr3i5odUkvpFkOpzGqrltGdbSs+EVCkEBGXbuR7uT0VzhXKIkhem6PDzHdx5EonA+rfbB3QvP6aDOw5+zp5Q==",
"optional": true
},
"@types/babel__core": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz",
@ -2921,11 +2915,6 @@
"@types/node": "*"
}
},
"@types/graphql": {
"version": "0.12.6",
"resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.12.6.tgz",
"integrity": "sha512-wXAVyLfkG1UMkKOdMijVWFky39+OD/41KftzqfX1Oejd0Gm6dOIKjCihSVECg6X7PHjftxXmfOKA/d1H79ZfvQ=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@ -2960,8 +2949,7 @@
"@types/node": {
"version": "12.7.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.1.tgz",
"integrity": "sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw==",
"dev": true
"integrity": "sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw=="
},
"@types/stack-utils": {
"version": "1.0.1",
@ -3011,6 +2999,23 @@
"lodash": "^4.17.4"
}
},
"@wry/context": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.4.tgz",
"integrity": "sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==",
"requires": {
"@types/node": ">=6",
"tslib": "^1.9.3"
}
},
"@wry/equality": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.9.tgz",
"integrity": "sha512-mB6ceGjpMGz1ZTza8HYnrPGos2mC6So4NhS1PtZ8s4Qt0K7fBiIGhpSxUbQmhwcSWE3no+bYxmI2OL6KuXYmoQ==",
"requires": {
"tslib": "^1.9.3"
}
},
"abab": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
@ -3175,79 +3180,109 @@
}
},
"apollo-cache": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.14.tgz",
"integrity": "sha512-Zmo9nVqpWFogki2QyulX6Xx6KYXMyYWX74grwgsYYUOukl4pIAdtYyK8e874o0QDgzSOq5AYPXjtfkoVpqhCRw==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.4.tgz",
"integrity": "sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA==",
"requires": {
"apollo-utilities": "^1.0.18"
"apollo-utilities": "^1.3.3",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"apollo-cache-inmemory": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.2.7.tgz",
"integrity": "sha512-ikL3hWsd1DejiZSAuiGnX6TG3cKAZmkMTZZfNZggp9vcTa47kfPqms/pX0F4iajCJP/p7/AllhbpsQ3zVMOZGg==",
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz",
"integrity": "sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA==",
"requires": {
"apollo-cache": "^1.1.14",
"apollo-utilities": "^1.0.18",
"graphql-anywhere": "^4.1.16"
"apollo-cache": "^1.3.4",
"apollo-utilities": "^1.3.3",
"optimism": "^0.10.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"apollo-client": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.3.8.tgz",
"integrity": "sha512-X5wsBD1be1P/mScGsH5H+2hIE8d78WAfqOvFvBpP+C+jzJ9387uHLyFmYYMLRRqDQ3ihjI4iSID7KEOW2gyCcQ==",
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.8.tgz",
"integrity": "sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw==",
"requires": {
"@types/async": "2.0.49",
"@types/zen-observable": "^0.8.0",
"apollo-cache": "^1.1.14",
"apollo-cache": "1.3.4",
"apollo-link": "^1.0.0",
"apollo-link-dedup": "^1.0.0",
"apollo-utilities": "^1.0.18",
"apollo-utilities": "1.3.3",
"symbol-observable": "^1.0.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0",
"zen-observable": "^0.8.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"apollo-link": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.2.tgz",
"integrity": "sha512-Uk/BC09dm61DZRDSu52nGq0nFhq7mcBPTjy5EEH1eunJndtCaNXQhQz/BjkI2NdrfGI+B+i5he6YSoRBhYizdw==",
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.13.tgz",
"integrity": "sha512-+iBMcYeevMm1JpYgwDEIDt/y0BB7VWyvlm/7x+TIPNLHCTCMgcEgDuW5kH86iQZWo0I7mNwQiTOz+/3ShPFmBw==",
"requires": {
"@types/graphql": "0.12.6",
"apollo-utilities": "^1.0.0",
"zen-observable-ts": "^0.8.9"
}
},
"apollo-link-dedup": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/apollo-link-dedup/-/apollo-link-dedup-1.0.9.tgz",
"integrity": "sha512-RbuEKpmSHVMtoREMPh2wUFTeh65q+0XPVeqgaOP/rGEAfvLyOMvX0vT2nVaejMohoMxuUnfZwpldXaDFWnlVbg==",
"requires": {
"apollo-link": "^1.2.2"
"apollo-utilities": "^1.3.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3",
"zen-observable-ts": "^0.8.20"
}
},
"apollo-link-http": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.4.tgz",
"integrity": "sha512-e9Ng3HfnW00Mh3TI6DhNRfozmzQOtKgdi+qUAsHBOEcTP0PTAmb+9XpeyEEOueLyO0GXhB92HUCIhzrWMXgwyg==",
"version": "1.5.16",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.16.tgz",
"integrity": "sha512-IA3xA/OcrOzINRZEECI6IdhRp/Twom5X5L9jMehfzEo2AXdeRwAMlH5LuvTZHgKD8V1MBnXdM6YXawXkTDSmJw==",
"requires": {
"apollo-link": "^1.2.2",
"apollo-link-http-common": "^0.2.4"
"apollo-link": "^1.2.13",
"apollo-link-http-common": "^0.2.15",
"tslib": "^1.9.3"
}
},
"apollo-link-http-common": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.4.tgz",
"integrity": "sha512-4j6o6WoXuSPen9xh4NBaX8/vL98X1xY2cYzUEK1F8SzvHe2oFONfxJBTekwU8hnvapcuq8Qh9Uct+gelu8T10g==",
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.15.tgz",
"integrity": "sha512-+Heey4S2IPsPyTf8Ag3PugUupASJMW894iVps6hXbvwtg1aHSNMXUYO5VG7iRHkPzqpuzT4HMBanCTXPjtGzxg==",
"requires": {
"apollo-link": "^1.2.2"
"apollo-link": "^1.2.13",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3"
}
},
"apollo-utilities": {
"version": "1.0.18",
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.18.tgz",
"integrity": "sha512-hHrmsoMYzzzfUlTOPpxr0qRpTLotMkBIQ93Ub7ki2SWdLfYYKrp6/KB8YOUkbCwXxSFvYSV24ccuwUEqZIaHIA==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.3.tgz",
"integrity": "sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw==",
"requires": {
"fast-json-stable-stringify": "^2.0.0"
"@wry/equality": "^0.1.2",
"fast-json-stable-stringify": "^2.0.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"appolo": {
@ -7103,7 +7138,7 @@
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@ -8985,7 +9020,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -9003,11 +9039,13 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
"bundled": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -9020,15 +9058,18 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -9131,7 +9172,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -9141,6 +9183,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -9153,17 +9196,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -9180,6 +9226,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -9252,7 +9299,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -9262,6 +9310,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -9367,6 +9416,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -9384,6 +9434,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -9422,7 +9473,8 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
@ -9666,18 +9718,10 @@
"iterall": "^1.2.1"
}
},
"graphql-anywhere": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/graphql-anywhere/-/graphql-anywhere-4.1.16.tgz",
"integrity": "sha512-DNQGxrh2p8w4vQwHIW1Sw65ZDbOr6ktQCeol6itH3LeWy1a3IoZ67jxrhgrHM+Upg8oiazvteSr64VRxJ8n5+g==",
"requires": {
"apollo-utilities": "^1.0.18"
}
},
"graphql-tag": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.9.2.tgz",
"integrity": "sha512-qnNmof9pAqj/LUzs3lStP0Gw1qhdVCUS7Ab7+SUB6KD5aX1uqxWQRwMnOGTkhKuLvLNIs1TvNz+iS9kUGl1MhA=="
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz",
"integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg=="
},
"growl": {
"version": "1.9.2",
@ -11430,12 +11474,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -11455,7 +11501,8 @@
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
@ -13065,7 +13112,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -13112,13 +13159,13 @@
},
"onetime": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
"ora": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
"resolved": "http://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
"integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
"dev": true,
"requires": {
@ -13176,7 +13223,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -13240,7 +13287,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -13272,7 +13319,7 @@
},
"onetime": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
@ -13542,7 +13589,7 @@
},
"onetime": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
@ -14685,6 +14732,14 @@
"is-wsl": "^1.1.0"
}
},
"optimism": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.10.3.tgz",
"integrity": "sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==",
"requires": {
"@wry/context": "^0.4.0"
}
},
"optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@ -19027,6 +19082,14 @@
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA=="
},
"ts-invariant": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz",
"integrity": "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==",
"requires": {
"tslib": "^1.9.3"
}
},
"tsconfig": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
@ -20570,15 +20633,16 @@
}
},
"zen-observable": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.9.tgz",
"integrity": "sha512-Y9kPzjGvIZ5jchSlqlCpBW3I82zBBL4z+ulXDRVA1NwsKzjt5kwAi+gOYIy0htNkfuehGZZtP5mRXHRV6TjDWw=="
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
},
"zen-observable-ts": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz",
"integrity": "sha512-KJz2O8FxbAdAU5CSc8qZ1K2WYEJb1HxS6XDRF+hOJ1rOYcg6eTMmS9xYHCXzqZZzKw6BbXWyF4UpwSsBQnHJeA==",
"version": "0.8.20",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz",
"integrity": "sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA==",
"requires": {
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"
}
}

View File

@ -21,10 +21,10 @@
"@babel/preset-env": "^7.5.4",
"@babel/preset-stage-2": "^7.0.0",
"@babel/runtime": "^7.5.4",
"apollo-cache-inmemory": "^1.2.2",
"apollo-client": "^2.3.2",
"apollo-link": "^1.2.2",
"apollo-link-http": "^1.5.4",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-link": "^1.2.13",
"apollo-link-http": "^1.5.16",
"appolo": "^6.0.19",
"autoprefixer": "^7.1.2",
"axios": "^0.18.0",
@ -50,7 +50,7 @@
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"graphql-tag": "^2.10.1",
"html-webpack-plugin": "^2.30.1",
"lodash": "^4.17.10",
"moment": "^2.24.0",

View File

@ -1,5 +1,5 @@
<template>
<div class="chapter">
<div class="chapter" :data-scrollto="chapter.id">
<h3 :id="'chapter-' + index">{{chapter.title}}</h3>
<bookmark-actions

View File

@ -1,5 +1,7 @@
<template>
<div class="content-component" :class="{'content-component--bookmarked': bookmarked}">
<div class="content-component"
:class="{'content-component--bookmarked': bookmarked}"
:data-scrollto="component.id">
<bookmark-actions
v-if="showBookmarkActions"
@add-note="addNote(component.id)"

View File

@ -1,5 +1,5 @@
<template>
<div class="link-block">
<div class="link-block" :class="{ 'link-block--no-margin': noMargin}">
<link-icon class="link-block__icon"></link-icon>
<a :href="href" class="link-block__link" target="_blank">{{value.text}}</a>
</div>
@ -9,7 +9,12 @@
import LinkIcon from '@/components/icons/LinkIcon';
export default {
props: ['value'],
props: {
value: Object,
'no-margin': {
default: false
}
},
components: {
LinkIcon
@ -31,6 +36,10 @@
grid-template-columns: 50px 1fr;
align-items: center;
&--no-margin {
margin-bottom: 0;
}
&__icon {
width: 30px;
height: 30px;

View File

@ -1,5 +1,5 @@
<template>
<div class="survey-block">
<div class="survey-block" :data-scrollto="value.id">
<router-link class="button button--primary"
:to="{name: 'survey', params: {id:value.id}}">Übung anzeigen
</router-link>

View File

@ -1,5 +1,5 @@
<template>
<div class="assignment">
<div class="assignment" :data-scrollto="value.id">
<p class="assignment__assignment-text">
{{assignment.assignment}}
</p>

View File

@ -7,7 +7,7 @@
<a class="bookmark-actions__action bookmark-actions__add-note" v-if="bookmarked && !note" @click="$emit('add-note')">
<add-note-icon></add-note-icon>
</a>
<a class="bookmark-actions__action bookmark-actions__edit-note bookmark-actions__action--noted" @click="$emit('edit-note')" v-if="note">
<a :data-scrollto="note.id" class="bookmark-actions__action bookmark-actions__edit-note bookmark-actions__action--noted" @click="$emit('edit-note')" v-if="note">
<note-icon></note-icon>
</a>
</div>

View File

@ -0,0 +1,64 @@
<template>
<div class="activity-entry">
<div class="activity-entry__content">
<h3 class="activity-entry__title">{{title}}</h3>
<slot></slot>
</div>
<div class="activity-entry__link" @click="$emit('link')">
<chevron-right class="activity-entry__icon"></chevron-right>
</div>
</div>
</template>
<script>
import ChevronRight from '@/components/icons/ChevronRight';
export default {
props: ['title'],
components: {
ChevronRight
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.activity-entry {
padding: $small-spacing 0;
border-bottom: 1px solid $color-silver;
display: flex;
justify-content: space-between;
&__title {
@include small-text;
// todo: make style definition for small text and silver color
color: $color-silver-dark;
margin-bottom: 0;
}
&__content {
flex-grow: 1;
@include regular-text;
line-height: $default-line-height;
}
&__link {
display: flex;
flex-grow: 0;
align-content: center;
cursor: pointer;
}
&__icon {
fill: $color-brand;
width: 30px;
}
/deep/ p {
@include regular-text;
}
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<div class="activity-filter">
<a class="activity-filter__link"
:class="{'activity-filter__link--active': filter === entry.tag}"
@click="$emit('change-filter', entry.tag)"
v-for="(entry, i) in filters"
:key="i"
>{{entry.label}}</a>
</div>
</template>
<script>
export default {
props: ['filter'],
data() {
return {
filters: [
{tag: '', label: 'Alle'},
{tag: 'assignments', label: 'Ergebnisse'},
{tag: 'surveys', label: 'Übungen'},
{tag: 'bookmarks', label: 'Lesezeichen'},
{tag: 'notes', label: 'Notizen'},
]
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.activity-filter {
display: flex;
justify-content: space-between;
&__link {
@include small-text;
cursor: pointer;
padding: $large-spacing 0;
color: $color-silver-dark;
&--active {
color: $color-brand;
}
}
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<div class="content-bookmark module-activity-entry">
<div v-html="text" v-if="content.type === 'text_block'"></div>
<div v-else-if="content.type === 'link_block'">
<link-block :value="content.value" :no-margin="true"></link-block>
</div>
<p v-else>
{{type}}
</p>
</div>
</template>
<script>
import LinkBlock from '@/components/content-blocks/LinkBlock';
export default {
components: {LinkBlock},
props: ['bookmark'],
computed: {
content() {
return this.bookmark.contentBlock.contents.find(e => e.id === this.bookmark.uuid)
},
text() {
return this.content.value.text ? this.content.value.text : 'TO BE DEFINED'
},
type() {
switch (this.content.type) {
case 'assignment':
return 'Aufgabe & Ergebnis';
case 'link_block':
return this.content;
case 'survey':
return 'Übung';
case 'image_url_block':
return 'Bild';
default:
return this.content.type;
}
}
}
}
</script>

View File

@ -1,39 +1,90 @@
<template>
<div class="module-activity">
<h3 class="module-activity__module-name">{{moduleTitle}}</h3>
<h2 class="module-activity__title">{{title}}</h2>
<div class="module-activity" v-if="!empty">
<h3 class="module-activity__module-name">{{module.topic.title}}</h3>
<h2 class="module-activity__title">{{module.metaTitle}} - {{module.title}}</h2>
<div class="module-activity__tasks activity-tasks">
<h4 class="activity-tasks__title">Meine Ergebnisse</h4>
<ol class="activity-tasks__task-list task-list">
<li v-for="activity in activities" :key="activity.key" class="task-list__item task-item">
<h5 class="task-item__title">{{activity.assignmentTitle}}</h5>
<p class="task-item__submission">{{activity.answer}}</p>
<a href="#" @click="goToAssignment(activity)"><chevron-right class="task-item__chevron"></chevron-right></a>
</li>
</ol>
<activity-entry title="Aufgabe & Ergebnis" class="module-activity__entry"
v-for="submission in module.mySubmissions"
:key="submission.id"
v-if="applyFilter('assignments')"
@link="goTo(submission.assignment.id)">
{{submission.text}}
</activity-entry>
<activity-entry title="Übung" class="module-activity__entry"
v-for="answer in module.myAnswers"
v-if="applyFilter('surveys')"
:key="answer.id"
@link="goTo(answer.survey.id)">
{{answer.survey.title}}
</activity-entry>
<activity-entry title="Lesezeichen" class="module-activity__entry"
v-for="bookmark in module.myContentBookmarks"
:key="bookmark.id"
v-if="applyFilter('bookmarks')"
@link="goTo(bookmark.uuid)">
<content-bookmark :bookmark="bookmark"></content-bookmark>
</activity-entry>
<activity-entry title="Lesezeichen" class="module-activity__entry"
v-for="bookmark in module.myChapterBookmarks"
v-if="applyFilter('bookmarks')"
:key="bookmark.id"
@link="goTo(bookmark.chapter.id)">
{{bookmark.chapter.description}}
</activity-entry>
<activity-entry title="Notiz" class="module-activity__entry"
v-if="applyFilter('notes')"
v-for="note in notes" :key="note.id"
@link="goTo(note.id)">
{{note.text}}
</activity-entry>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import ChevronRight from '@/components/icons/ChevronRight';
import {mapActions} from 'vuex'
import ContentBookmark from '@/components/profile/ContentBookmark';
import ActivityEntry from '@/components/profile/ActivityEntry';
import SCROLL_TO_MUTATION from '@/graphql/gql/local/mutations/scrollTo.gql';
export default {
props: ['metaTitle', 'title', 'activities', 'topic', 'slug'],
components: {ChevronRight},
props: ['module', 'filter'],
components: {
ContentBookmark,
ActivityEntry
},
computed: {
moduleTitle () {
return `${this.topic.title} - ${this.metaTitle}`
empty() {
return !(this.module.mySubmissions.length ||
this.module.myAnswers.length ||
this.module.myContentBookmarks.length ||
this.module.myChapterBookmarks.length ||
this.module.bookmark)
},
notes() {
return this.module.myChapterBookmarks
.concat(this.module.myContentBookmarks)
.concat([this.module.bookmark ? this.module.bookmark : {}])
.filter(b => b.note)
.map(b => b.note);
}
},
methods: {
...mapActions(['scrollToAssignmentId']),
goToAssignment (activity) {
const url = `/module/${this.slug}/`;
this.scrollToAssignmentId(activity.assignmentId);
goTo(scrollTo) {
const url = `/module/${this.module.slug}/`;
this.$apollo.mutate({
mutation: SCROLL_TO_MUTATION,
variables: {
scrollTo
}
});
this.$router.push(url);
},
applyFilter(filterCriteria) {
return !this.filter || this.filter === filterCriteria;
}
}
}
@ -44,11 +95,6 @@
@import "@/styles/_mixins.scss";
.module-activity {
@include widget-shadow;
padding: $medium-spacing;
/* used for text ellipis... somehow https://css-flexbox-text-ellipsis.dinhquangtrung.net/ just does not work */
max-width: 640px;
@include desktop {
@ -63,60 +109,22 @@
max-width: 320px;
}
margin-bottom: 2*$large-spacing;
&__module-name {
@include small-text;
color: $color-silver-dark;
@include regular-text;
margin-bottom: $small-spacing;
}
}
.activity-tasks {
&__title {
background-color: $color-brand-light;
padding: $small-spacing $medium-spacing;
border-radius: $default-border-radius;
&--alternative {
background-color: $color-accent-1-light;
}
}
}
.task-item {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
$line-height: 50px;
&__title {
&::after {
content: ":"
@include heading-2;
}
&__entry {
&:first-of-type {
border-top: 1px solid $color-silver;
}
margin-right: $medium-spacing;
}
&__submission {
width: 100%;
min-width: 0;
text-overflow: ellipsis;
overflow: hidden;
}
&__title, &__submission {
line-height: $line-height;
height: $line-height;
white-space: nowrap;
}
&__chevron {
line-height: $line-height;
height: $medium-spacing;
vertical-align: middle;
position: relative;
top: ($line-height - $medium-spacing) / 2;
fill: $color-brand;
width: 30px;
}
}

View File

@ -1,11 +1,13 @@
import {InMemoryCache} from 'apollo-cache-inmemory/lib/index'
import {HttpLink} from 'apollo-link-http/lib/index'
import {ApolloClient} from 'apollo-client/index'
import {createHttpLink} from 'apollo-link-http'
import {ApolloClient} from 'apollo-client'
import {ApolloLink} from 'apollo-link'
import fetch from 'unfetch'
import {typeDefs} from '@/graphql/typedefs';
import {resolvers} from '@/graphql/resolvers';
export default function (uri) {
const httpLink = new HttpLink({
const httpLink = createHttpLink({
// uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
uri,
credentials: 'include',
@ -48,7 +50,8 @@ export default function (uri) {
assignment: (_, args, {getCacheKey}) => getCacheKey({__typename: 'AssignmentNode', id: args.id}),
objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}),
objectiveGroup: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveGroupNode', id: args.id}),
module: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ModuleNode', id: args.id}),
// todo: remove, the new client seems to cache this correctly by itself
// module: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ModuleNode', id: args.id}),
projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}),
}
}
@ -66,11 +69,23 @@ export default function (uri) {
}
};
// we use the cache as our local state
cache.writeData({
data: {
scrollPosition: {
__typename: 'ScrollPosition',
scrollTo: ''
}
}
});
// Create the apollo client
return new ApolloClient({
link: composedLink,
// link: httpLink,
cache: cache,
connectToDevTools: true
cache,
connectToDevTools: true,
typeDefs,
resolvers
})
}

View File

@ -0,0 +1,3 @@
mutation($scrollTo: String!) {
scrollTo(scrollTo: $scrollTo) @client
}

View File

@ -0,0 +1,5 @@
query ScrollPosition {
scrollPosition @client {
scrollTo
}
}

View File

@ -1,22 +1,83 @@
query {
query MyActivityQuery {
myActivity {
edges {
node {
id
text
assignment {
id
title
slug
metaTitle
topic {
title
module {
title
metaTitle
slug
id
topic {
title
}
mySubmissions {
edges {
node {
id
text
assignment {
id
title
}
}
}
}
myAnswers {
edges {
node {
id
survey {
id
title
}
}
}
}
myContentBookmarks {
edges {
node {
id
uuid
note {
id
text
}
contentBlock {
id
type
contents
}
}
}
}
myChapterBookmarks {
edges {
node {
id
note {
id
text
}
chapter {
id
title
description
}
}
}
}
bookmark {
id
note {
id
text
}
module {
id
teaser
metaTitle
intro
}
}
}
}
}

View File

@ -0,0 +1,12 @@
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
export const resolvers = {
Mutation: {
scrollTo: (_, {scrollTo}, {cache}) => {
const data = cache.readQuery({query: SCROLL_POSITION});
data.scrollPosition.scrollTo = scrollTo;
cache.writeQuery({query: SCROLL_POSITION, data});
return data.scrollPosition;
}
}
};

View File

@ -0,0 +1,11 @@
import gql from 'graphql-tag';
export const typeDefs = gql`
type ScrollPosition {
scrollTo: String!
}
type Mutation {
scrollTo(scrollTo: String!): ScrollPosition
}
`;

View File

@ -1,58 +1,46 @@
<template>
<div class="activity">
<h1 class="activity__header">Meine Aktivitäten</h1>
<activity-filter
:filter="filter"
@change-filter="filter = $event"
></activity-filter>
<div class="modules">
<module-activity v-for="moduleId in Object.keys(modules)" v-bind="modules[moduleId]" :key="moduleId" class="activity"></module-activity>
<module-activity
v-for="(module, id) in modules"
:filter="filter"
:module="module"
:key="id"
class="activity"></module-activity>
</div>
</div>
</template>
<script>
import ModuleActivity from '@/components/profile/ModuleActivity';
import ActivityFilter from '@/components/profile/ActivityFilter';
import MY_ACTIVITY_QUERY from '@/graphql/gql/myActivity.gql'
export default {
components: {
ModuleActivity
ModuleActivity,
ActivityFilter
},
apollo: {
submissions: {
modules: {
query: MY_ACTIVITY_QUERY,
update(data) {
return this.$getRidOfEdges(data).myActivity;
}
},
pollInterval: 5000,
}
},
data() {
return {
submissions: []
}
},
computed: {
modules () {
let modules = {};
this.submissions.map((submission) => {
let activity = {
assignmentId: submission.assignment.id,
assignmentTitle: submission.assignment.title,
answer: submission.text
};
const module = submission.assignment.module
if (!(module.id in modules)) {
modules[module.id] = {
...module,
activities: []
}
}
modules[module.id].activities = [...modules[module.id].activities, activity];
});
return modules
modules: [],
filter: ''
}
}
}

View File

@ -3,9 +3,12 @@
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import {mapGetters, mapActions} from 'vuex'
import ASSIGNMENTS_QUERY from '@/graphql/gql/assignmentsQuery.gql';
import {moduleQuery} from '@/graphql/queries';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
import SCROLL_TO_MUTATION from '@/graphql/gql/local/mutations/scrollTo.gql';
import Module from '@/components/modules/Module.vue';
@ -16,23 +19,65 @@
methods: {
...mapActions(['scrollToAssignmentReady', 'scrollingToAssignment']),
scrollTo() {
if (this.scrollPosition && this.scrollPosition.scrollTo) {
let options = {
container: 'body',
easing: 'ease',
offset: -60,
onStart: (element) => {
},
onDone: (element) => {
},
onCancel: function () {
// scrolling has been interrupted
},
x: false,
y: true
};
setTimeout(() => {
this.$scrollTo(`[data-scrollto="${this.scrollPosition.scrollTo}"]`, 1000, options);
this.$apollo.mutate({
mutation: SCROLL_TO_MUTATION,
variables: {
scrollTo: ''
}
})
}, 250); // unfortunately this timeout is needed as it is hard to tell when everything is rendered
}
}
},
computed: {
...mapGetters({
scrollToAssignmentId: 'scrollToAssignmentId',
isScrollingToAssignment: 'scrollingToAssignment',
editModule: 'editModule'
}),
editModule: 'editModule'
}),
},
apollo: {
module: moduleQuery,
module() {
return {
query: MODULE_DETAILS_QUERY,
variables: {
slug: this.$route.params.slug
},
update(data) {
return this.$getRidOfEdges(data).module || {};
},
result() {
// scroll only after the module has been loaded completely
this.scrollTo();
}
}
},
assignments() {
return {
query: ASSIGNMENTS_QUERY
}
}
},
scrollPosition: {
query: SCROLL_POSITION
},
},
data() {
@ -41,34 +86,5 @@
assignments: []
}
},
mounted () {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'setScrollToAssignmentReady' && mutation.payload && !this.isScrollingToAssignment) {
this.scrollingToAssignment(true);
let options = {
container: '#app',
easing: 'ease',
offset: -60,
onStart: (element) => {
},
onDone: (element) => {
this.scrollToAssignmentReady(false);
this.scrollingToAssignment(false);
},
onCancel: function() {
// scrolling has been interrupted
},
x: false,
y: true
};
setTimeout(() => {
this.$scrollTo(`#${this.scrollToAssignmentId.replace(/=/g, '')}`, 1000, options);
}, 250) // unfortunately this timeout is needed as it is hard to tell when everything is rendered
}
})
},
beforeDestroy () {
this.scrollToAssignmentReady(false);
}
}
</script>

View File

@ -3,6 +3,7 @@ import Vuex from 'vuex'
Vue.use(Vuex)
// WARNING fixme todo: please do not use this anymore, use the local GraphQL cache
export default new Vuex.Store({
modules: {},

View File

@ -21,3 +21,4 @@
@import "password_forms";
@import "public-page";
@import "student-submission";
@import "module-activity";

View File

@ -0,0 +1,12 @@
.module-activity-entry {
&__title {
@include small-text;
// todo: make style definition for small text and silver color
color: $color-silver-dark;
margin-bottom: 0;
}
&__text {
@include regular-text;
}
}

View File

@ -6,7 +6,7 @@ from graphene_django.debug import DjangoDebug
# noinspection PyUnresolvedReferences
from api import graphene_wagtail # Keep this import exactly here, it's necessary for StreamField conversion
from assignments.schema.mutations import AssignmentMutations
from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery, MyActivityQuery
from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
from basicknowledge.queries import BasicKnowledgeQuery
from books.schema.mutations.main import BookMutations
from books.schema.queries import BookQuery
@ -26,7 +26,7 @@ from registration.mutations_public import RegistrationMutations
class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery,
StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, SurveysQuery,
graphene.ObjectType):
node = relay.Node.Field()

View File

@ -12,11 +12,3 @@ class AssignmentsQuery(object):
class StudentSubmissionQuery(object):
student_submission = relay.Node.Field(StudentSubmissionNode)
class MyActivityQuery(object):
my_activity = DjangoFilterConnectionField(StudentSubmissionNode)
def resolve_my_activity(self, info, **kwargs):
user = info.context.user
return StudentSubmission.objects.filter(student=user)

View File

@ -39,20 +39,88 @@ class MyAssignemntsText(DefaultUserTestCase):
def query_my_assignments(self):
query = '''
query {
query MyActivityQuery {
myActivity {
edges {
node {
id
text
assignment {
title
slug
metaTitle
mySubmissions {
edges {
node {
id
text
assignment {
id
title
}
}
}
}
myAnswers {
edges {
node {
id
survey {
id
title
}
}
}
}
myContentBookmarks {
edges {
node {
id
uuid
note {
id
text
}
contentBlock {
id
type
contents
}
}
}
}
myChapterBookmarks {
edges {
node {
id
note {
id
text
}
chapter {
id
title
description
}
}
}
}
bookmark {
id
title
note {
id
text
}
module {
id
teaser
metaTitle
intro
}
}
}
}
}
}
'''
result = self.client.execute(query)
@ -68,5 +136,5 @@ class MyAssignemntsText(DefaultUserTestCase):
result = self.query_my_assignments()
contents = self.get_content(result)
self.assertEqual(len(contents), 1)
self.assertEquals(contents[0].get('node').get('text'), self.submission1.text)
self.assertEquals(contents[0].get('node').get('mySubmissions').get('edges')[0].get('node').get('text'), self.submission1.text)

View File

@ -4,10 +4,14 @@ from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from api.utils import get_object
from assignments.models import StudentSubmission
from assignments.schema.types import StudentSubmissionNode
from books.utils import are_solutions_enabled_for
from notes.models import ContentBlockBookmark, ChapterBookmark, ModuleBookmark
from notes.schema import ContentBlockBookmarkNode, ChapterBookmarkNode, ModuleBookmarkNode
from rooms.models import ModuleRoomSlug
from surveys.models import Answer
from surveys.schema import AnswerNode
from ..models import Book, Topic, Module, Chapter, ContentBlock
@ -103,6 +107,7 @@ class ChapterNode(DjangoObjectType):
chapter=self
).first()
class ModuleNode(DjangoObjectType):
pk = graphene.Int()
chapters = DjangoFilterConnectionField(ChapterNode)
@ -110,6 +115,10 @@ class ModuleNode(DjangoObjectType):
hero_image = graphene.String()
solutions_enabled = graphene.Boolean()
bookmark = graphene.Field(ModuleBookmarkNode)
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
my_answers = DjangoFilterConnectionField(AnswerNode)
my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode)
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
class Meta:
model = Module
@ -145,6 +154,32 @@ class ModuleNode(DjangoObjectType):
module=self
).first()
def resolve_my_submissions(self, info, **kwargs):
user = info.context.user
return StudentSubmission.objects.filter(student=user, assignment__module=self)
# we want:
# StudentSubmission
def resolve_my_answers(self, info, **kwargs):
user = info.context.user
return Answer.objects.filter(owner=user, survey__module=self)
# Survey
def resolve_my_content_bookmarks(self, info, **kwargs):
user = info.context.user
content_blocks = ContentBlock.objects.live().descendant_of(self)
return ContentBlockBookmark.objects.filter(content_block__in=content_blocks, user=user)
# Bookmark Text
# Bookmark Image etc
# Bookmark Other
# Note
#
def resolve_my_chapter_bookmarks(self, info, **kwargs):
user = info.context.user
chapters = Chapter.objects.live().descendant_of(self)
return ChapterBookmark.objects.filter(chapter__in=chapters, user=user)
class TopicNode(DjangoObjectType):
pk = graphene.Int()
@ -190,34 +225,35 @@ class BookNode(DjangoObjectType):
return Topic.get_by_parent(self)
class FilteredChapterNode(DjangoObjectType):
content_blocks = DjangoFilterConnectionField(ContentBlockNode)
class Meta:
model = Chapter
only_fields = [
'slug', 'title',
]
filter_fields = [
'slug', 'title',
]
interfaces = (relay.Node,)
def resolve_content_blocks(self, *args, **kwargs):
return ContentBlock.get_by_parent(self)
# todo: do we need this?
# class FilteredChapterNode(DjangoObjectType):
# content_blocks = DjangoFilterConnectionField(ContentBlockNode)
#
# class Meta:
# model = Chapter
# only_fields = [
# 'slug', 'title', 'description',
# ]
# filter_fields = [
# 'slug', 'title',
# ]
# interfaces = (relay.Node,)
#
# def resolve_content_blocks(self, *args, **kwargs):
# return ContentBlock.get_by_parent(self)
class BookQuery(object):
book = relay.Node.Field(BookNode)
topic = graphene.Field(TopicNode, slug=graphene.String())
module = graphene.Field(ModuleNode, slug=graphene.String(), id=graphene.ID())
chapter = relay.Node.Field(FilteredChapterNode)
chapter = relay.Node.Field(ChapterNode)
content_block = relay.Node.Field(ContentBlockNode)
books = DjangoFilterConnectionField(BookNode)
topics = DjangoFilterConnectionField(TopicNode)
modules = DjangoFilterConnectionField(ModuleNode)
chapters = DjangoFilterConnectionField(FilteredChapterNode)
chapters = DjangoFilterConnectionField(ChapterNode)
def resolve_books(self, *args, **kwargs):
return Book.objects.filter(**kwargs).live()

View File

@ -0,0 +1,29 @@
from django.core.management import BaseCommand
from assignments.models import Assignment, StudentSubmission
import random
import json
from django.db.models import Q
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('jsonfile', type=str)
def handle(self, *args, **options):
jsonfile = options['jsonfile']
self.stdout.write("Exporting assignments")
assignment_list = []
for assignment in Assignment.objects.filter(user_created=False):
assignment_dict = {
"title": assignment.title,
"id": assignment.id,
"text": assignment.assignment,
}
submissions = assignment.submissions.filter(text__isnull=False).filter(~Q(text=''))
if submissions.count() > 0:
example = random.choice(submissions)
assignment_dict['example'] = example.text
assignment_list.append(assignment_dict)
with open(jsonfile, 'w') as f:
f.write(json.dumps(assignment_list, indent=2, sort_keys=True))

View File

@ -23,6 +23,8 @@ class ContentBlockBookmarkNode(DjangoObjectType):
class Meta:
model = ContentBlockBookmark
filter_fields = []
interfaces = (relay.Node,)
class ModuleBookmarkNode(DjangoObjectType):
@ -37,3 +39,5 @@ class ChapterBookmarkNode(DjangoObjectType):
class Meta:
model = ChapterBookmark
filter_fields = []
interfaces = (relay.Node,)

View File

@ -14,6 +14,7 @@ class AnswerNode(DjangoObjectType):
class Meta:
model = Answer
interfaces = (relay.Node,)
filter_fields = []
def resolve_pk(self, *args, **kwargs):
return self.id

View File

@ -3,6 +3,10 @@ from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from assignments.models import StudentSubmission
from assignments.schema.types import StudentSubmissionNode
from books.models import Module
from books.schema.queries import ModuleNode
from users.models import User, SchoolClass
@ -43,6 +47,7 @@ class UserNode(DjangoObjectType):
class UsersQuery(object):
me = graphene.Field(UserNode)
all_users = DjangoFilterConnectionField(UserNode)
my_activity = DjangoFilterConnectionField(ModuleNode)
def resolve_me(self, info, **kwargs):
return info.context.user
@ -53,6 +58,9 @@ class UsersQuery(object):
else:
return User.objects.all()
def resolve_my_activity(self, info, **kwargs):
return Module.objects.all()
class AllUsersQuery(object):
me = graphene.Field(UserNode)
@ -63,5 +71,3 @@ class AllUsersQuery(object):
return User.objects.none()
else:
return User.objects.all()