aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webui/package-lock.json244
-rw-r--r--webui/package.json11
-rw-r--r--webui/src/App.js48
-rw-r--r--webui/src/Label.js21
-rw-r--r--webui/src/bug/Bug.js65
-rw-r--r--webui/src/bug/LabelChange.js13
-rw-r--r--webui/src/bug/Message.js43
-rw-r--r--webui/src/bug/SetStatus.js13
-rw-r--r--webui/src/bug/SetTitle.js13
-rw-r--r--webui/src/bug/Timeline.js60
-rw-r--r--webui/src/index.js19
-rw-r--r--webui/src/list/BugRow.js67
-rw-r--r--webui/src/list/List.js160
-rw-r--r--webui/src/list/ListQuery.js39
14 files changed, 458 insertions, 358 deletions
diff --git a/webui/package-lock.json b/webui/package-lock.json
index 50f8d797..fc2bc0b2 100644
--- a/webui/package-lock.json
+++ b/webui/package-lock.json
@@ -849,6 +849,11 @@
"resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz",
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw=="
},
+ "@emotion/hash": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.1.tgz",
+ "integrity": "sha512-OYpa/Sg+2GDX+jibUfpZVn1YqSVRpYmTLF2eyAfrFTIJSbwyIrc+YscayoykvaOME/wV4BV0Sa0yqdMrgse6mA=="
+ },
"@material-ui/core": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz",
@@ -892,6 +897,41 @@
"recompose": "0.28.0 - 0.30.0"
}
},
+ "@material-ui/styles": {
+ "version": "3.0.0-alpha.10",
+ "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-3.0.0-alpha.10.tgz",
+ "integrity": "sha512-qJ5eiupBPRCNlMCDZ2G5h8auBtBtm8uT/oCUAJ/FqhO5oC7POLmmvDN1Cq1cgAmqQnaL6uN5mAM1Gc90GpKr9A==",
+ "requires": {
+ "@babel/runtime": "^7.2.0",
+ "@emotion/hash": "^0.7.1",
+ "@material-ui/utils": "^3.0.0-alpha.2",
+ "classnames": "^2.2.5",
+ "deepmerge": "^3.0.0",
+ "hoist-non-react-statics": "^3.2.1",
+ "jss": "^10.0.0-alpha.7",
+ "jss-plugin-camel-case": "^10.0.0-alpha.7",
+ "jss-plugin-default-unit": "^10.0.0-alpha.7",
+ "jss-plugin-global": "^10.0.0-alpha.7",
+ "jss-plugin-nested": "^10.0.0-alpha.7",
+ "jss-plugin-props-sort": "^10.0.0-alpha.7",
+ "jss-plugin-rule-value-function": "^10.0.0-alpha.7",
+ "jss-plugin-vendor-prefixer": "^10.0.0-alpha.7",
+ "prop-types": "^15.6.0",
+ "warning": "^4.0.1"
+ },
+ "dependencies": {
+ "jss": {
+ "version": "10.0.0-alpha.16",
+ "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.16.tgz",
+ "integrity": "sha512-HmKNNnr82TR5jkWjBcbrx/uim2ief588pWp7zsf4GQpL125zRkEaWYL1SXv5bR6bBvAoTtvJsTAOxDIlLxUNZg==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "is-in-browser": "^1.1.3",
+ "tiny-warning": "^1.0.2"
+ }
+ }
+ }
+ },
"@material-ui/system": {
"version": "3.0.0-alpha.2",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz",
@@ -3103,7 +3143,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -3121,11 +3162,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"
@@ -3138,15 +3181,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",
@@ -3249,7 +3295,8 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -3259,6 +3306,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3271,17 +3319,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.3.5",
"bundled": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -3298,6 +3349,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3370,7 +3422,8 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -3380,6 +3433,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3455,7 +3509,8 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -3485,6 +3540,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",
@@ -3502,6 +3558,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -3540,11 +3597,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
},
@@ -5225,9 +5284,9 @@
}
},
"eslint-config-prettier": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-4.1.0.tgz",
- "integrity": "sha512-zILwX9/Ocz4SV2vX7ox85AsrAgXV3f2o2gpIicdMIOra48WYqgUnWNH/cR/iHtmD2Vb3dLSC3LiEJnS05Gkw7w==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz",
+ "integrity": "sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==",
"dev": true,
"requires": {
"get-stdin": "^6.0.0"
@@ -5437,9 +5496,9 @@
}
},
"eslint-plugin-prettier": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz",
- "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.0.tgz",
+ "integrity": "sha512-XWX2yVuwVNLOUhQijAkXz+rMPPoCr7WFiAl8ig6I7Xn+pPVhDhzg4DxHpmbeb0iqjO9UronEA3Tb09ChnFVHHA==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
@@ -6721,7 +6780,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -6739,11 +6799,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"
@@ -6756,15 +6818,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",
@@ -6867,7 +6932,8 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -6877,6 +6943,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -6889,17 +6956,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"
@@ -6916,6 +6986,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -6988,7 +7059,8 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -6998,6 +7070,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -7073,7 +7146,8 @@
},
"safe-buffer": {
"version": "5.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -7103,6 +7177,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",
@@ -7120,6 +7195,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -7158,11 +7234,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
},
@@ -7307,9 +7385,9 @@
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"graphql": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.2.0.tgz",
- "integrity": "sha512-dlFHRtxsL4sBy1C1e3v64IUd5ndZhAOHZ/z3Dr4Nm6+cvr9elrnz4BhMF9h9mRBBnhUCGLc4GH4xvPbKG6sUeA==",
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.3.0.tgz",
+ "integrity": "sha512-MdfI4v7kSNC3NhB7cF8KNijDsifuWO2XOtzpyququqaclO8wVuChYv+KogexDwgP5sp7nFI9Z6N4QHgoLkfjrg==",
"requires": {
"iterall": "^1.2.2"
}
@@ -9380,6 +9458,76 @@
}
}
},
+ "jss-plugin-camel-case": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-Bwrav1ZB0XywdJW6TaEuFhKe1ZpZvUlESh3jsFOvebA9aFTYNCkmHMEqjA5+u9VMxksl3u77nnZHtukpxkzrBA==",
+ "requires": {
+ "@babel/runtime": "^7.0.0",
+ "hyphenate-style-name": "^1.0.2"
+ }
+ },
+ "jss-plugin-default-unit": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-auuJUbQaWMxoHOVFPrfZNZpZm9ab8PZeDyvey8nMt2lbokkmZ53UyAnM/1kNsg5BdAXTItcLDxDB3I4gwNU84g==",
+ "requires": {
+ "@babel/runtime": "^7.0.0"
+ }
+ },
+ "jss-plugin-global": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-OWeoW4szLDgRUKviST+xfilqa8O5uXJCW+O3YonheCRTRJg6rRzlE/b5pfYPoU9UtwvY9n7JvwBX5r3c1lMsEQ==",
+ "requires": {
+ "@babel/runtime": "^7.0.0"
+ }
+ },
+ "jss-plugin-nested": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-wsRzuIZXAc6WMjc61mREW9cUrDxgSI7dK/fx5c7a06IDUfSn+83NJ30J/RB4oBnbQW9SijV/muujz7IJqpn9Gw==",
+ "requires": {
+ "@babel/runtime": "^7.0.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-props-sort": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-KXOCaHUk1+KXqE0z3q66/w1fDoy+VsZvI77gLxOqTsTrvIKFLX0jarwXogW3CDlaPQQFTZ6JykJJXtPRTBlstA==",
+ "requires": {
+ "@babel/runtime": "^7.0.0"
+ }
+ },
+ "jss-plugin-rule-value-function": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-ett83hvIM69/LknmrWndrrdiDlfLfP+rneU5qP7gTOWJ7g1P9GuEL1Tc4CWdZUWBX+T58tgIBP0V1pzWCkP0QA==",
+ "requires": {
+ "@babel/runtime": "^7.0.0"
+ }
+ },
+ "jss-plugin-vendor-prefixer": {
+ "version": "10.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0-alpha.7.tgz",
+ "integrity": "sha512-YbIVgqq+dLimOBOEYggho1Iuc0roz4PJSZYyaok9n8JnXVIqPnxYJbr8+bMbvzJ5CL3eeJij/e7L2IPCceRKrA==",
+ "requires": {
+ "@babel/runtime": "^7.0.0",
+ "css-vendor": "^1.1.0"
+ },
+ "dependencies": {
+ "css-vendor": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-1.2.1.tgz",
+ "integrity": "sha512-ZpwiWxn5jWNJ7NF3DAb/Dc/+c2lRu+fnovej/adCv3VJsULJSjdXEpUwRcq4fnpAAh98Hi7b0GDnlyoNFcdv1g==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "is-in-browser": "^1.0.2"
+ }
+ }
+ }
+ },
"jss-props-sort": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz",
@@ -13096,9 +13244,9 @@
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
},
"prettier": {
- "version": "1.16.4",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz",
- "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==",
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.1.tgz",
+ "integrity": "sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==",
"dev": true
},
"prettier-linter-helpers": {
@@ -13381,24 +13529,26 @@
}
},
"react-apollo": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-2.5.3.tgz",
- "integrity": "sha512-sBh7M3h4xdbck8NFnn1nlUG0mD0hCTeIcvov4A8hagxjOyTVSakoum+DP6rYNaHuAZFDS3oNurNNSbGZmgoU6g==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-2.5.6.tgz",
+ "integrity": "sha512-WWX5UykTtmW6+awjqEsSWSdvVyZv/vsavUgpdI4ddn4CBdz47INC+iTdJBnYaUFMB24GmqjFFSoSd98gu1xqKA==",
"requires": {
- "apollo-utilities": "^1.2.1",
+ "apollo-utilities": "^1.3.0",
"hoist-non-react-statics": "^3.3.0",
"lodash.isequal": "^4.5.0",
"prop-types": "^15.7.2",
- "ts-invariant": "^0.3.2",
+ "ts-invariant": "^0.4.2",
"tslib": "^1.9.3"
},
"dependencies": {
- "hoist-non-react-statics": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
- "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
+ "apollo-utilities": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.0.tgz",
+ "integrity": "sha512-wQjV+FdWcTWmWUFlChG5rS0vHKy5OsXC6XlV9STRstQq6VbXANwHy6DHnTEQAfLXWAbNcPgBu+nBUpR3dFhwrA==",
"requires": {
- "react-is": "^16.7.0"
+ "fast-json-stable-stringify": "^2.0.0",
+ "ts-invariant": "^0.4.0",
+ "tslib": "^1.9.3"
}
},
"prop-types": {
@@ -13412,9 +13562,9 @@
}
},
"ts-invariant": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.3.2.tgz",
- "integrity": "sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==",
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.2.tgz",
+ "integrity": "sha512-PTAAn8lJPEdRBJJEs4ig6MVZWfO12yrFzV7YaPslmyhG7+4MA279y4BXT3f72gXeVl0mC1aAWq2rMX4eKTWU/Q==",
"requires": {
"tslib": "^1.9.3"
}
diff --git a/webui/package.json b/webui/package.json
index c5ccef9a..f3138a49 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -5,20 +5,21 @@
"dependencies": {
"@material-ui/core": "^3.9.3",
"@material-ui/icons": "^3.0.2",
+ "@material-ui/styles": "^3.0.0-alpha.10",
"apollo-boost": "^0.3.1",
- "graphql": "^14.2.0",
+ "graphql": "^14.3.0",
"moment": "^2.24.0",
"react": "^16.8.6",
- "react-apollo": "^2.5.3",
+ "react-apollo": "^2.5.6",
"react-dom": "^16.8.6",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"react-scripts": "^2.1.8"
},
"devDependencies": {
- "eslint-config-prettier": "^4.1.0",
- "eslint-plugin-prettier": "^3.0.1",
- "prettier": "^1.16.4"
+ "eslint-config-prettier": "^4.3.0",
+ "eslint-plugin-prettier": "^3.1.0",
+ "prettier": "^1.17.1"
},
"scripts": {
"start": "react-scripts start",
diff --git a/webui/src/App.js b/webui/src/App.js
index 3a693dcb..b2eb6bf0 100644
--- a/webui/src/App.js
+++ b/webui/src/App.js
@@ -1,39 +1,39 @@
import AppBar from '@material-ui/core/AppBar';
import CssBaseline from '@material-ui/core/CssBaseline';
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import Toolbar from '@material-ui/core/Toolbar';
-import Typography from '@material-ui/core/Typography';
import React from 'react';
-import { Route, Switch, withRouter } from 'react-router';
+import { Route, Switch } from 'react-router';
import { Link } from 'react-router-dom';
import BugQuery from './bug/BugQuery';
import ListQuery from './list/ListQuery';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
appTitle: {
+ ...theme.typography.title,
color: 'white',
textDecoration: 'none',
},
-});
+}));
-const App = ({ location, classes }) => (
- <React.Fragment>
- <CssBaseline />
- <AppBar position="static" color="primary">
- <Toolbar>
- <Link to="/" className={classes.appTitle}>
- <Typography variant="title" color="inherit">
- git-bug webui
- </Typography>
- </Link>
- </Toolbar>
- </AppBar>
- <Switch>
- <Route path="/" exact component={ListQuery} />
- <Route path="/bug/:id" exact component={BugQuery} />
- </Switch>
- </React.Fragment>
-);
+export default function App() {
+ const classes = useStyles();
-export default withStyles(styles)(withRouter(App));
+ return (
+ <>
+ <CssBaseline />
+ <AppBar position="static" color="primary">
+ <Toolbar>
+ <Link to="/" className={classes.appTitle}>
+ git-bug webui
+ </Link>
+ </Toolbar>
+ </AppBar>
+ <Switch>
+ <Route path="/" exact component={ListQuery} />
+ <Route path="/bug/:id" exact component={BugQuery} />
+ </Switch>
+ </>
+ );
+}
diff --git a/webui/src/Label.js b/webui/src/Label.js
index e7c25c23..826d21f5 100644
--- a/webui/src/Label.js
+++ b/webui/src/Label.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import {
getContrastRatio,
darken,
@@ -42,7 +42,7 @@ const _genStyle = background => ({
// Generate a style object (text, background and border colors) from the label
const genStyle = label => _genStyle(getColor(label));
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
label: {
...theme.typography.body2,
padding: '0 6px',
@@ -53,12 +53,15 @@ const styles = theme => ({
borderBottom: 'solid 1.5px',
verticalAlign: 'bottom',
},
-});
+}));
-const Label = ({ label, classes }) => (
- <span className={classes.label} style={genStyle(label)}>
- {label}
- </span>
-);
+function Label({ label }) {
+ const classes = useStyles();
+ return (
+ <span className={classes.label} style={genStyle(label)}>
+ {label}
+ </span>
+ );
+}
-export default withStyles(styles)(Label);
+export default Label;
diff --git a/webui/src/bug/Bug.js b/webui/src/bug/Bug.js
index 9b5f84ad..829a4af2 100644
--- a/webui/src/bug/Bug.js
+++ b/webui/src/bug/Bug.js
@@ -1,4 +1,4 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import Typography from '@material-ui/core/Typography/Typography';
import gql from 'graphql-tag';
import React from 'react';
@@ -7,7 +7,7 @@ import Date from '../Date';
import TimelineQuery from './TimelineQuery';
import Label from '../Label';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
maxWidth: 800,
margin: 'auto',
@@ -48,38 +48,41 @@ const styles = theme => ({
display: 'block',
},
},
-});
+}));
-const Bug = ({ bug, classes }) => (
- <main className={classes.main}>
- <div className={classes.header}>
- <span className={classes.title}>{bug.title}</span>
- <span className={classes.id}>{bug.humanId}</span>
+function Bug({ bug }) {
+ const classes = useStyles();
+ return (
+ <main className={classes.main}>
+ <div className={classes.header}>
+ <span className={classes.title}>{bug.title}</span>
+ <span className={classes.id}>{bug.humanId}</span>
- <Typography color={'textSecondary'}>
- <Author author={bug.author} />
- {' opened this bug '}
- <Date date={bug.createdAt} />
- </Typography>
- </div>
-
- <div className={classes.container}>
- <div className={classes.timeline}>
- <TimelineQuery id={bug.id} />
+ <Typography color={'textSecondary'}>
+ <Author author={bug.author} />
+ {' opened this bug '}
+ <Date date={bug.createdAt} />
+ </Typography>
</div>
- <div className={classes.sidebar}>
- <Typography variant={'subheading'}>Labels</Typography>
- <ul className={classes.labelList}>
- {bug.labels.map(l => (
- <li className={classes.label}>
- <Label label={l} key={l} />
- </li>
- ))}
- </ul>
+
+ <div className={classes.container}>
+ <div className={classes.timeline}>
+ <TimelineQuery id={bug.id} />
+ </div>
+ <div className={classes.sidebar}>
+ <Typography variant={'subheading'}>Labels</Typography>
+ <ul className={classes.labelList}>
+ {bug.labels.map(l => (
+ <li className={classes.label}>
+ <Label label={l} key={l} />
+ </li>
+ ))}
+ </ul>
+ </div>
</div>
- </div>
- </main>
-);
+ </main>
+ );
+}
Bug.fragment = gql`
fragment Bug on Bug {
@@ -97,4 +100,4 @@ Bug.fragment = gql`
}
`;
-export default withStyles(styles)(Bug);
+export default Bug;
diff --git a/webui/src/bug/LabelChange.js b/webui/src/bug/LabelChange.js
index 6301f35f..76b6e6e2 100644
--- a/webui/src/bug/LabelChange.js
+++ b/webui/src/bug/LabelChange.js
@@ -1,11 +1,11 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import gql from 'graphql-tag';
import React from 'react';
import Author from '../Author';
import Date from '../Date';
import Label from '../Label';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
...theme.typography.body2,
marginLeft: theme.spacing.unit + 40,
@@ -13,10 +13,11 @@ const styles = theme => ({
author: {
fontWeight: 'bold',
},
-});
+}));
-const LabelChange = ({ op, classes }) => {
+function LabelChange({ op }) {
const { added, removed } = op;
+ const classes = useStyles();
return (
<div className={classes.main}>
<Author author={op.author} className={classes.author} />
@@ -37,7 +38,7 @@ const LabelChange = ({ op, classes }) => {
<Date date={op.date} />
</div>
);
-};
+}
LabelChange.fragment = gql`
fragment LabelChange on TimelineItem {
@@ -54,4 +55,4 @@ LabelChange.fragment = gql`
}
`;
-export default withStyles(styles)(LabelChange);
+export default LabelChange;
diff --git a/webui/src/bug/Message.js b/webui/src/bug/Message.js
index 493de8ee..ff039444 100644
--- a/webui/src/bug/Message.js
+++ b/webui/src/bug/Message.js
@@ -1,4 +1,4 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import Paper from '@material-ui/core/Paper';
import gql from 'graphql-tag';
import React from 'react';
@@ -6,7 +6,7 @@ import Author from '../Author';
import { Avatar } from '../Author';
import Date from '../Date';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
author: {
fontWeight: 'bold',
},
@@ -44,24 +44,27 @@ const styles = theme => ({
padding: '1rem',
whiteSpace: 'pre-wrap',
},
-});
+}));
-const Message = ({ op, classes }) => (
- <article className={classes.container}>
- <Avatar author={op.author} className={classes.avatar} />
- <Paper elevation={1} className={classes.bubble}>
- <header className={classes.header}>
- <div className={classes.title}>
- <Author className={classes.author} author={op.author} />
- <span> commented </span>
- <Date date={op.createdAt} />
- </div>
- {op.edited && <div className={classes.tag}>Edited</div>}
- </header>
- <section className={classes.body}>{op.message}</section>
- </Paper>
- </article>
-);
+function Message({ op }) {
+ const classes = useStyles();
+ return (
+ <article className={classes.container}>
+ <Avatar author={op.author} className={classes.avatar} />
+ <Paper elevation={1} className={classes.bubble}>
+ <header className={classes.header}>
+ <div className={classes.title}>
+ <Author className={classes.author} author={op.author} />
+ <span> commented </span>
+ <Date date={op.createdAt} />
+ </div>
+ {op.edited && <div className={classes.tag}>Edited</div>}
+ </header>
+ <section className={classes.body}>{op.message}</section>
+ </Paper>
+ </article>
+ );
+}
Message.createFragment = gql`
fragment Create on TimelineItem {
@@ -95,4 +98,4 @@ Message.commentFragment = gql`
}
`;
-export default withStyles(styles)(Message);
+export default Message;
diff --git a/webui/src/bug/SetStatus.js b/webui/src/bug/SetStatus.js
index 58b81630..ab591038 100644
--- a/webui/src/bug/SetStatus.js
+++ b/webui/src/bug/SetStatus.js
@@ -1,17 +1,18 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import gql from 'graphql-tag';
import React from 'react';
import Author from '../Author';
import Date from '../Date';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
...theme.typography.body2,
marginLeft: theme.spacing.unit + 40,
},
-});
+}));
-const SetStatus = ({ op, classes }) => {
+function SetStatus({ op }) {
+ const classes = useStyles();
return (
<div className={classes.main}>
<Author author={op.author} bold />
@@ -19,7 +20,7 @@ const SetStatus = ({ op, classes }) => {
<Date date={op.date} />
</div>
);
-};
+}
SetStatus.fragment = gql`
fragment SetStatus on TimelineItem {
@@ -35,4 +36,4 @@ SetStatus.fragment = gql`
}
`;
-export default withStyles(styles)(SetStatus);
+export default SetStatus;
diff --git a/webui/src/bug/SetTitle.js b/webui/src/bug/SetTitle.js
index f5c48568..d9a09ad5 100644
--- a/webui/src/bug/SetTitle.js
+++ b/webui/src/bug/SetTitle.js
@@ -1,10 +1,10 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import gql from 'graphql-tag';
import React from 'react';
import Author from '../Author';
import Date from '../Date';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
...theme.typography.body2,
marginLeft: theme.spacing.unit + 40,
@@ -12,9 +12,10 @@ const styles = theme => ({
bold: {
fontWeight: 'bold',
},
-});
+}));
-const SetTitle = ({ op, classes }) => {
+function SetTitle({ op }) {
+ const classes = useStyles();
return (
<div className={classes.main}>
<Author author={op.author} className={classes.bold} />
@@ -25,7 +26,7 @@ const SetTitle = ({ op, classes }) => {
<Date date={op.date} />
</div>
);
-};
+}
SetTitle.fragment = gql`
fragment SetTitle on TimelineItem {
@@ -42,4 +43,4 @@ SetTitle.fragment = gql`
}
`;
-export default withStyles(styles)(SetTitle);
+export default SetTitle;
diff --git a/webui/src/bug/Timeline.js b/webui/src/bug/Timeline.js
index 3123f45f..d77e0d4b 100644
--- a/webui/src/bug/Timeline.js
+++ b/webui/src/bug/Timeline.js
@@ -1,51 +1,43 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import React from 'react';
import LabelChange from './LabelChange';
import Message from './Message';
import SetStatus from './SetStatus';
import SetTitle from './SetTitle';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
'& > *:not(:last-child)': {
marginBottom: theme.spacing.unit * 2,
},
},
-});
+}));
-class Timeline extends React.Component {
- props: {
- ops: Array,
- fetchMore: any => any,
- classes: any,
- };
+const componentMap = {
+ CreateTimelineItem: Message,
+ AddCommentTimelineItem: Message,
+ LabelChangeTimelineItem: LabelChange,
+ SetTitleTimelineItem: SetTitle,
+ SetStatusTimelineItem: SetStatus,
+};
- render() {
- const { ops, classes } = this.props;
+function Timeline({ ops }) {
+ const classes = useStyles();
- return (
- <div className={classes.main}>
- {ops.map((op, index) => {
- switch (op.__typename) {
- case 'CreateTimelineItem':
- return <Message key={index} op={op} />;
- case 'AddCommentTimelineItem':
- return <Message key={index} op={op} />;
- case 'LabelChangeTimelineItem':
- return <LabelChange key={index} op={op} />;
- case 'SetTitleTimelineItem':
- return <SetTitle key={index} op={op} />;
- case 'SetStatusTimelineItem':
- return <SetStatus key={index} op={op} />;
+ return (
+ <div className={classes.main}>
+ {ops.map((op, index) => {
+ const Component = componentMap[op.__typename];
- default:
- console.log('unsupported operation type ' + op.__typename);
- return null;
- }
- })}
- </div>
- );
- }
+ if (!Component) {
+ console.warn('unsupported operation type ' + op.__typename);
+ return null;
+ }
+
+ return <Component key={index} op={op} />;
+ })}
+ </div>
+ );
}
-export default withStyles(styles)(Timeline);
+export default Timeline;
diff --git a/webui/src/index.js b/webui/src/index.js
index f5d95ccc..885911f5 100644
--- a/webui/src/index.js
+++ b/webui/src/index.js
@@ -1,22 +1,31 @@
+import { install } from '@material-ui/styles';
+import ThemeProvider from '@material-ui/styles/ThemeProvider';
+import { createMuiTheme } from '@material-ui/core/styles';
import ApolloClient from 'apollo-boost';
import React from 'react';
import { ApolloProvider } from 'react-apollo';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
-import App from './App';
+install();
+
+// TODO(sandhose): this is temporary until Material-UI v4 goes out
+const App = React.lazy(() => import('./App'));
+
+const theme = createMuiTheme();
const client = new ApolloClient({
uri: '/graphql',
- connectToDevTools: true,
});
ReactDOM.render(
<ApolloProvider client={client}>
<BrowserRouter>
- <React.Fragment>
- <App />
- </React.Fragment>
+ <ThemeProvider theme={theme}>
+ <React.Suspense fallback={'Loading…'}>
+ <App />
+ </React.Suspense>
+ </ThemeProvider>
</BrowserRouter>
</ApolloProvider>,
document.getElementById('root')
diff --git a/webui/src/list/BugRow.js b/webui/src/list/BugRow.js
index a045770b..e82d81db 100644
--- a/webui/src/list/BugRow.js
+++ b/webui/src/list/BugRow.js
@@ -1,4 +1,4 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import TableCell from '@material-ui/core/TableCell/TableCell';
import TableRow from '@material-ui/core/TableRow/TableRow';
import Tooltip from '@material-ui/core/Tooltip/Tooltip';
@@ -33,7 +33,7 @@ const Status = ({ status, className }) => {
}
};
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
cell: {
display: 'flex',
alignItems: 'center',
@@ -53,36 +53,39 @@ const styles = theme => ({
labels: {
paddingLeft: theme.spacing.unit,
},
-});
+}));
-const BugRow = ({ bug, classes }) => (
- <TableRow hover>
- <TableCell className={classes.cell}>
- <Status status={bug.status} className={classes.status} />
- <div className={classes.expand}>
- <Link to={'bug/' + bug.humanId}>
- <div className={classes.expand}>
- <Typography variant={'title'} className={classes.title}>
- {bug.title}
- </Typography>
- {bug.labels.length > 0 && (
- <span className={classes.labels}>
- {bug.labels.map(l => (
- <Label key={l} label={l} />
- ))}
- </span>
- )}
- </div>
- </Link>
- <Typography color={'textSecondary'}>
- {bug.humanId} opened
- <Date date={bug.createdAt} />
- by {bug.author.displayName}
- </Typography>
- </div>
- </TableCell>
- </TableRow>
-);
+function BugRow({ bug }) {
+ const classes = useStyles();
+ return (
+ <TableRow hover>
+ <TableCell className={classes.cell}>
+ <Status status={bug.status} className={classes.status} />
+ <div className={classes.expand}>
+ <Link to={'bug/' + bug.humanId}>
+ <div className={classes.expand}>
+ <Typography variant={'title'} className={classes.title}>
+ {bug.title}
+ </Typography>
+ {bug.labels.length > 0 && (
+ <span className={classes.labels}>
+ {bug.labels.map(l => (
+ <Label key={l} label={l} />
+ ))}
+ </span>
+ )}
+ </div>
+ </Link>
+ <Typography color={'textSecondary'}>
+ {bug.humanId} opened
+ <Date date={bug.createdAt} />
+ by {bug.author.displayName}
+ </Typography>
+ </div>
+ </TableCell>
+ </TableRow>
+ );
+}
BugRow.fragment = gql`
fragment BugRow on Bug {
@@ -99,4 +102,4 @@ BugRow.fragment = gql`
}
`;
-export default withStyles(styles)(BugRow);
+export default BugRow;
diff --git a/webui/src/list/List.js b/webui/src/list/List.js
index d36be8a1..45c2c963 100644
--- a/webui/src/list/List.js
+++ b/webui/src/list/List.js
@@ -1,134 +1,50 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
+import IconButton from '@material-ui/core/IconButton';
import Table from '@material-ui/core/Table/Table';
import TableBody from '@material-ui/core/TableBody/TableBody';
-import TablePagination from '@material-ui/core/TablePagination/TablePagination';
+import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
+import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
import React from 'react';
import BugRow from './BugRow';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
maxWidth: 600,
margin: 'auto',
marginTop: theme.spacing.unit * 4,
},
-});
-
-class List extends React.Component {
- props: {
- bugs: Array,
- fetchMore: any => any,
- classes: any,
- };
-
- state = {
- page: 0,
- rowsPerPage: 10,
- lastQuery: {},
- };
-
- handleChangePage = (event, page) => {
- const { bugs, fetchMore } = this.props;
- const { rowsPerPage } = this.state;
- const pageInfo = bugs.pageInfo;
-
- if (page === this.state.page + 1) {
- if (!pageInfo.hasNextPage) {
- return;
- }
-
- const variables = {
- after: pageInfo.endCursor,
- first: rowsPerPage,
- };
-
- fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
-
- this.setState({ page, lastQuery: variables });
- return;
- }
-
- if (page === this.state.page - 1) {
- if (!pageInfo.hasPreviousPage) {
- return;
- }
-
- const variables = {
- before: pageInfo.startCursor,
- last: rowsPerPage,
- };
-
- fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
-
- this.setState({ page, lastQuery: variables });
- return;
- }
-
- throw new Error('non neighbour page pagination is not supported');
- };
-
- handleChangeRowsPerPage = event => {
- const { fetchMore } = this.props;
- const { lastQuery } = this.state;
- const rowsPerPage = event.target.value;
-
- const variables = lastQuery;
-
- if (lastQuery.first) {
- variables.first = rowsPerPage;
- } else if (lastQuery.last) {
- variables.last = rowsPerPage;
- } else {
- variables.first = rowsPerPage;
- }
-
- fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
-
- this.setState({ rowsPerPage, lastQuery: variables });
- };
-
- updateQuery = (previousResult, { fetchMoreResult }) => {
- return fetchMoreResult ? fetchMoreResult : previousResult;
- };
-
- render() {
- const { classes, bugs } = this.props;
- const { page, rowsPerPage } = this.state;
-
- return (
- <main className={classes.main}>
- <Table className={classes.table}>
- <TableBody>
- {bugs.edges.map(({ cursor, node }) => (
- <BugRow bug={node} key={cursor} />
- ))}
- </TableBody>
- </Table>
- <TablePagination
- component="div"
- count={bugs.totalCount}
- rowsPerPage={rowsPerPage}
- page={page}
- backIconButtonProps={{
- 'aria-label': 'Previous Page',
- }}
- nextIconButtonProps={{
- 'aria-label': 'Next Page',
- }}
- onChangePage={this.handleChangePage}
- onChangeRowsPerPage={this.handleChangeRowsPerPage}
- />
- </main>
- );
- }
+ pagination: {
+ ...theme.typography.overline,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
+ },
+}));
+
+function List({ bugs, nextPage, prevPage }) {
+ const classes = useStyles();
+ const { hasNextPage, hasPreviousPage } = bugs.pageInfo;
+ return (
+ <main className={classes.main}>
+ <Table className={classes.table}>
+ <TableBody>
+ {bugs.edges.map(({ cursor, node }) => (
+ <BugRow bug={node} key={cursor} />
+ ))}
+ </TableBody>
+ </Table>
+
+ <div className={classes.pagination}>
+ <div>Total: {bugs.totalCount}</div>
+ <IconButton onClick={prevPage} disabled={!hasPreviousPage}>
+ <KeyboardArrowLeft />
+ </IconButton>
+ <IconButton onClick={nextPage} disabled={!hasNextPage}>
+ <KeyboardArrowRight />
+ </IconButton>
+ </div>
+ </main>
+ );
}
-export default withStyles(styles)(List);
+export default List;
diff --git a/webui/src/list/ListQuery.js b/webui/src/list/ListQuery.js
index 9dbe4e53..869bca79 100644
--- a/webui/src/list/ListQuery.js
+++ b/webui/src/list/ListQuery.js
@@ -1,13 +1,13 @@
// @flow
import CircularProgress from '@material-ui/core/CircularProgress';
import gql from 'graphql-tag';
-import React from 'react';
+import React, { useState } from 'react';
import { Query } from 'react-apollo';
import BugRow from './BugRow';
import List from './List';
const QUERY = gql`
- query($first: Int = 10, $last: Int, $after: String, $before: String) {
+ query($first: Int, $last: Int, $after: String, $before: String) {
defaultRepository {
bugs: allBugs(
first: $first
@@ -35,14 +35,31 @@ const QUERY = gql`
${BugRow.fragment}
`;
-const ListQuery = () => (
- <Query query={QUERY}>
- {({ loading, error, data, fetchMore }) => {
- if (loading) return <CircularProgress />;
- if (error) return <p>Error: {error}</p>;
- return <List bugs={data.defaultRepository.bugs} fetchMore={fetchMore} />;
- }}
- </Query>
-);
+function ListQuery() {
+ const [page, setPage] = useState({ first: 10, after: null });
+
+ const perPage = page.first || page.last;
+ const nextPage = pageInfo =>
+ setPage({ first: perPage, after: pageInfo.endCursor });
+ const prevPage = pageInfo =>
+ setPage({ last: perPage, before: pageInfo.startCursor });
+
+ return (
+ <Query query={QUERY} variables={page}>
+ {({ loading, error, data }) => {
+ if (loading) return <CircularProgress />;
+ if (error) return <p>Error: {error}</p>;
+ const bugs = data.defaultRepository.bugs;
+ return (
+ <List
+ bugs={bugs}
+ nextPage={() => nextPage(bugs.pageInfo)}
+ prevPage={() => prevPage(bugs.pageInfo)}
+ />
+ );
+ }}
+ </Query>
+ );
+}
export default ListQuery;