From 6d8845e3f9c74a2ca64c60624eb3078de74d7cd3 Mon Sep 17 00:00:00 2001 From: Victor Viale Date: Sun, 26 Apr 2020 17:51:14 +0200 Subject: [PATCH 1/7] Translate SortableTable to Typescript This also removes the dependency on recompose :wave: --- packages/core/package.json | 3 +- .../{SortableTable.js => SortableTable.tsx} | 263 ++++++++++-------- yarn.lock | 80 +----- 3 files changed, 158 insertions(+), 188 deletions(-) rename packages/core/src/components/SortableTable/{SortableTable.js => SortableTable.tsx} (54%) diff --git a/packages/core/package.json b/packages/core/package.json index aa3efafa95..a5f7c35c16 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -53,8 +53,7 @@ "react-helmet": "5.2.1", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", - "react-sparklines": "^1.7.0", - "recompose": "0.30.0" + "react-sparklines": "^1.7.0" }, "files": [ "dist" diff --git a/packages/core/src/components/SortableTable/SortableTable.js b/packages/core/src/components/SortableTable/SortableTable.tsx similarity index 54% rename from packages/core/src/components/SortableTable/SortableTable.js rename to packages/core/src/components/SortableTable/SortableTable.tsx index ab4e1af2de..4db3aee9ff 100644 --- a/packages/core/src/components/SortableTable/SortableTable.js +++ b/packages/core/src/components/SortableTable/SortableTable.tsx @@ -14,9 +14,7 @@ * limitations under the License. */ -import React from 'react'; -import { pure } from 'recompose'; -import PropTypes from 'prop-types'; +import React, { FC, CSSProperties, memo, useState, useEffect } from 'react'; import { Table, TableBody, @@ -28,11 +26,45 @@ import { Tooltip, } from '@material-ui/core'; +type Column = { + id: string; + label: string; + numeric?: boolean; + disablePadding: boolean; + style: CSSProperties; + // FIXME (@Koroeskohr) + sortValue: (obj: object) => any; +}; + +// XXX (@Koroeskohr): no idea what I did but this typechecks. Need answer for CellContentProps +type Row = { [name in string]: any }; + +type SortHandler = ( + event: React.MouseEvent, + property: string, +) => void; + +type Order = 'asc' | 'desc'; + +type EnhancedTableHeadProps = { + columns: Column[]; + onRequestSort: SortHandler; + order: Order; + orderBy: string; +}; + /** * Table header which supports sorting ascending and desc */ -const EnhancedTableHead = ({ columns, onRequestSort, order, orderBy }) => { - const createSortHandler = property => event => { +const EnhancedTableHead: FC = ({ + columns, + onRequestSort, + order, + orderBy, +}) => { + const createSortHandler = (property: string) => ( + event: React.MouseEvent, + ) => { onRequestSort(event, property); }; @@ -68,25 +100,15 @@ const EnhancedTableHead = ({ columns, onRequestSort, order, orderBy }) => { ); }; -EnhancedTableHead.propTypes = { - columns: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - numeric: PropTypes.bool, - disablePadding: PropTypes.bool, - style: PropTypes.object, - }), - ).isRequired, - onRequestSort: PropTypes.func.isRequired, - order: PropTypes.string.isRequired, - orderBy: PropTypes.string.isRequired, +type CellContentProps = { + // XXX (@Koroeskohr): what am I supposed to use here + data: any | any[]; }; /** * CellContent can be an array or a string */ -const CellContent = ({ data }) => { +const CellContent: FC = ({ data }) => { if (Array.isArray(data)) { return data.map((item, index) => ( @@ -98,11 +120,12 @@ const CellContent = ({ data }) => { return data; }; -CellContent.propTypes = { - data: PropTypes.any.isRequired, +type DataTableCellProps = { + column: Column; + row: Row; }; -const DataTableCell = ({ column, row }) => { +const DataTableCell: FC = ({ column, row }) => { return ( { }; const noop = () => {}; -const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { - const onClick = event => (handleRowClick || noop)(event, row.id); +type DataTableRowProps = { + row: Row; + columns: Column[]; + handleRowClick?: (event: React.MouseEvent, rowId: string) => void; + style?: React.CSSProperties; +}; +const _DataTableRow: FC = ({ + row, + columns, + handleRowClick, + style, +}) => { + const onClick: React.MouseEventHandler = event => + (handleRowClick || noop)(event, row.id); return ( {columns.map(column => ( @@ -124,17 +159,18 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { ))} ); -}); +}; +const DataTableRow = memo(_DataTableRow); /** * Table with sorting capabilites automatic rendering of cells * Note that the objects in props.data needs have an id property * The columns array defines which columns from the data to show. * - * @param {Array[Object]} data A list of data entries, where object properties must + * @param data A list of data entries, where object properties must * be strictly equal to column ids. * - * @param {Array[Object]} columns A list of columns with the following shape: + * @param columns A list of columns with the following shape: * { * // The column identifier must be strictly equal the relevant data entry * // key: @@ -154,17 +190,17 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { * sortValue: (Object) => Any * } * - * @param {String} orderBy The column ID initially used for sorting + * @param orderBy The column ID initially used for sorting * - * @param {String} [dataVersion] A version identifier for the data which *must* + * @param dataVersion A version identifier for the data which *must* * be updated when the contents of the data changes. This can be used for * components where the same SortableTable element will be used to display * variable sets of data. * - * @param {Array[Object]} [footerData] A list of data entries to be placed in + * @param footerData A list of data entries to be placed in * the table footer, which will not be sorted. * - * @param {(String, Event) => Void} [onRowClicked] Get notified when a user clicks + * @param onRowClicked Get notified when a user clicks * on the row. The handler will receive the row id as the first argument, and * the synthetic click event as the second argument. * @@ -188,49 +224,38 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { * ev.preventDefault();}}/>) * } * + * // XXX (@koroeskohr): supposedly this is leftover from your internal doc * @deprecated use shared/components/DataGrid */ -class SortableTable extends React.Component { - static propTypes = { - // TODO: figure out how to make id of the object requried while others are dynamic - data: PropTypes.arrayOf(PropTypes.object).isRequired, - orderBy: PropTypes.string.isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - onRowClicked: PropTypes.func, - dataVersion: PropTypes.string, - }; - constructor(props) { - super(props); - this.handleRowClick = this.handleRowClick.bind(this); +type SortableTableProps = { + data: Row[]; + footerData: Row[]; + orderBy: string; + columns: Column[]; + onRowClicked?: ( + id: string, + event: React.MouseEvent, + ) => void; + dataVersion: string; +}; - this.state = { - orderBy: props.orderBy, - order: 'asc', - data: props.data, - }; - } +type TableState = { + orderBy: string; + order: Order; + data: Row[]; +}; - handleRequestSort = (event, property) => { - event.preventDefault(); - const orderBy = property; - let order = 'desc'; - if (this.state.orderBy === property && this.state.order === 'desc') { - order = 'asc'; - } - this.updateData(this.state.data, orderBy, order); - }; +const SortableTable: FC = props => { + const [state, setState] = useState({ + orderBy: props.orderBy, + order: 'asc', + data: props.data, + }); - handleRowClick = (event, id) => { - if (this.props.onRowClicked) { - this.props.onRowClicked(id, event); - } - }; - - updateData = (data, orderBy, order) => { - const sortValueFn = ( - this.props.columns.filter(col => col.id === orderBy)[0] || {} - ).sortValue; + const updateData = (data: Row[], orderBy: string, order: Order) => { + const sortValueFn = (props.columns.find(col => col.id === orderBy) || {}) + .sortValue; const sortedData = data.slice().sort((a, b) => { const valueA = sortValueFn ? sortValueFn(a) : a[orderBy]; @@ -241,56 +266,70 @@ class SortableTable extends React.Component { if (valueB === '' || valueB === null) return -inc; return valueA < valueB ? -inc : inc; }); - this.setState({ data: sortedData, order, orderBy }); + setState({ data: sortedData, order, orderBy }); }; - UNSAFE_componentWillReceiveProps(props) { - if (props.dataVersion !== this.props.dataVersion) { - this.updateData(props.data, this.state.orderBy, this.state.order); + const handleRequestSort: SortHandler = (event, property) => { + event.preventDefault(); + const orderBy = property; + let order: Order = 'desc'; + if (state.orderBy === property && state.order === 'desc') { + order = 'asc'; } - } + updateData(state.data, orderBy, order); + }; - render() { - const { data, order, orderBy } = this.state; - const { columns, dataVersion, footerData } = this.props; - - let tableFoot = null; - if (footerData && footerData.length > 0) { - tableFoot = ( - - {footerData.map(row => ( - - ))} - - ); + const handleRowClick = (event: React.MouseEvent, id: string): void => { + if (props.onRowClicked) { + props.onRowClicked(id, event); } - return ( - - - - {data.map(row => ( - - ))} - - {tableFoot} -
+ }; + + useEffect(() => { + const { data } = props; + const { orderBy, order } = state; + updateData(data, orderBy, order); + }, [props.dataVersion]); + + const { data, order, orderBy } = state; + const { columns, dataVersion, footerData } = props; + + let tableFoot = null; + if (footerData && footerData.length > 0) { + tableFoot = ( + + {footerData.map(row => ( + + ))} + ); } -} + return ( + + + + {data.map(row => ( + + ))} + + {tableFoot} +
+ ); +}; export default SortableTable; diff --git a/yarn.lock b/yarn.lock index 5126cfa721..df37b90d8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5133,7 +5133,7 @@ arrify@^1.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: +asap@^2.0.0, asap@~2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -6333,11 +6333,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -change-emitter@^0.1.2: - version "0.1.6" - resolved "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" - integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= - character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" @@ -7107,11 +7102,6 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= - core-js@^2.4.0, core-js@^2.5.0: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -9204,19 +9194,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fbjs@^0.8.1: - version "0.8.17" - resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" - integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -10429,11 +10406,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1: - version "2.5.5" - resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== - hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -11543,7 +11515,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -11656,14 +11628,6 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -14363,14 +14327,6 @@ node-fetch@2.6.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-forge@0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" @@ -16569,13 +16525,6 @@ promise.series@^0.2.0: resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - promise@^8.0.3: version "8.1.0" resolved "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -17039,7 +16988,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -17477,18 +17426,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -recompose@0.30.0: - version "0.30.0" - resolved "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0" - integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w== - dependencies: - "@babel/runtime" "^7.0.0" - change-emitter "^0.1.2" - fbjs "^0.8.1" - hoist-non-react-statics "^2.3.1" - react-lifecycles-compat "^3.0.2" - symbol-observable "^1.0.4" - recursive-readdir@2.2.2, recursive-readdir@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" @@ -18395,7 +18332,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -19394,7 +19331,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.4, symbol-observable@^1.1.0: +symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -20021,11 +19958,6 @@ typescript@^3.7.4, typescript@^3.8.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -ua-parser-js@^0.7.18: - version "0.7.21" - resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== - uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" @@ -20708,7 +20640,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: +whatwg-fetch@3.0.0, whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== From 69a3fa79e7fdd4b03a5732ca622b0db02d12005f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 28 Apr 2020 15:54:34 +0200 Subject: [PATCH 2/7] Move backend lint config to cli config --- .eslintrc.js | 6 +-- packages/backend/.eslintrc.js | 5 +- packages/cli/config/eslint.backend.js | 71 ++++++++++++++++++++++++++ packages/test-utils-core/package.json | 3 +- packages/theme/.eslintrc.js | 3 ++ plugins/inventory-backend/.eslintrc.js | 5 +- plugins/tech-radar/.eslintrc.js | 2 +- 7 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 packages/cli/config/eslint.backend.js create mode 100644 packages/theme/.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index da18d19a95..3681612b35 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,4 @@ module.exports = { - extends: [ - require.resolve('@backstage/cli/config/eslint'), - '@spotify/eslint-config-oss', - ], + root: true, + extends: ['@spotify/eslint-config-oss'], }; diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js index f400a039e7..16a033dbc6 100644 --- a/packages/backend/.eslintrc.js +++ b/packages/backend/.eslintrc.js @@ -1,6 +1,3 @@ module.exports = { - rules: { - 'no-console': 0, // Permitted in console programs - 'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()' - }, + extends: [require.resolve('@backstage/cli/config/eslint.backend')], }; diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js new file mode 100644 index 0000000000..ef6e513cf5 --- /dev/null +++ b/packages/cli/config/eslint.backend.js @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + extends: [ + '@spotify/eslint-config-base', + '@spotify/eslint-config-typescript', + 'prettier', + 'prettier/@typescript-eslint', + 'plugin:jest/recommended', + 'plugin:monorepo/recommended', + ], + parser: '@typescript-eslint/parser', + plugins: ['import'], + env: { + jest: true, + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, + ignorePatterns: ['**/dist/**', '**/build/**'], + rules: { + 'no-console': 0, // Permitted in console programs + 'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()' + 'import/no-duplicates': 'warn', + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: false, + optionalDependencies: true, + peerDependencies: true, + bundledDependencies: true, + }, + ], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { vars: 'all', args: 'after-used', ignoreRestSiblings: true }, + ], + }, + overrides: [ + { + files: ['*.test.*', 'src/setupTests.*', 'dev/**'], + rules: { + // Tests are allowed to import dev dependencies + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + optionalDependencies: true, + peerDependencies: true, + bundledDependencies: true, + }, + ], + }, + }, + ], +}; diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index 1b70f4132a..4d695f2d61 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -21,7 +21,8 @@ "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", - "test": "backstage-cli test" + "test": "backstage-cli test", + "clean": "backstage-cli clean" }, "dependencies": { "@testing-library/jest-dom": "^4.2.4", diff --git a/packages/theme/.eslintrc.js b/packages/theme/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/packages/theme/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/inventory-backend/.eslintrc.js b/plugins/inventory-backend/.eslintrc.js index f400a039e7..16a033dbc6 100644 --- a/plugins/inventory-backend/.eslintrc.js +++ b/plugins/inventory-backend/.eslintrc.js @@ -1,6 +1,3 @@ module.exports = { - rules: { - 'no-console': 0, // Permitted in console programs - 'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()' - }, + extends: [require.resolve('@backstage/cli/config/eslint.backend')], }; diff --git a/plugins/tech-radar/.eslintrc.js b/plugins/tech-radar/.eslintrc.js index dd47f29781..13573efa9c 100644 --- a/plugins/tech-radar/.eslintrc.js +++ b/plugins/tech-radar/.eslintrc.js @@ -1,3 +1,3 @@ module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint.js')], + extends: [require.resolve('@backstage/cli/config/eslint')], }; From 77d48d8794669af0f3fb6f36d59ea46e08b6d553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 29 Apr 2020 09:28:50 +0200 Subject: [PATCH 3/7] Add a backend-common package with common concerns such as logging --- packages/backend-common/.eslintrc.js | 3 + packages/backend-common/README.md | 36 +++ packages/backend-common/package.json | 45 ++++ packages/backend-common/src/errors.ts | 36 +++ packages/backend-common/src/index.ts | 19 ++ packages/backend-common/src/logging/index.ts | 17 ++ packages/backend-common/src/logging/logger.ts | 40 ++++ .../src/middleware/errorHandler.test.ts | 48 ++++ .../src/middleware/errorHandler.ts | 53 +++++ .../backend-common/src/middleware/index.ts | 19 ++ .../src/middleware/notFoundHandler.test.ts | 33 +++ .../src/middleware/notFoundHandler.ts | 32 +++ .../middleware/requestLoggingHandler.test.ts | 38 +++ .../src/middleware/requestLoggingHandler.ts | 38 +++ packages/backend-common/src/setupTests.ts | 17 ++ packages/backend-common/tsconfig.json | 15 ++ packages/backend/package.json | 2 + packages/backend/src/index.ts | 19 +- yarn.lock | 225 +++++++++++++++++- 19 files changed, 722 insertions(+), 13 deletions(-) create mode 100644 packages/backend-common/.eslintrc.js create mode 100644 packages/backend-common/README.md create mode 100644 packages/backend-common/package.json create mode 100644 packages/backend-common/src/errors.ts create mode 100644 packages/backend-common/src/index.ts create mode 100644 packages/backend-common/src/logging/index.ts create mode 100644 packages/backend-common/src/logging/logger.ts create mode 100644 packages/backend-common/src/middleware/errorHandler.test.ts create mode 100644 packages/backend-common/src/middleware/errorHandler.ts create mode 100644 packages/backend-common/src/middleware/index.ts create mode 100644 packages/backend-common/src/middleware/notFoundHandler.test.ts create mode 100644 packages/backend-common/src/middleware/notFoundHandler.ts create mode 100644 packages/backend-common/src/middleware/requestLoggingHandler.test.ts create mode 100644 packages/backend-common/src/middleware/requestLoggingHandler.ts create mode 100644 packages/backend-common/src/setupTests.ts create mode 100644 packages/backend-common/tsconfig.json diff --git a/packages/backend-common/.eslintrc.js b/packages/backend-common/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/packages/backend-common/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/packages/backend-common/README.md b/packages/backend-common/README.md new file mode 100644 index 0000000000..972dc41fff --- /dev/null +++ b/packages/backend-common/README.md @@ -0,0 +1,36 @@ +# @backstage/backend-common + +Common functionality library for Backstage backends, implementing logging, +error handling and similar. + +## Usage + +Add the library to your backend package: + +```sh +yarn add @backstage/backend-common +``` + +then make use of the handlers and logger as necessary: + +```typescript +import { + logger, + errorHandler, + notFoundHandler, +} from '@backstage/backend-common'; + +const app = express(); +app.use('/home', myHomeRouter); +app.use(errorHandler()); +app.use(notFoundHandler()); + +app.listen(PORT, () => { + logger.info(`Listening on port ${PORT}`); +}); +``` + +## Documentation + +- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json new file mode 100644 index 0000000000..b2f1bb3735 --- /dev/null +++ b/packages/backend-common/package.json @@ -0,0 +1,45 @@ +{ + "name": "@backstage/backend-common", + "description": "Common functionality library for Backstage backends", + "version": "0.1.1-alpha.4", + "main": "dist", + "private": false, + "publishConfig": { + "access": "public" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/spotify/backstage", + "directory": "packages/backend-common" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "scripts": { + "build": "backstage-cli build-cache -- tsc", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "clean": "backstage-cli clean" + }, + "dependencies": { + "express": "^4.17.1", + "morgan": "^1.10.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.4", + "@types/express": "^4.17.6", + "@types/morgan": "^1.9.0", + "@types/supertest": "^2.0.8", + "get-port": "^5.1.1", + "jest": "^25.1.0", + "jest-fetch-mock": "^3.0.3", + "supertest": "^4.0.2", + "typescript": "^3.8.3" + }, + "files": [ + "dist" + ] +} diff --git a/packages/backend-common/src/errors.ts b/packages/backend-common/src/errors.ts new file mode 100644 index 0000000000..e59cd984d8 --- /dev/null +++ b/packages/backend-common/src/errors.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class StatusCodeError extends Error { + public statusCode: number; + + constructor(statusCode: number, message?: string) { + super(message); + this.statusCode = statusCode; + } +} + +export class InvalidRequestError extends StatusCodeError { + constructor(message?: string) { + super(400, message || 'Invalid Request'); + } +} + +export class NotFoundError extends StatusCodeError { + constructor(message?: string) { + super(404, message || 'Not Found'); + } +} diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts new file mode 100644 index 0000000000..b2c38ab506 --- /dev/null +++ b/packages/backend-common/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './errors'; +export * from './logging'; +export * from './middleware'; diff --git a/packages/backend-common/src/logging/index.ts b/packages/backend-common/src/logging/index.ts new file mode 100644 index 0000000000..6e186fe384 --- /dev/null +++ b/packages/backend-common/src/logging/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './logger'; diff --git a/packages/backend-common/src/logging/logger.ts b/packages/backend-common/src/logging/logger.ts new file mode 100644 index 0000000000..7021422d52 --- /dev/null +++ b/packages/backend-common/src/logging/logger.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import winston, { Logger } from 'winston'; + +export let logger: Logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: + process.env.NODE_ENV === 'production' + ? winston.format.json() + : winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.simple(), + ), + defaultMeta: { service: 'backstage' }, + transports: [ + new winston.transports.Console({ + silent: + process.env.JEST_WORKER_ID !== undefined && !process.env.LOG_LEVEL, + }), + ], +}); + +export function setLogger(newLogger: Logger) { + logger = newLogger; +} diff --git a/packages/backend-common/src/middleware/errorHandler.test.ts b/packages/backend-common/src/middleware/errorHandler.test.ts new file mode 100644 index 0000000000..a6ec2860a8 --- /dev/null +++ b/packages/backend-common/src/middleware/errorHandler.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import express from 'express'; +import request from 'supertest'; +import { errorHandler } from './errorHandler'; +import { StatusCodeError } from '../errors'; + +describe('errorHandler', () => { + it('gives default code and message', async () => { + const app = express(); + app.use('/breaks', () => { + throw new Error('some message'); + }); + app.use(errorHandler()); + + const response = await request(app).get('/breaks'); + + expect(response.status).toBe(500); + expect(response.text).toBe('some message'); + }); + + it('takes code from StatusCodeError', async () => { + const app = express(); + app.use('/breaks', () => { + throw new StatusCodeError(432, 'Some Message'); + }); + app.use(errorHandler()); + + const response = await request(app).get('/breaks'); + + expect(response.status).toBe(432); + expect(response.text).toContain('Some Message'); + }); +}); diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts new file mode 100644 index 0000000000..4491be1283 --- /dev/null +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; + +/** + * Express middleware to handle errors during request processing. + * + * This is commonly the second to last middleware in the chain (before the + * notFoundHandler). It special cases StatusCodeError errors to expose their + * embedded status codes. + * + * + */ +export function errorHandler(): ErrorRequestHandler { + /* eslint-disable @typescript-eslint/no-unused-vars */ + return ( + error: Error, + _request: Request, + response: Response, + _next: NextFunction, + ) => { + const status = getStatusCode(error); + const message = error.message || 'Internal Server Error'; + response.status(status).send(message); + }; +} + +function getStatusCode(error: Error): number { + const errorStatusCode = (error as any).statusCode; + if ( + typeof errorStatusCode === 'number' && + errorStatusCode >= 100 && + errorStatusCode <= 599 + ) { + return errorStatusCode; + } + + return 500; +} diff --git a/packages/backend-common/src/middleware/index.ts b/packages/backend-common/src/middleware/index.ts new file mode 100644 index 0000000000..083b36c3e9 --- /dev/null +++ b/packages/backend-common/src/middleware/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './errorHandler'; +export * from './notFoundHandler'; +export * from './requestLoggingHandler'; diff --git a/packages/backend-common/src/middleware/notFoundHandler.test.ts b/packages/backend-common/src/middleware/notFoundHandler.test.ts new file mode 100644 index 0000000000..65858e8cc1 --- /dev/null +++ b/packages/backend-common/src/middleware/notFoundHandler.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import express from 'express'; +import request from 'supertest'; +import { notFoundHandler } from './notFoundHandler'; + +describe('notFoundHandler', () => { + it('handles only missing routes', async () => { + const app = express(); + app.use('/exists', (_, res) => res.status(200).send()); + app.use(notFoundHandler()); + + const existsResponse = await request(app).get('/exists'); + const doesNotExistResponse = await request(app).get('/doesNotExist'); + + expect(existsResponse.status).toBe(200); + expect(doesNotExistResponse.status).toBe(404); + }); +}); diff --git a/packages/backend-common/src/middleware/notFoundHandler.ts b/packages/backend-common/src/middleware/notFoundHandler.ts new file mode 100644 index 0000000000..7d148ec355 --- /dev/null +++ b/packages/backend-common/src/middleware/notFoundHandler.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NextFunction, Request, RequestHandler, Response } from 'express'; + +/** + * Express middleware to handle requests for missing routes. + * + * Should be used as the very last handler in the chain, as it unconditionally + * returns a 404 status. + * + * @returns An Apollo request handler + */ +export function notFoundHandler(): RequestHandler { + /* eslint-disable @typescript-eslint/no-unused-vars */ + return (_request: Request, response: Response, _next: NextFunction) => { + response.status(404).send('Not Found'); + }; +} diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts new file mode 100644 index 0000000000..35f5c8b978 --- /dev/null +++ b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import express from 'express'; +import request from 'supertest'; +import { requestLoggingHandler } from './requestLoggingHandler'; + +describe('requestLoggingHandler', () => { + it('emits logs for each request', async () => { + const logger = jest.fn(); + + const app = express(); + app.use(requestLoggingHandler(logger)); + app.use('/exists1', (_, res) => res.status(200).send()); + app.use('/exists2', (_, res) => res.status(201).send()); + + const r = request(app); + await r.get('/exists1'); + await r.get('/exists2'); + + expect(logger).toHaveBeenCalledTimes(2); + expect(logger).toHaveBeenNthCalledWith(1, expect.stringContaining('200')); + expect(logger).toHaveBeenNthCalledWith(2, expect.stringContaining('201')); + }); +}); diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.ts b/packages/backend-common/src/middleware/requestLoggingHandler.ts new file mode 100644 index 0000000000..36d0cae769 --- /dev/null +++ b/packages/backend-common/src/middleware/requestLoggingHandler.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from 'express'; +import morgan from 'morgan'; +import { logger as commonLogger } from '../logging'; + +/** + * Logs incoming requests. + * + * @param logger An optional logger to use. If not specified, the default logger is used. + * @returns An Apollo request handler + */ +export function requestLoggingHandler( + logger?: (message: String) => void, +): RequestHandler { + const actualLogger = logger || commonLogger.info; + return morgan('combined', { + stream: { + write(message: String) { + actualLogger(message); + }, + }, + }); +} diff --git a/packages/backend-common/src/setupTests.ts b/packages/backend-common/src/setupTests.ts new file mode 100644 index 0000000000..3fa7cb04b4 --- /dev/null +++ b/packages/backend-common/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require('jest-fetch-mock').enableMocks(); diff --git a/packages/backend-common/tsconfig.json b/packages/backend-common/tsconfig.json new file mode 100644 index 0000000000..b463ac102f --- /dev/null +++ b/packages/backend-common/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist", + "incremental": true, + "sourceMap": true, + "declaration": true, + "strict": true, + "target": "es5", + "module": "commonjs", + "esModuleInterop": true, + "types": ["node", "jest"] + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 0530f04452..3ac52f6c4e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -15,6 +15,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/backend-common": "0.1.1-alpha.4", "@backstage/plugin-inventory-backend": "0.1.1-alpha.4", "compression": "^1.7.4", "cors": "^2.8.5", @@ -28,6 +29,7 @@ "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "@types/helmet": "^0.0.45", + "jest": "^25.1.0", "tsc-watch": "^4.2.3", "typescript": "^3.8.3" }, diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index fd8015c328..c55efd79d2 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -22,12 +22,17 @@ * Happy hacking! */ -import express from 'express'; -import cors from 'cors'; -import helmet from 'helmet'; -import compression from 'compression'; -import { testRouter } from './test'; +import { + errorHandler, + logger, + notFoundHandler, +} from '@backstage/backend-common'; import { router as inventoryRouter } from '@backstage/plugin-inventory-backend'; +import compression from 'compression'; +import cors from 'cors'; +import express from 'express'; +import helmet from 'helmet'; +import { testRouter } from './test'; const DEFAULT_PORT = 7000; @@ -40,7 +45,9 @@ app.use(compression()); app.use(express.json()); app.use('/test', testRouter); app.use('/inventory', inventoryRouter); +app.use(errorHandler()); +app.use(notFoundHandler()); app.listen(PORT, () => { - console.log(`Listening on port ${PORT}`); + logger.info(`Listening on port ${PORT}`); }); diff --git a/yarn.lock b/yarn.lock index 1be5ef5381..cd412c1780 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3857,6 +3857,11 @@ dependencies: "@types/node" "*" +"@types/cookiejar@*": + version "2.1.1" + resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" + integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== + "@types/cors@^2.8.6": version "2.8.6" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz#cfaab33c49c15b1ded32f235111ce9123009bd02" @@ -4121,6 +4126,13 @@ resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== +"@types/morgan@^1.9.0": + version "1.9.0" + resolved "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.0.tgz#342119ae57fe67d36b91537143fc5aef16c2479f" + integrity sha512-warrzirh5dlTMaETytBTKR886pRXwr+SMZD87ZE13gLMR8Pzz69SiYFkvoDaii78qGP1iyBIUYz5GiXyryO//A== + dependencies: + "@types/express" "*" + "@types/node@*", "@types/node@>= 8", "@types/node@^13.7.2": version "13.9.2" resolved "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" @@ -4348,6 +4360,21 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/superagent@*": + version "4.1.7" + resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.7.tgz#a7d92d98c490ee0f802a127fdf149b9a114f77a5" + integrity sha512-JSwNPgRYjIC4pIeOqLwWwfGj6iP1n5NE6kNBEbGx2V8H78xCPwx7QpNp9plaI30+W3cFEzJO7BIIsXE+dbtaGg== + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + +"@types/supertest@^2.0.8": + version "2.0.8" + resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.8.tgz#23801236e2b85204ed771a8e7c40febba7da2bda" + integrity sha512-wcax7/ip4XSSJRLbNzEIUVy2xjcBIZZAuSd2vtltQfRK7kxhx5WMHbLHkYdxN3wuQCrwpYrg86/9byDjPXoGMA== + dependencies: + "@types/superagent" "*" + "@types/tapable@*": version "1.0.5" resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" @@ -5235,7 +5262,7 @@ async-limiter@~1.0.0: resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.6.2: +async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -5756,6 +5783,13 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +basic-auth@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + batch-processor@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" @@ -6726,6 +6760,14 @@ color-string@^1.5.2: color-name "^1.0.0" simple-swizzle "^0.2.2" +color@3.0.x: + version "3.0.0" + resolved "https://registry.npmjs.org/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + color@^3.0.0, color@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" @@ -6734,16 +6776,29 @@ color@^3.0.0, color@^3.1.2: color-convert "^1.9.1" color-string "^1.5.2" +colornames@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" + integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= + colors@1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= -colors@^1.1.2: +colors@^1.1.2, colors@^1.2.1: version "1.4.0" resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== +colorspace@1.1.x: + version "1.1.2" + resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" + integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== + dependencies: + color "3.0.x" + text-hex "1.0.x" + columnify@^1.5.4, columnify@~1.5.4: version "1.5.4" resolved "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" @@ -6833,7 +6888,7 @@ compare-versions@^3.5.1: resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== -component-emitter@^1.2.1: +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -7096,6 +7151,11 @@ cookie@0.4.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookiejar@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -7936,7 +7996,7 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@2.0.0: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -8018,6 +8078,15 @@ dezalgo@^1.0.0, dezalgo@~1.0.3: asap "^2.0.0" wrappy "1" +diagnostics@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" + integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== + dependencies: + colorspace "1.1.x" + enabled "1.0.x" + kuler "1.0.x" + diff-sequences@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" @@ -8387,6 +8456,13 @@ emotion-theming@^10.0.19: "@emotion/weak-memoize" "0.2.5" hoist-non-react-statics "^3.3.0" +enabled@1.0.x: + version "1.0.2" + resolved "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" + integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= + dependencies: + env-variable "0.0.x" + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -8438,6 +8514,11 @@ env-paths@^2.2.0: resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== +env-variable@0.0.x: + version "0.0.6" + resolved "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz#74ab20b3786c545b62b4a4813ab8cf22726c9808" + integrity sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg== + envinfo@^7.3.1: version "7.5.0" resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" @@ -9204,6 +9285,11 @@ fast-levenshtein@~2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-safe-stringify@^2.0.4: + version "2.0.7" + resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + fast-shallow-equal@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b" @@ -9279,6 +9365,11 @@ feature-policy@0.3.0: resolved "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ== +fecha@^2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" + integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -9569,6 +9660,15 @@ fork-ts-checker-webpack-plugin@^4.0.5: tapable "^1.0.0" worker-rpc "^0.1.0" +form-data@^2.3.1: + version "2.5.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -9583,6 +9683,11 @@ format@^0.2.0: resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= +formidable@^1.2.0: + version "1.2.2" + resolved "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -9841,6 +9946,11 @@ get-port@^4.2.0: resolved "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== +get-port@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + get-stdin@7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" @@ -12943,6 +13053,13 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +kuler@1.0.x: + version "1.0.1" + resolved "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" + integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== + dependencies: + colornames "^1.1.1" + last-call-webpack-plugin@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" @@ -13568,6 +13685,17 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +logform@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" + integrity sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^2.3.3" + ms "^2.1.1" + triple-beam "^1.3.0" + loglevel@^1.6.6: version "1.6.7" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" @@ -13943,7 +14071,7 @@ merge@^1.2.1: resolved "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -methods@~1.1.2: +methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -14005,7 +14133,7 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: dependencies: mime-db "1.43.0" -mime@1.6.0: +mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -14225,6 +14353,17 @@ moment@2.24.0: resolved "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +morgan@^1.10.0: + version "1.10.0" + resolved "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" + integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== + dependencies: + basic-auth "~2.0.1" + debug "2.6.9" + depd "~2.0.0" + on-finished "~2.3.0" + on-headers "~1.0.2" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -14997,6 +15136,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0, once@~1.4.0: dependencies: wrappy "1" +one-time@0.0.4: + version "0.0.4" + resolved "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" + integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= + onetime@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -16783,6 +16927,11 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.5.1: + version "6.9.3" + resolved "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" + integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== + qs@^6.6.0: version "6.9.1" resolved "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" @@ -17445,7 +17594,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -18940,6 +19089,11 @@ stack-generator@^2.0.5: dependencies: stackframe "^1.1.1" +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + stack-utils@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" @@ -19379,6 +19533,30 @@ stylis@3.5.0: resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== +superagent@^3.8.3: + version "3.8.3" + resolved "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + +supertest@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz#c2234dbdd6dc79b6f15b99c8d6577b90e4ce3f36" + integrity sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ== + dependencies: + methods "^1.1.2" + superagent "^3.8.3" + supports-color@7.1.0, supports-color@^7.0.0, supports-color@^7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -19631,6 +19809,11 @@ text-extensions@^1.0.0: resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -19881,6 +20064,11 @@ trim@0.0.1: resolved "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + trough@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" @@ -20838,6 +21026,29 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" +winston-transport@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66" + integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A== + dependencies: + readable-stream "^2.3.6" + triple-beam "^1.2.0" + +winston@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" + integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw== + dependencies: + async "^2.6.1" + diagnostics "^1.1.1" + is-stream "^1.1.0" + logform "^2.1.1" + one-time "0.0.4" + readable-stream "^3.1.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.3.0" + word-wrap@^1.0.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" From 04db16099620ce481a5e8473b1aa88ace31ad8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Wed, 29 Apr 2020 23:31:59 +0200 Subject: [PATCH 4/7] Add TabbedCard and CardTab to the exported layouts components --- packages/core/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 62a89fd337..619e672072 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -22,6 +22,7 @@ export { default as ContentHeader } from './layout/ContentHeader/ContentHeader'; export { default as Header } from './layout/Header/Header'; export { default as HeaderLabel } from './layout/HeaderLabel'; export { default as InfoCard } from './layout/InfoCard'; +export { CardTab, TabbedCard } from './layout/TabbedCard'; export { default as ErrorBoundary } from './layout/ErrorBoundary'; export * from './layout/Sidebar'; export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid'; From fd0d4150f0c9da076f2898c493e9782c7cc1c2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 30 Apr 2020 10:49:32 +0200 Subject: [PATCH 5/7] Address comments --- packages/backend-common/README.md | 6 ++-- packages/backend-common/package.json | 2 ++ packages/backend-common/src/index.ts | 1 - packages/backend-common/src/logging/index.ts | 2 +- .../{errors.ts => logging/rootLogger.test.ts} | 35 ++++++++++--------- .../src/logging/{logger.ts => rootLogger.ts} | 10 ++++-- .../src/middleware/errorHandler.test.ts | 4 +-- .../src/middleware/errorHandler.ts | 30 ++++++++++------ .../src/middleware/notFoundHandler.ts | 4 +-- .../middleware/requestLoggingHandler.test.ts | 21 ++++++++--- .../src/middleware/requestLoggingHandler.ts | 18 +++++----- packages/backend/src/index.ts | 6 ++-- yarn.lock | 27 ++++++++------ 13 files changed, 103 insertions(+), 63 deletions(-) rename packages/backend-common/src/{errors.ts => logging/rootLogger.test.ts} (51%) rename packages/backend-common/src/logging/{logger.ts => rootLogger.ts} (85%) diff --git a/packages/backend-common/README.md b/packages/backend-common/README.md index 972dc41fff..32bdd3d2a8 100644 --- a/packages/backend-common/README.md +++ b/packages/backend-common/README.md @@ -15,18 +15,20 @@ then make use of the handlers and logger as necessary: ```typescript import { - logger, errorHandler, + getRootLogger, notFoundHandler, + requestLoggingHandler, } from '@backstage/backend-common'; const app = express(); +app.use(requestLoggingHandler()); app.use('/home', myHomeRouter); app.use(errorHandler()); app.use(notFoundHandler()); app.listen(PORT, () => { - logger.info(`Listening on port ${PORT}`); + getRootLogger().info(`Listening on port ${PORT}`); }); ``` diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b2f1bb3735..eec21063ce 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -31,9 +31,11 @@ "devDependencies": { "@backstage/cli": "^0.1.1-alpha.4", "@types/express": "^4.17.6", + "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", "@types/supertest": "^2.0.8", "get-port": "^5.1.1", + "http-errors": "^1.7.3", "jest": "^25.1.0", "jest-fetch-mock": "^3.0.3", "supertest": "^4.0.2", diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index b2c38ab506..54b9f5c40f 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export * from './errors'; export * from './logging'; export * from './middleware'; diff --git a/packages/backend-common/src/logging/index.ts b/packages/backend-common/src/logging/index.ts index 6e186fe384..06ce76ac54 100644 --- a/packages/backend-common/src/logging/index.ts +++ b/packages/backend-common/src/logging/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './logger'; +export * from './rootLogger'; diff --git a/packages/backend-common/src/errors.ts b/packages/backend-common/src/logging/rootLogger.test.ts similarity index 51% rename from packages/backend-common/src/errors.ts rename to packages/backend-common/src/logging/rootLogger.test.ts index e59cd984d8..cc7b174221 100644 --- a/packages/backend-common/src/errors.ts +++ b/packages/backend-common/src/logging/rootLogger.test.ts @@ -14,23 +14,24 @@ * limitations under the License. */ -export class StatusCodeError extends Error { - public statusCode: number; +import { PassThrough } from 'stream'; +import winston from 'winston'; +import { getRootLogger, setRootLogger } from './rootLogger'; - constructor(statusCode: number, message?: string) { - super(message); - this.statusCode = statusCode; - } -} +describe('rootLogger', () => { + it('can replace the default logger', () => { + const logger = winston.createLogger({ + transports: [ + new winston.transports.Stream({ stream: new PassThrough() }), + ], + }); + jest.spyOn(logger, 'info'); -export class InvalidRequestError extends StatusCodeError { - constructor(message?: string) { - super(400, message || 'Invalid Request'); - } -} + setRootLogger(logger); + getRootLogger().info('testing'); -export class NotFoundError extends StatusCodeError { - constructor(message?: string) { - super(404, message || 'Not Found'); - } -} + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('testing'), + ); + }); +}); diff --git a/packages/backend-common/src/logging/logger.ts b/packages/backend-common/src/logging/rootLogger.ts similarity index 85% rename from packages/backend-common/src/logging/logger.ts rename to packages/backend-common/src/logging/rootLogger.ts index 7021422d52..8058e22947 100644 --- a/packages/backend-common/src/logging/logger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -16,7 +16,7 @@ import winston, { Logger } from 'winston'; -export let logger: Logger = winston.createLogger({ +let rootLogger: Logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: process.env.NODE_ENV === 'production' @@ -35,6 +35,10 @@ export let logger: Logger = winston.createLogger({ ], }); -export function setLogger(newLogger: Logger) { - logger = newLogger; +export function getRootLogger(): Logger { + return rootLogger; +} + +export function setRootLogger(newLogger: Logger) { + rootLogger = newLogger; } diff --git a/packages/backend-common/src/middleware/errorHandler.test.ts b/packages/backend-common/src/middleware/errorHandler.test.ts index a6ec2860a8..f90794b07b 100644 --- a/packages/backend-common/src/middleware/errorHandler.test.ts +++ b/packages/backend-common/src/middleware/errorHandler.test.ts @@ -15,9 +15,9 @@ */ import express from 'express'; +import createError from 'http-errors'; import request from 'supertest'; import { errorHandler } from './errorHandler'; -import { StatusCodeError } from '../errors'; describe('errorHandler', () => { it('gives default code and message', async () => { @@ -36,7 +36,7 @@ describe('errorHandler', () => { it('takes code from StatusCodeError', async () => { const app = express(); app.use('/breaks', () => { - throw new StatusCodeError(432, 'Some Message'); + throw createError(432, 'Some Message'); }); app.use(errorHandler()); diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 4491be1283..655d93ad91 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -20,10 +20,15 @@ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; * Express middleware to handle errors during request processing. * * This is commonly the second to last middleware in the chain (before the - * notFoundHandler). It special cases StatusCodeError errors to expose their - * embedded status codes. + * notFoundHandler). * + * Its primary purpose is not to do translation of business logic exceptions, + * but rather to be a gobal catch-all for uncaught "fatal" errors that are + * expected to result in a 500 error. However, it also does handle some common + * error types (such as http-error exceptions) and returns the enclosed status + * code accordingly. * + * @returns An Express error request handler */ export function errorHandler(): ErrorRequestHandler { /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -34,19 +39,24 @@ export function errorHandler(): ErrorRequestHandler { _next: NextFunction, ) => { const status = getStatusCode(error); - const message = error.message || 'Internal Server Error'; + const message = error.message; response.status(status).send(message); }; } function getStatusCode(error: Error): number { - const errorStatusCode = (error as any).statusCode; - if ( - typeof errorStatusCode === 'number' && - errorStatusCode >= 100 && - errorStatusCode <= 599 - ) { - return errorStatusCode; + const knownStatusCodeFields = ['statusCode', 'status']; + + for (const field of knownStatusCodeFields) { + const statusCode = (error as any)[field]; + if ( + typeof statusCode === 'number' && + (statusCode | 0) === statusCode && // is whole integer + statusCode >= 100 && + statusCode <= 599 + ) { + return statusCode; + } } return 500; diff --git a/packages/backend-common/src/middleware/notFoundHandler.ts b/packages/backend-common/src/middleware/notFoundHandler.ts index 7d148ec355..19dd130c64 100644 --- a/packages/backend-common/src/middleware/notFoundHandler.ts +++ b/packages/backend-common/src/middleware/notFoundHandler.ts @@ -22,11 +22,11 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'; * Should be used as the very last handler in the chain, as it unconditionally * returns a 404 status. * - * @returns An Apollo request handler + * @returns An Express request handler */ export function notFoundHandler(): RequestHandler { /* eslint-disable @typescript-eslint/no-unused-vars */ return (_request: Request, response: Response, _next: NextFunction) => { - response.status(404).send('Not Found'); + response.status(404).send(); }; } diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts index 35f5c8b978..191949bebe 100644 --- a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts +++ b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts @@ -15,12 +15,19 @@ */ import express from 'express'; +import { PassThrough } from 'stream'; import request from 'supertest'; +import winston from 'winston'; import { requestLoggingHandler } from './requestLoggingHandler'; describe('requestLoggingHandler', () => { it('emits logs for each request', async () => { - const logger = jest.fn(); + const logger = winston.createLogger({ + transports: [ + new winston.transports.Stream({ stream: new PassThrough() }), + ], + }); + jest.spyOn(logger, 'info'); const app = express(); app.use(requestLoggingHandler(logger)); @@ -31,8 +38,14 @@ describe('requestLoggingHandler', () => { await r.get('/exists1'); await r.get('/exists2'); - expect(logger).toHaveBeenCalledTimes(2); - expect(logger).toHaveBeenNthCalledWith(1, expect.stringContaining('200')); - expect(logger).toHaveBeenNthCalledWith(2, expect.stringContaining('201')); + expect(logger.info).toHaveBeenCalledTimes(2); + expect(logger.info).toHaveBeenNthCalledWith( + 1, + expect.stringContaining('200'), + ); + expect(logger.info).toHaveBeenNthCalledWith( + 2, + expect.stringContaining('201'), + ); }); }); diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.ts b/packages/backend-common/src/middleware/requestLoggingHandler.ts index 36d0cae769..6604ec245c 100644 --- a/packages/backend-common/src/middleware/requestLoggingHandler.ts +++ b/packages/backend-common/src/middleware/requestLoggingHandler.ts @@ -15,23 +15,25 @@ */ import { RequestHandler } from 'express'; +import { Logger } from 'winston'; import morgan from 'morgan'; -import { logger as commonLogger } from '../logging'; +import { getRootLogger } from '../logging'; /** * Logs incoming requests. * - * @param logger An optional logger to use. If not specified, the default logger is used. - * @returns An Apollo request handler + * @param logger An optional logger to use. If not specified, the root logger will be used. + * @returns An Express request handler */ -export function requestLoggingHandler( - logger?: (message: String) => void, -): RequestHandler { - const actualLogger = logger || commonLogger.info; +export function requestLoggingHandler(logger?: Logger): RequestHandler { + const actualLogger = (logger || getRootLogger()).child({ + type: 'incomingRequest', + }); + return morgan('combined', { stream: { write(message: String) { - actualLogger(message); + actualLogger.info(message); }, }, }); diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index c55efd79d2..e0fd44430d 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -24,8 +24,9 @@ import { errorHandler, - logger, + getRootLogger, notFoundHandler, + requestLoggingHandler, } from '@backstage/backend-common'; import { router as inventoryRouter } from '@backstage/plugin-inventory-backend'; import compression from 'compression'; @@ -43,11 +44,12 @@ app.use(helmet()); app.use(cors()); app.use(compression()); app.use(express.json()); +app.use(requestLoggingHandler()); app.use('/test', testRouter); app.use('/inventory', inventoryRouter); app.use(errorHandler()); app.use(notFoundHandler()); app.listen(PORT, () => { - logger.info(`Listening on port ${PORT}`); + getRootLogger().info(`Listening on port ${PORT}`); }); diff --git a/yarn.lock b/yarn.lock index cd412c1780..0cf9fa778f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3999,6 +3999,11 @@ "@types/tapable" "*" "@types/webpack" "*" +"@types/http-errors@^1.6.3": + version "1.6.3" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.6.3.tgz#619a55768eab98299e8f76747339f3373f134e69" + integrity sha512-4KCE/agIcoQ9bIfa4sBxbZdnORzRjIw8JNQPLfqoNv7wQl/8f8mRbW68Q8wBsQFoJkPUHGlQYZ9sqi5WpfGSEQ== + "@types/http-proxy-middleware@*": version "0.19.3" resolved "https://registry.npmjs.org/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03" @@ -10795,17 +10800,7 @@ http-errors@1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: +http-errors@^1.7.3, http-errors@~1.7.2: version "1.7.3" resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -10816,6 +10811,16 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + "http-parser-js@>=0.4.0 <0.4.11": version "0.4.10" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" From ae0d3994e98aaf492afd3bd461e73c10ef2dbc85 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 30 Apr 2020 12:23:30 +0200 Subject: [PATCH 6/7] packages/core: remove old widget related things --- packages/core/src/api/api.ts | 5 -- packages/core/src/api/plugin/Plugin.tsx | 11 --- packages/core/src/api/plugin/types.ts | 7 -- .../src/api/widgetView/WidgetViewBuilder.tsx | 83 ------------------- packages/core/src/api/widgetView/types.ts | 26 ------ .../DefaultWidgetView/DefaultWidgetView.tsx | 48 ----------- .../src/components/DefaultWidgetView/index.ts | 17 ---- .../core/src/layout/InfoCard/InfoCard.tsx | 8 -- 8 files changed, 205 deletions(-) delete mode 100644 packages/core/src/api/widgetView/WidgetViewBuilder.tsx delete mode 100644 packages/core/src/api/widgetView/types.ts delete mode 100644 packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx delete mode 100644 packages/core/src/components/DefaultWidgetView/index.ts diff --git a/packages/core/src/api/api.ts b/packages/core/src/api/api.ts index abaa28cbbf..cafd0af126 100644 --- a/packages/core/src/api/api.ts +++ b/packages/core/src/api/api.ts @@ -16,7 +16,6 @@ import ApiRef, { ApiRefConfig } from './apis/ApiRef'; import AppBuilder from './app/AppBuilder'; -import WidgetViewBuilder from './widgetView/WidgetViewBuilder'; import BackstagePlugin, { PluginConfig } from './plugin/Plugin'; export function createApp() { @@ -27,10 +26,6 @@ export function createApiRef(config: ApiRefConfig) { return new ApiRef(config); } -export function createWidgetView() { - return new WidgetViewBuilder(); -} - export function createPlugin(config: PluginConfig): BackstagePlugin { return new BackstagePlugin(config); } diff --git a/packages/core/src/api/plugin/Plugin.tsx b/packages/core/src/api/plugin/Plugin.tsx index 47f8331e10..a8b694ae8d 100644 --- a/packages/core/src/api/plugin/Plugin.tsx +++ b/packages/core/src/api/plugin/Plugin.tsx @@ -22,7 +22,6 @@ import { FeatureFlagName, } from './types'; import { validateBrowserCompat, validateFlagName } from 'api/app/FeatureFlags'; -import { Widget } from 'api/widgetView/types'; export type PluginConfig = { id: string; @@ -31,7 +30,6 @@ export type PluginConfig = { export type PluginHooks = { router: RouterHooks; - widgets: WidgetHooks; featureFlags: FeatureFlagsHooks; }; @@ -49,10 +47,6 @@ export type RouterHooks = { ): void; }; -export type WidgetHooks = { - add(widget: Widget): void; -}; - export type FeatureFlagsHooks = { register(name: FeatureFlagName): void; }; @@ -88,11 +82,6 @@ export default class Plugin { outputs.push({ type: 'redirect-route', path, target, options }); }, }, - widgets: { - add(widget: Widget) { - outputs.push({ type: 'widget', widget }); - }, - }, featureFlags: { register(name) { validateBrowserCompat(); diff --git a/packages/core/src/api/plugin/types.ts b/packages/core/src/api/plugin/types.ts index 3192dfd095..c4ac426e5c 100644 --- a/packages/core/src/api/plugin/types.ts +++ b/packages/core/src/api/plugin/types.ts @@ -15,7 +15,6 @@ */ import { ComponentType } from 'react'; -import { Widget } from 'api/widgetView/types'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. @@ -38,11 +37,6 @@ export type RedirectRouteOutput = { options?: RouteOptions; }; -export type WidgetOutput = { - type: 'widget'; - widget: Widget; -}; - export type FeatureFlagName = string; export type FeatureFlagOutput = { @@ -53,5 +47,4 @@ export type FeatureFlagOutput = { export type PluginOutput = | RouteOutput | RedirectRouteOutput - | WidgetOutput | FeatureFlagOutput; diff --git a/packages/core/src/api/widgetView/WidgetViewBuilder.tsx b/packages/core/src/api/widgetView/WidgetViewBuilder.tsx deleted file mode 100644 index 0bf873df86..0000000000 --- a/packages/core/src/api/widgetView/WidgetViewBuilder.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType } from 'react'; -import { AppComponentBuilder } from 'api/app/types'; -import { Widget } from './types'; -import BackstagePlugin from 'api/plugin/Plugin'; -import DefaultWidgetView from 'components/DefaultWidgetView'; - -type WidgetViewRegistration = - | { - type: 'component'; - widget: Widget; - } - | { - type: 'plugin'; - plugin: BackstagePlugin; - }; - -export default class WidgetViewBuilder extends AppComponentBuilder { - private readonly registrations = new Array(); - private output?: ComponentType; - - add(widget: Widget): WidgetViewBuilder { - this.registrations.push({ type: 'component', widget }); - return this; - } - - register(plugin: BackstagePlugin): WidgetViewBuilder { - this.registrations.push({ type: 'plugin', plugin }); - return this; - } - - build(): ComponentType { - if (this.output) { - return this.output; - } - - const widgets = new Array(); - - for (const reg of this.registrations) { - switch (reg.type) { - case 'component': - widgets.push(reg.widget); - break; - case 'plugin': - { - let added = false; - for (const output of reg.plugin.output()) { - if (output.type === 'widget') { - widgets.push(output.widget); - added = true; - } - } - if (!added) { - throw new Error( - `Plugin ${reg.plugin} was registered as widget provider, but did not provide any widgets`, - ); - } - } - break; - default: - throw new Error(`Unknown WidgetViewBuilder registration`); - } - } - - this.output = () => ; - return this.output; - } -} diff --git a/packages/core/src/api/widgetView/types.ts b/packages/core/src/api/widgetView/types.ts deleted file mode 100644 index 9e066225ab..0000000000 --- a/packages/core/src/api/widgetView/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ComponentType } from 'react'; - -export type Widget = { - size: 4 | 6 | 8 | 12; - component: ComponentType; -}; - -export type WidgetViewProps = { - widgets: Widget[]; -}; diff --git a/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx b/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx deleted file mode 100644 index 990ecb0e92..0000000000 --- a/packages/core/src/components/DefaultWidgetView/DefaultWidgetView.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { FC } from 'react'; -import { Grid, Paper, makeStyles, Theme } from '@material-ui/core'; -import { WidgetViewProps } from 'api/widgetView/types'; - -const useStyles = makeStyles(theme => ({ - root: { - padding: theme.spacing(2), - }, - widgetWrapper: { - padding: theme.spacing(2), - }, -})); - -const WidgetViewComponent: FC = ({ widgets }) => { - const classes = useStyles(); - - return ( -
- - {widgets.map(({ size, component: WidgetComponent }, index) => ( - - - - - - ))} - -
- ); -}; - -export default WidgetViewComponent; diff --git a/packages/core/src/components/DefaultWidgetView/index.ts b/packages/core/src/components/DefaultWidgetView/index.ts deleted file mode 100644 index b4aaece4cb..0000000000 --- a/packages/core/src/components/DefaultWidgetView/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { default } from './DefaultWidgetView'; diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index d645b25aaa..fe2bbfa1ce 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -52,9 +52,6 @@ const VARIANT_STYLES = { display: 'flex', flexDirection: 'column', }, - widget: { - height: 430, - }, fullHeight: { height: '100%', }, @@ -79,11 +76,6 @@ const VARIANT_STYLES = { }, }, cardContent: { - widget: { - overflowY: 'auto', - height: 332, - width: '100%', - }, fullHeight: { height: 'calc(100% - 50px)', }, From e953259871215489f4ef1ea1a2150ea9888dee7e Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 30 Apr 2020 21:12:51 +0200 Subject: [PATCH 7/7] 404 page (#681) * core: Implemented error page, closes #170 * Swap margin to padding * more ui work * Convert to TypeScript, functioncal components, use useStyles/makeStyles, clean up some styles and use components from material ui * fixup * Fix lint errors * Remove unused theme consumer * Decrease max size of image * Remove BackstageTheme added by mistake on rebase * Add space to text * Use BackstageTheme type and change color from palette * Add test Co-authored-by: Ryan Pannell --- packages/core/src/api/app/AppBuilder.tsx | 8 +- .../src/layout/ErrorPage/ErrorPage.test.tsx | 38 ++++ .../core/src/layout/ErrorPage/ErrorPage.tsx | 68 ++++++++ packages/core/src/layout/ErrorPage/MicDrop.js | 162 ++++++++++++++++++ packages/core/src/layout/ErrorPage/index.ts | 17 ++ 5 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/layout/ErrorPage/ErrorPage.test.tsx create mode 100644 packages/core/src/layout/ErrorPage/ErrorPage.tsx create mode 100644 packages/core/src/layout/ErrorPage/MicDrop.js create mode 100644 packages/core/src/layout/ErrorPage/index.ts diff --git a/packages/core/src/api/app/AppBuilder.tsx b/packages/core/src/api/app/AppBuilder.tsx index ccc1303ccb..0af21f5c8d 100644 --- a/packages/core/src/api/app/AppBuilder.tsx +++ b/packages/core/src/api/app/AppBuilder.tsx @@ -21,6 +21,8 @@ import { App } from './types'; import BackstagePlugin from 'api/plugin/Plugin'; import { FeatureFlagsRegistryItem } from './FeatureFlags'; import { featureFlagsApiRef } from 'api/apis/definitions/featureFlags'; +import ErrorPage from '../../layout/ErrorPage'; + import { IconComponent, SystemIcons, @@ -115,7 +117,11 @@ export default class AppBuilder { let rendered = ( {routes} - 404 Not Found} /> + ( + + )} + /> ); diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx new file mode 100644 index 0000000000..1309fdf474 --- /dev/null +++ b/packages/core/src/layout/ErrorPage/ErrorPage.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import ErrorPage from './ErrorPage'; +import { wrapInThemedTestApp } from '@backstage/test-utils'; + +describe('', () => { + it('should render with status code, status message and go back link', () => { + const rendered = render( + wrapInThemedTestApp( + {} }} + />, + ), + ); + rendered.getByText(/page not found/i); + rendered.getByText(/404/i); + rendered.getByText(/Looks like someone dropped the mic!/i); + expect(rendered.getByTestId('go-back-link')).toBeDefined(); + }); +}); diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.tsx new file mode 100644 index 0000000000..ea593dbb49 --- /dev/null +++ b/packages/core/src/layout/ErrorPage/ErrorPage.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Typography, Link, Grid } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; +import MicDrop from './MicDrop'; + +interface IErrorPageProps { + status: string; + statusMessage: string; + history: { + goBack: () => void; + }; +} + +const useStyles = makeStyles(theme => ({ + container: { + padding: theme.spacing(8), + }, + title: { + paddingBottom: theme.spacing(5), + }, + subtitle: { + color: theme.palette.textSubtle, + }, +})); + +const ErrorPage = ({ status, statusMessage, history }: IErrorPageProps) => { + const classes = useStyles(); + + return ( + + + + + ERROR {status}: {statusMessage} + + + Looks like someone dropped the mic! + + + + Go back + + ... or if you think this is a bug, please file an{' '} + issue. + + + + ); +}; + +export default ErrorPage; diff --git a/packages/core/src/layout/ErrorPage/MicDrop.js b/packages/core/src/layout/ErrorPage/MicDrop.js new file mode 100644 index 0000000000..f951830574 --- /dev/null +++ b/packages/core/src/layout/ErrorPage/MicDrop.js @@ -0,0 +1,162 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + micDrop: { + maxWidth: '60%', + bottom: 10, + right: 10, + position: 'absolute', + }, +}); + +const MicDrop = () => { + const classes = useStyles(); + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default MicDrop; diff --git a/packages/core/src/layout/ErrorPage/index.ts b/packages/core/src/layout/ErrorPage/index.ts new file mode 100644 index 0000000000..872e5cce2d --- /dev/null +++ b/packages/core/src/layout/ErrorPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default } from './ErrorPage';