From 4ef2b51e50ae1756f3ae72537c31684bd4210e06 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Mon, 14 Feb 2022 21:13:59 -0600 Subject: [PATCH 01/46] install @storybook/addon-react-native-web --- .storybook/main.js | 1 + package-lock.json | 6 ++++++ package.json | 1 + 3 files changed, 8 insertions(+) diff --git a/.storybook/main.js b/.storybook/main.js index fcf0b3b46879..4bdb815522ae 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -6,6 +6,7 @@ module.exports = { addons: [ '@storybook/addon-essentials', '@storybook/addon-a11y', + '@storybook/addon-react-native-web', ], staticDirs: [ './public', diff --git a/package-lock.json b/package-lock.json index 4113115966f5..94074cabd4ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8454,6 +8454,12 @@ } } }, + "@storybook/addon-react-native-web": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@storybook/addon-react-native-web/-/addon-react-native-web-0.0.18.tgz", + "integrity": "sha512-tMZumiF+Dgk7sngMWFrbDdBN3h5C001traYM46CqNQvtJoZvwYe2MPGEeWy3wk+5D8vOv8TFw2i96f3wwfralg==", + "dev": true + }, "@storybook/addon-toolbars": { "version": "6.4.12", "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-6.4.12.tgz", diff --git a/package.json b/package.json index d00f22c9dcd8..709166612bb4 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "@react-native-community/eslint-config": "^2.0.0", "@storybook/addon-a11y": "^6.4.12", "@storybook/addon-essentials": "^6.4.12", + "@storybook/addon-react-native-web": "0.0.18", "@storybook/addons": "^6.4.12", "@storybook/react": "^6.4.12", "@storybook/theming": "^6.4.12", From def46e5c2b7897b1def465a2d1244b6d8ed7b921 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Mon, 14 Feb 2022 21:16:00 -0600 Subject: [PATCH 02/46] align error text to label --- src/components/InlineErrorText.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/InlineErrorText.js b/src/components/InlineErrorText.js index 90f6838e19bc..7ac4192585de 100644 --- a/src/components/InlineErrorText.js +++ b/src/components/InlineErrorText.js @@ -19,7 +19,7 @@ const InlineErrorText = (props) => { } return ( - {props.children} + {props.children} ); }; From 01fa79541b6b07b940e1edef22f07fe100935b1f Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Mon, 14 Feb 2022 21:35:27 -0600 Subject: [PATCH 03/46] Refactor BasePicker to Picker new props --- src/components/Picker/BasePicker/index.js | 57 +++++++++++++++-------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index d283bf248398..561257a67a6c 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -1,30 +1,49 @@ import React from 'react'; import RNPickerSelect from 'react-native-picker-select'; +import _ from 'underscore'; import styles from '../../../styles/styles'; import * as basePickerPropTypes from './basePickerPropTypes'; import basePickerStyles from './basePickerStyles'; -const BasePicker = props => ( - props.icon(props.size)} - disabled={props.disabled} - fixAndroidTouchableBug - onOpen={props.onOpen} - onClose={props.onClose} - pickerProps={{ - onFocus: props.onOpen, - onBlur: props.onClose, - }} - /> -); +class BasePicker extends React.Component { + constructor(props) { + super(props); + this.state = { + selectValue: this.props.value || this.props.defaultValue || '', + }; + } + + handleChange = (value) => { + this.props.onChange(value); + this.setState({selectValue: value}); + } + + render() { + const hasError = !_.isEmpty(this.props.errorText); + return ( + this.props.icon(this.props.size)} + disabled={this.props.disabled} + fixAndroidTouchableBug + onOpen={this.props.onOpen} + onClose={this.props.onClose} + pickerProps={{ + onFocus: this.props.onOpen, + onBlur: this.props.onBlur, + ref: this.props.innerRef, + }} + /> + ); + } +} BasePicker.propTypes = basePickerPropTypes.propTypes; BasePicker.defaultProps = basePickerPropTypes.defaultProps; From a5db0dbdb35f86e15f8fcc31e120270127533a9d Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 15 Feb 2022 14:25:01 -0600 Subject: [PATCH 04/46] Create BasePicker's Web version file --- src/components/Picker/BasePicker/index.web.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/components/Picker/BasePicker/index.web.js diff --git a/src/components/Picker/BasePicker/index.web.js b/src/components/Picker/BasePicker/index.web.js new file mode 100644 index 000000000000..561257a67a6c --- /dev/null +++ b/src/components/Picker/BasePicker/index.web.js @@ -0,0 +1,52 @@ +import React from 'react'; +import RNPickerSelect from 'react-native-picker-select'; +import _ from 'underscore'; + +import styles from '../../../styles/styles'; +import * as basePickerPropTypes from './basePickerPropTypes'; +import basePickerStyles from './basePickerStyles'; + +class BasePicker extends React.Component { + constructor(props) { + super(props); + + this.state = { + selectValue: this.props.value || this.props.defaultValue || '', + }; + } + + handleChange = (value) => { + this.props.onChange(value); + this.setState({selectValue: value}); + } + + render() { + const hasError = !_.isEmpty(this.props.errorText); + return ( + this.props.icon(this.props.size)} + disabled={this.props.disabled} + fixAndroidTouchableBug + onOpen={this.props.onOpen} + onClose={this.props.onClose} + pickerProps={{ + onFocus: this.props.onOpen, + onBlur: this.props.onBlur, + ref: this.props.innerRef, + }} + /> + ); + } +} + +BasePicker.propTypes = basePickerPropTypes.propTypes; +BasePicker.defaultProps = basePickerPropTypes.defaultProps; +BasePicker.displayName = 'BasePicker'; + +export default BasePicker; From 68d619fcfd2a0e4094159b7cb4510b434d2ff9ad Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 15 Feb 2022 14:33:13 -0600 Subject: [PATCH 05/46] Extend focus method of View ref to scroll --- src/components/Picker/BasePicker/index.web.js | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/components/Picker/BasePicker/index.web.js b/src/components/Picker/BasePicker/index.web.js index 561257a67a6c..ee98c004eddd 100644 --- a/src/components/Picker/BasePicker/index.web.js +++ b/src/components/Picker/BasePicker/index.web.js @@ -1,5 +1,6 @@ import React from 'react'; import RNPickerSelect from 'react-native-picker-select'; +import {View} from 'react-native'; import _ from 'underscore'; import styles from '../../../styles/styles'; @@ -15,6 +16,29 @@ class BasePicker extends React.Component { }; } + componentDidMount() { + const viewRef = this.viewRef; + if (!viewRef) { + return; + } + + if (typeof this.props.innerRef !== 'function') { + return; + } + + // ! If RNPickerSelect pickerProps returns the tag's ref, the implementation of this file's Web version would be no longer neccesary, - // ! check for any update on https://github.com/lawnstarter/react-native-picker-select or https://github.com/react-native-picker/picker - // View ref will return a
, apply 'focus()' on it will not scroll to it. - // Applying 'scrollIntoView(false)' will scroll to element. + // ! Picker from https://github.com/react-native-picker/picker is being used inside RNPickerSelect. Picker 'ref' prop does not work on Web + // ! therefore it can not get '' ref that Picker renders, so a pull request https://github.com/react-native-picker/picker/pull/376 - // ! has been made with a potential fix to this issue , if Picker returns a 'ref' the implementation to this file would not be neccesary. - // An alternative is getting a View container ref. View ref will return a
, + // 'ref' is not exposed in Picker to focus on it. An alternative is getting a View container ref. View ref will return a
, // apply 'focus()' on it will not scroll to it. Applying 'scrollIntoView(false)' will scroll to element. https://developer.mozilla.org/es/docs/Web/API/Element/scrollIntoView const originalFocus = viewRef.focus; viewRef.focus = function () { From 094efbaa69f6512052c47359ce47d2b3e542461e Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Fri, 18 Feb 2022 11:23:03 -0600 Subject: [PATCH 15/46] convert index to index.native, index.web to index --- src/components/Picker/BasePicker/index.js | 59 ++++++++++----- .../Picker/BasePicker/index.native.js | 52 +++++++++++++ src/components/Picker/BasePicker/index.web.js | 75 ------------------- 3 files changed, 93 insertions(+), 93 deletions(-) create mode 100644 src/components/Picker/BasePicker/index.native.js delete mode 100644 src/components/Picker/BasePicker/index.web.js diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index cb5f264e4942..e01a885f4c37 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -1,5 +1,6 @@ import React from 'react'; import RNPickerSelect from 'react-native-picker-select'; +import {View} from 'react-native'; import _ from 'underscore'; import styles from '../../../styles/styles'; @@ -15,6 +16,27 @@ class BasePicker extends React.Component { }; } + componentDidMount() { + const viewRef = this.viewRef; + if (!viewRef) { + return; + } + + if (typeof this.props.innerRef !== 'function') { + return; + } + + // 'ref' is not exposed in Picker to focus on it. An alternative is getting a View container ref. View ref will return a
, + // apply 'focus()' on it will not scroll to it. Applying 'scrollIntoView(false)' will scroll to element. https://developer.mozilla.org/es/docs/Web/API/Element/scrollIntoView + const originalFocus = viewRef.focus; + viewRef.focus = function () { + // Execute original focus() using apply on ref context to prevent infinite focus() call stack and preserve original focus() functionality + originalFocus.apply(viewRef); + viewRef.scrollIntoView(false); + }; + this.props.innerRef(viewRef); + } + handleChange = (value) => { this.props.onChange(value); this.setState({selectValue: value}); @@ -23,24 +45,25 @@ class BasePicker extends React.Component { render() { const hasError = !_.isEmpty(this.props.errorText); return ( - this.props.icon(this.props.size)} - disabled={this.props.disabled} - fixAndroidTouchableBug - onOpen={this.props.onOpen} - onClose={this.props.onClose} - pickerProps={{ - onFocus: this.props.onOpen, - onBlur: this.props.onBlur, - ref: this.props.innerRef, - }} - /> + this.viewRef = ref}> + this.props.icon(this.props.size)} + disabled={this.props.disabled} + fixAndroidTouchableBug + onOpen={this.props.onOpen} + onClose={this.props.onClose} + pickerProps={{ + onFocus: this.props.onOpen, + onBlur: this.props.onBlur, + }} + /> + ); } } diff --git a/src/components/Picker/BasePicker/index.native.js b/src/components/Picker/BasePicker/index.native.js new file mode 100644 index 000000000000..cb5f264e4942 --- /dev/null +++ b/src/components/Picker/BasePicker/index.native.js @@ -0,0 +1,52 @@ +import React from 'react'; +import RNPickerSelect from 'react-native-picker-select'; +import _ from 'underscore'; + +import styles from '../../../styles/styles'; +import * as basePickerPropTypes from './basePickerPropTypes'; +import basePickerStyles from './basePickerStyles'; + +class BasePicker extends React.Component { + constructor(props) { + super(props); + + this.state = { + selectValue: this.props.value || this.props.defaultValue, + }; + } + + handleChange = (value) => { + this.props.onChange(value); + this.setState({selectValue: value}); + } + + render() { + const hasError = !_.isEmpty(this.props.errorText); + return ( + this.props.icon(this.props.size)} + disabled={this.props.disabled} + fixAndroidTouchableBug + onOpen={this.props.onOpen} + onClose={this.props.onClose} + pickerProps={{ + onFocus: this.props.onOpen, + onBlur: this.props.onBlur, + ref: this.props.innerRef, + }} + /> + ); + } +} + +BasePicker.propTypes = basePickerPropTypes.propTypes; +BasePicker.defaultProps = basePickerPropTypes.defaultProps; +BasePicker.displayName = 'BasePicker'; + +export default BasePicker; diff --git a/src/components/Picker/BasePicker/index.web.js b/src/components/Picker/BasePicker/index.web.js deleted file mode 100644 index e01a885f4c37..000000000000 --- a/src/components/Picker/BasePicker/index.web.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import RNPickerSelect from 'react-native-picker-select'; -import {View} from 'react-native'; -import _ from 'underscore'; - -import styles from '../../../styles/styles'; -import * as basePickerPropTypes from './basePickerPropTypes'; -import basePickerStyles from './basePickerStyles'; - -class BasePicker extends React.Component { - constructor(props) { - super(props); - - this.state = { - selectValue: this.props.value || this.props.defaultValue, - }; - } - - componentDidMount() { - const viewRef = this.viewRef; - if (!viewRef) { - return; - } - - if (typeof this.props.innerRef !== 'function') { - return; - } - - // 'ref' is not exposed in Picker to focus on it. An alternative is getting a View container ref. View ref will return a
, - // apply 'focus()' on it will not scroll to it. Applying 'scrollIntoView(false)' will scroll to element. https://developer.mozilla.org/es/docs/Web/API/Element/scrollIntoView - const originalFocus = viewRef.focus; - viewRef.focus = function () { - // Execute original focus() using apply on ref context to prevent infinite focus() call stack and preserve original focus() functionality - originalFocus.apply(viewRef); - viewRef.scrollIntoView(false); - }; - this.props.innerRef(viewRef); - } - - handleChange = (value) => { - this.props.onChange(value); - this.setState({selectValue: value}); - } - - render() { - const hasError = !_.isEmpty(this.props.errorText); - return ( - this.viewRef = ref}> - this.props.icon(this.props.size)} - disabled={this.props.disabled} - fixAndroidTouchableBug - onOpen={this.props.onOpen} - onClose={this.props.onClose} - pickerProps={{ - onFocus: this.props.onOpen, - onBlur: this.props.onBlur, - }} - /> - - ); - } -} - -BasePicker.propTypes = basePickerPropTypes.propTypes; -BasePicker.defaultProps = basePickerPropTypes.defaultProps; -BasePicker.displayName = 'BasePicker'; - -export default BasePicker; From 170fc7054e4eba17d9b492e4c9cef404251a2f6a Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Mon, 21 Feb 2022 10:33:19 -0600 Subject: [PATCH 16/46] update @react-native-picker/picker --- package-lock.json | 13 ++++++++++--- package.json | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94074cabd4ff..3b40abd5f05a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6710,9 +6710,9 @@ "integrity": "sha512-2Y9OXWHRutYmdyW+bNMaFTW8uTBpaaK20xdIFoVtqahEOO9++B+ut3CAWKPZcdRtb9ikG0LUKChGqMeocg0PGA==" }, "@react-native-picker/picker": { - "version": "1.9.11", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-1.9.11.tgz", - "integrity": "sha512-E7whvvMIl9Ln1sxgug7OAEVWQ69n82iV0d2OWWp5VV52+RW3azKh1IFm4rxdW5/oByMfl7FFL0eHNelGgY4BMQ==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.2.1.tgz", + "integrity": "sha512-EC7yv22QLHlTfnbC1ez9IUdXTOh1W31x96Oir0PfskSGFFJMWWdLTg4VrcE2DsGLzbfjjkBk123c173vf2a5MQ==" }, "@react-native/assets": { "version": "1.0.0", @@ -37107,6 +37107,13 @@ "requires": { "@react-native-picker/picker": "^1.8.3", "lodash.isequal": "^4.5.0" + }, + "dependencies": { + "@react-native-picker/picker": { + "version": "1.16.8", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-1.16.8.tgz", + "integrity": "sha512-pacdQDX6V6EmjF+HoiIh6u++qx4mTK0WnhgUHRc01B+Qt5eoeUwseBqmqfTSXTx/aHDEd6PiIw7UGvKgFoqgFQ==" + } } }, "react-native-plaid-link-sdk": { diff --git a/package.json b/package.json index 709166612bb4..720e6f16d13f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@react-native-firebase/crashlytics": "^12.3.0", "@react-native-firebase/perf": "^12.3.0", "@react-native-masked-view/masked-view": "^0.2.4", - "@react-native-picker/picker": "^1.9.11", + "@react-native-picker/picker": "^2.2.1", "@react-navigation/compat": "^5.3.20", "@react-navigation/drawer": "6.1.8", "@react-navigation/native": "6.0.8", From 7fb4b0f97834bead1edaac7b0fede72e0b7868ef Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Sun, 27 Feb 2022 22:14:46 -0600 Subject: [PATCH 17/46] remove false param on scrollIntoView --- src/components/Picker/BasePicker/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index e01a885f4c37..1279afecc894 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -32,7 +32,7 @@ class BasePicker extends React.Component { viewRef.focus = function () { // Execute original focus() using apply on ref context to prevent infinite focus() call stack and preserve original focus() functionality originalFocus.apply(viewRef); - viewRef.scrollIntoView(false); + viewRef.scrollIntoView(); }; this.props.innerRef(viewRef); } From e9a33e2dab527805b8d12ada6e4a69f236a4bb16 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 1 Mar 2022 21:31:13 -0600 Subject: [PATCH 18/46] update react native picker --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5caefe057c12..9348f3f2964b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6710,9 +6710,9 @@ "integrity": "sha512-2Y9OXWHRutYmdyW+bNMaFTW8uTBpaaK20xdIFoVtqahEOO9++B+ut3CAWKPZcdRtb9ikG0LUKChGqMeocg0PGA==" }, "@react-native-picker/picker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.2.1.tgz", - "integrity": "sha512-EC7yv22QLHlTfnbC1ez9IUdXTOh1W31x96Oir0PfskSGFFJMWWdLTg4VrcE2DsGLzbfjjkBk123c173vf2a5MQ==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.3.1.tgz", + "integrity": "sha512-DAw1o3bHNRnQPImsK53xCYkgC8bH+t9uTM+0JjNIlmMwvVLFnmDxi9v5iBTIWFMWG/pUHQaw66E29wW6oJG9Tw==" }, "@react-native/assets": { "version": "1.0.0", diff --git a/package.json b/package.json index 8e10432372a8..a4b6428004e9 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@react-native-firebase/crashlytics": "^12.3.0", "@react-native-firebase/perf": "^12.3.0", "@react-native-masked-view/masked-view": "^0.2.4", - "@react-native-picker/picker": "^2.2.1", + "@react-native-picker/picker": "^2.3.1", "@react-navigation/compat": "^5.3.20", "@react-navigation/drawer": "6.1.8", "@react-navigation/native": "6.0.8", From b8158b9fe0798c0b9d378d785b6ad3aaf0515ef7 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 1 Mar 2022 21:34:20 -0600 Subject: [PATCH 19/46] correct InputIDProps name --- src/components/Picker/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js index bd0d25a8fb72..7f6f1e78e93f 100644 --- a/src/components/Picker/index.js +++ b/src/components/Picker/index.js @@ -33,7 +33,7 @@ const propTypes = { * @param {Object} props - props passed to the input * @returns {Object} - returns an Error object if isFormInput is supplied but inputID is falsey or not a string */ - inputID: props => FormUtils.getInputIDPropTypes(props), + inputID: props => FormUtils.validateInputIDProps(props), /** Saves a draft of the input value when used in a form */ shouldSaveDraft: PropTypes.bool, From 2da10cf7195d8cab54e149af092c18216e6be1ab Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 1 Mar 2022 21:39:45 -0600 Subject: [PATCH 20/46] add style prop to InlineErrorText, add style Picker --- src/components/InlineErrorText.js | 4 +++- src/components/Picker/index.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/InlineErrorText.js b/src/components/InlineErrorText.js index 7ac4192585de..88345121bb55 100644 --- a/src/components/InlineErrorText.js +++ b/src/components/InlineErrorText.js @@ -7,10 +7,12 @@ import Text from './Text'; const propTypes = { /** Text to display */ children: PropTypes.string, + style: PropTypes.arrayOf(PropTypes.object), }; const defaultProps = { children: 'Error', + style: [], }; const InlineErrorText = (props) => { @@ -19,7 +21,7 @@ const InlineErrorText = (props) => { } return ( - {props.children} + {props.children} ); }; diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js index 7f6f1e78e93f..b7ed7febea68 100644 --- a/src/components/Picker/index.js +++ b/src/components/Picker/index.js @@ -85,7 +85,7 @@ class Picker extends PureComponent { /> {!_.isEmpty(this.props.errorText) && ( - + {this.props.errorText} )} From a893f1a923b560e7c49824997314683046734d29 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 1 Mar 2022 21:52:15 -0600 Subject: [PATCH 21/46] remove alternative logic to Picker ref --- src/components/Picker/BasePicker/index.js | 59 ++++++------------- .../Picker/BasePicker/index.native.js | 52 ---------------- 2 files changed, 18 insertions(+), 93 deletions(-) delete mode 100644 src/components/Picker/BasePicker/index.native.js diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index 1279afecc894..cb5f264e4942 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -1,6 +1,5 @@ import React from 'react'; import RNPickerSelect from 'react-native-picker-select'; -import {View} from 'react-native'; import _ from 'underscore'; import styles from '../../../styles/styles'; @@ -16,27 +15,6 @@ class BasePicker extends React.Component { }; } - componentDidMount() { - const viewRef = this.viewRef; - if (!viewRef) { - return; - } - - if (typeof this.props.innerRef !== 'function') { - return; - } - - // 'ref' is not exposed in Picker to focus on it. An alternative is getting a View container ref. View ref will return a
, - // apply 'focus()' on it will not scroll to it. Applying 'scrollIntoView(false)' will scroll to element. https://developer.mozilla.org/es/docs/Web/API/Element/scrollIntoView - const originalFocus = viewRef.focus; - viewRef.focus = function () { - // Execute original focus() using apply on ref context to prevent infinite focus() call stack and preserve original focus() functionality - originalFocus.apply(viewRef); - viewRef.scrollIntoView(); - }; - this.props.innerRef(viewRef); - } - handleChange = (value) => { this.props.onChange(value); this.setState({selectValue: value}); @@ -45,25 +23,24 @@ class BasePicker extends React.Component { render() { const hasError = !_.isEmpty(this.props.errorText); return ( - this.viewRef = ref}> - this.props.icon(this.props.size)} - disabled={this.props.disabled} - fixAndroidTouchableBug - onOpen={this.props.onOpen} - onClose={this.props.onClose} - pickerProps={{ - onFocus: this.props.onOpen, - onBlur: this.props.onBlur, - }} - /> - + this.props.icon(this.props.size)} + disabled={this.props.disabled} + fixAndroidTouchableBug + onOpen={this.props.onOpen} + onClose={this.props.onClose} + pickerProps={{ + onFocus: this.props.onOpen, + onBlur: this.props.onBlur, + ref: this.props.innerRef, + }} + /> ); } } diff --git a/src/components/Picker/BasePicker/index.native.js b/src/components/Picker/BasePicker/index.native.js deleted file mode 100644 index cb5f264e4942..000000000000 --- a/src/components/Picker/BasePicker/index.native.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import RNPickerSelect from 'react-native-picker-select'; -import _ from 'underscore'; - -import styles from '../../../styles/styles'; -import * as basePickerPropTypes from './basePickerPropTypes'; -import basePickerStyles from './basePickerStyles'; - -class BasePicker extends React.Component { - constructor(props) { - super(props); - - this.state = { - selectValue: this.props.value || this.props.defaultValue, - }; - } - - handleChange = (value) => { - this.props.onChange(value); - this.setState({selectValue: value}); - } - - render() { - const hasError = !_.isEmpty(this.props.errorText); - return ( - this.props.icon(this.props.size)} - disabled={this.props.disabled} - fixAndroidTouchableBug - onOpen={this.props.onOpen} - onClose={this.props.onClose} - pickerProps={{ - onFocus: this.props.onOpen, - onBlur: this.props.onBlur, - ref: this.props.innerRef, - }} - /> - ); - } -} - -BasePicker.propTypes = basePickerPropTypes.propTypes; -BasePicker.defaultProps = basePickerPropTypes.defaultProps; -BasePicker.displayName = 'BasePicker'; - -export default BasePicker; From 57ab9f76f758309e1bf47daaadaf8c05f3610942 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Wed, 2 Mar 2022 13:33:52 -0600 Subject: [PATCH 22/46] fix minor naming and empty lines --- src/components/Picker/BasePicker/index.js | 6 +++--- src/components/Picker/index.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index cb5f264e4942..44e4a14b8037 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -11,13 +11,13 @@ class BasePicker extends React.Component { super(props); this.state = { - selectValue: this.props.value || this.props.defaultValue, + selectedValue: this.props.value || this.props.defaultValue, }; } handleChange = (value) => { this.props.onChange(value); - this.setState({selectValue: value}); + this.setState({selectedValue: value}); } render() { @@ -29,7 +29,7 @@ class BasePicker extends React.Component { style={this.props.size === 'normal' ? basePickerStyles(this.props.disabled, hasError, this.props.focused) : styles.pickerSmall} useNativeAndroidPickerStyle={false} placeholder={this.props.placeholder} - value={this.state.selectValue} + value={this.state.selectedValue} Icon={() => this.props.icon(this.props.size)} disabled={this.props.disabled} fixAndroidTouchableBug diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js index b7ed7febea68..3f1498d6c69b 100644 --- a/src/components/Picker/index.js +++ b/src/components/Picker/index.js @@ -48,7 +48,6 @@ const defaultProps = { inputID: undefined, shouldSaveDraft: false, value: undefined, - }; class Picker extends PureComponent { From bc1310dc9e040926984495071f57629cb7b80760 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Fri, 11 Mar 2022 11:41:48 -0600 Subject: [PATCH 23/46] fix coding style errors --- src/components/InlineErrorText.js | 2 ++ src/components/Picker/BasePicker/index.js | 7 ++++--- src/components/Picker/index.js | 2 +- src/stories/Picker.stories.js | 13 ++++--------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/InlineErrorText.js b/src/components/InlineErrorText.js index 88345121bb55..a21af6629efb 100644 --- a/src/components/InlineErrorText.js +++ b/src/components/InlineErrorText.js @@ -7,6 +7,8 @@ import Text from './Text'; const propTypes = { /** Text to display */ children: PropTypes.string, + + /** Customized styles applied on a single InlineErrorText component */ style: PropTypes.arrayOf(PropTypes.object), }; diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index 44e4a14b8037..59cca5ea1511 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -13,9 +13,11 @@ class BasePicker extends React.Component { this.state = { selectedValue: this.props.value || this.props.defaultValue, }; + + this.updateSelectedValueAndExecuteOnChange = this.updateSelectedValueAndExecuteOnChange.bind(this); } - handleChange = (value) => { + updateSelectedValueAndExecuteOnChange(value) { this.props.onChange(value); this.setState({selectedValue: value}); } @@ -24,7 +26,7 @@ class BasePicker extends React.Component { const hasError = !_.isEmpty(this.props.errorText); return ( {!_.isEmpty(this.props.errorText) && ( - + {this.props.errorText} )} diff --git a/src/stories/Picker.stories.js b/src/stories/Picker.stories.js index 4d5a763969ea..fd230fab83ee 100644 --- a/src/stories/Picker.stories.js +++ b/src/stories/Picker.stories.js @@ -17,13 +17,11 @@ const Template = args => ; // Arguments can be passed to the component by binding // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args - const Default = Template.bind({}); Default.args = { label: 'Default picker', name: 'Default', - onChange: () => { - }, + onChange: () => {}, items: [ { label: 'Orange', @@ -40,8 +38,7 @@ const PickerWithValue = Template.bind({}); PickerWithValue.args = { label: 'Picker with defined value', name: 'Picker with defined value', - onChange: () => { - }, + onChange: () => {}, value: 'apple', items: [ { @@ -60,8 +57,7 @@ ErrorStory.args = { label: 'Picker with error', name: 'PickerWithError', errorText: 'This field has an error.', - onChange: () => { - }, + onChange: () => {}, items: [ { label: 'Orange', @@ -79,8 +75,7 @@ Disabled.args = { label: 'Picker disabled', name: 'Disabled', isDisabled: true, - onChange: () => { - }, + onChange: () => {}, items: [ { label: 'Orange', From 189ca7c7fc6b20c475113ec244f64828c4ff8414 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Fri, 11 Mar 2022 12:49:23 -0600 Subject: [PATCH 24/46] rename onInputChange on Picker.stories --- src/stories/Picker.stories.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stories/Picker.stories.js b/src/stories/Picker.stories.js index fd230fab83ee..dd26adebc484 100644 --- a/src/stories/Picker.stories.js +++ b/src/stories/Picker.stories.js @@ -21,7 +21,7 @@ const Default = Template.bind({}); Default.args = { label: 'Default picker', name: 'Default', - onChange: () => {}, + onInputChange: () => {}, items: [ { label: 'Orange', @@ -38,7 +38,7 @@ const PickerWithValue = Template.bind({}); PickerWithValue.args = { label: 'Picker with defined value', name: 'Picker with defined value', - onChange: () => {}, + onInputChange: () => {}, value: 'apple', items: [ { @@ -57,7 +57,7 @@ ErrorStory.args = { label: 'Picker with error', name: 'PickerWithError', errorText: 'This field has an error.', - onChange: () => {}, + onInputChange: () => {}, items: [ { label: 'Orange', @@ -75,7 +75,7 @@ Disabled.args = { label: 'Picker disabled', name: 'Disabled', isDisabled: true, - onChange: () => {}, + onInputChange: () => {}, items: [ { label: 'Orange', From 7da320acc1009d74532f4c5617e8fa8f7728de1a Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 29 Mar 2022 16:54:55 -0600 Subject: [PATCH 25/46] remove old picker dependency on picker-select --- package-lock.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1166ce86ec8c..369cb7734640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36613,13 +36613,6 @@ "from": "git+https://github.com/Expensify/react-native-picker-select.git#7f09b2c15ffae320d769788f75bdf8948714bb10", "requires": { "lodash.isequal": "^4.5.0" - }, - "dependencies": { - "@react-native-picker/picker": { - "version": "1.16.8", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-1.16.8.tgz", - "integrity": "sha512-pacdQDX6V6EmjF+HoiIh6u++qx4mTK0WnhgUHRc01B+Qt5eoeUwseBqmqfTSXTx/aHDEd6PiIw7UGvKgFoqgFQ==" - } } }, "react-native-plaid-link-sdk": { From bcc4c66f1529c3ffb01bb7e0f4ad516ae84ac2a5 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Thu, 31 Mar 2022 12:43:52 -0600 Subject: [PATCH 26/46] rename onChange to onInputChange on all Pickers --- src/components/AddPlaidBankAccount.js | 2 +- src/components/LocalePicker.js | 2 +- src/components/StatePicker.js | 2 +- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- src/pages/ReportSettingsPage.js | 2 +- src/pages/settings/PreferencesPage.js | 2 +- src/pages/settings/Profile/ProfilePage.js | 4 ++-- src/pages/workspace/WorkspaceNewRoomPage.js | 4 ++-- src/pages/workspace/WorkspaceSettingsPage.js | 2 +- src/pages/workspace/reimburse/WorkspaceReimburseNoVBAView.js | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index f3837f8ad61d..39d1a8f9dd09 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -243,7 +243,7 @@ class AddPlaidBankAccount extends React.Component { { + onInputChange={(index) => { this.setState({selectedIndex: Number(index)}); this.clearError('selectedBank'); }} diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js index 30eba46aba9e..aafb06ea6fd2 100644 --- a/src/components/LocalePicker.js +++ b/src/components/LocalePicker.js @@ -49,7 +49,7 @@ const LocalePicker = (props) => { return ( { + onInputChange={(locale) => { if (locale === props.preferredLocale) { return; } diff --git a/src/components/StatePicker.js b/src/components/StatePicker.js index d9fbe810eeb4..11d4cad09f55 100644 --- a/src/components/StatePicker.js +++ b/src/components/StatePicker.js @@ -32,7 +32,7 @@ const StatePicker = props => ( ({value, label}))} - onChange={value => this.clearErrorAndSetValue('incorporationType', value)} + onInputChange={value => this.clearErrorAndSetValue('incorporationType', value)} value={this.state.incorporationType} placeholder={{value: '', label: '-'}} hasError={this.getErrors().incorporationType} diff --git a/src/pages/ReportSettingsPage.js b/src/pages/ReportSettingsPage.js index a0ddbc6c8e55..a3ec5a1b6f7e 100644 --- a/src/pages/ReportSettingsPage.js +++ b/src/pages/ReportSettingsPage.js @@ -168,7 +168,7 @@ class ReportSettingsPage extends Component { { + onInputChange={(notificationPreference) => { Report.updateNotificationPreference( this.props.report.reportID, notificationPreference, diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js index 4752dfc291d8..ae7de7e4f51f 100755 --- a/src/pages/settings/PreferencesPage.js +++ b/src/pages/settings/PreferencesPage.js @@ -85,7 +85,7 @@ const PreferencesPage = (props) => { NameValuePair.set(CONST.NVP.PRIORITY_MODE, mode, ONYXKEYS.NVP_PRIORITY_MODE) } items={_.values(priorityModes)} diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index cf4ecd2e5ce2..1dc7a502e788 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -250,7 +250,7 @@ class ProfilePage extends Component { { + onInputChange={(pronouns) => { const hasSelfSelectedPronouns = pronouns === CONST.PRONOUNS.SELF_SELECT; this.setState({ pronouns: hasSelfSelectedPronouns ? '' : pronouns, @@ -288,7 +288,7 @@ class ProfilePage extends Component { this.setState({selectedTimezone})} + onInputChange={selectedTimezone => this.setState({selectedTimezone})} items={timezones} isDisabled={this.state.isAutomaticTimezone} value={this.state.selectedTimezone} diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 780bf75f487e..a31dad1708c5 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -173,7 +173,7 @@ class WorkspaceNewRoomPage extends React.Component { items={this.state.workspaceOptions} errorText={this.state.errors.policyID} hasError={Boolean(this.state.errors.policyID)} - onChange={policyID => this.clearErrorAndSetValue('policyID', policyID)} + onInputChange={policyID => this.clearErrorAndSetValue('policyID', policyID)} /> @@ -181,7 +181,7 @@ class WorkspaceNewRoomPage extends React.Component { value={this.state.visibility} label={this.props.translate('newRoomPage.visibility')} items={visibilityOptions} - onChange={visibility => this.setState({visibility})} + onInputChange={visibility => this.setState({visibility})} /> diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index ee9989d86ff3..a0bb45fcbe6b 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -168,7 +168,7 @@ class WorkspaceSettingsPage extends React.Component { this.setState({currency})} + onInputChange={currency => this.setState({currency})} items={this.getCurrencyItems()} value={this.state.currency} isDisabled={hasVBA} diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseNoVBAView.js b/src/pages/workspace/reimburse/WorkspaceReimburseNoVBAView.js index 6e68c98488cd..e068a8666eeb 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseNoVBAView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseNoVBAView.js @@ -181,7 +181,7 @@ class WorkspaceReimburseNoVBAView extends React.Component { label={this.props.translate('workspace.reimburse.trackDistanceUnit')} items={this.unitItems} value={this.state.unitValue} - onChange={value => this.setUnit(value)} + onInputChange={value => this.setUnit(value)} /> From 1647cf9c5134394e76d008d51f413ed534749b57 Mon Sep 17 00:00:00 2001 From: Dennis Kawawa Date: Fri, 1 Apr 2022 09:13:12 +0300 Subject: [PATCH 27/46] updated regex for detecting attachments --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2767abf30d72..1adea76e1189 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -183,7 +183,7 @@ function getSimplifiedReportObject(report) { const createTimestamp = lodashGet(report, 'lastActionCreated', 0); const lastMessageTimestamp = moment.utc(createTimestamp).unix(); const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], ''); - const isLastMessageAttachment = /]+)\/>/gi.test(lastActionMessage); + const isLastMessageAttachment = /<\/?(a|img|...)\b[^<>]*>/gi.test(lastActionMessage); const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], ''); let lastMessageText = null; From 747215f8fb752548329deddf5cca5bfa160bb90e Mon Sep 17 00:00:00 2001 From: Dennis Kawawa Date: Fri, 1 Apr 2022 14:15:17 +0300 Subject: [PATCH 28/46] filter anchor tags --- src/libs/actions/Report.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 1adea76e1189..d425dc88c980 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -183,9 +183,11 @@ function getSimplifiedReportObject(report) { const createTimestamp = lodashGet(report, 'lastActionCreated', 0); const lastMessageTimestamp = moment.utc(createTimestamp).unix(); const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], ''); - const isLastMessageAttachment = /<\/?(a|img|...)\b[^<>]*>/gi.test(lastActionMessage); + const isLastMessageAttachment = /]*data-expensify-source\s*=\s*"[^"]*"[^>]*>/gi.test(lastActionMessage); const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], ''); + console.log("LastActionMessage",lastActionMessage); + let lastMessageText = null; if (report.reportActionCount > 0) { // We are removing any html tags from the message html since we cannot access the text version of any comments as From 78adfe08de67bd6f8d67733622a546beca10b0e9 Mon Sep 17 00:00:00 2001 From: Dennis Kawawa Date: Fri, 1 Apr 2022 17:06:24 +0300 Subject: [PATCH 29/46] [nit] remove debug log --- src/libs/actions/Report.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index d425dc88c980..11bcf8850e0b 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -186,8 +186,6 @@ function getSimplifiedReportObject(report) { const isLastMessageAttachment = /]*data-expensify-source\s*=\s*"[^"]*"[^>]*>/gi.test(lastActionMessage); const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], ''); - console.log("LastActionMessage",lastActionMessage); - let lastMessageText = null; if (report.reportActionCount > 0) { // We are removing any html tags from the message html since we cannot access the text version of any comments as From 2c84685fb101802b8d39a4d44bf781d2e35b53ad Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Apr 2022 13:06:58 -0600 Subject: [PATCH 30/46] show unique list of members --- src/pages/workspace/WorkspaceMembersPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 0d5e069b9fdc..f88ebf675511 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -249,6 +249,7 @@ class WorkspaceMembersPage extends React.Component { const data = _.chain(policyEmployeeList) .map(email => this.props.personalDetails[email]) .filter() + .unique() .sortBy(person => person.displayName.toLowerCase()) .value(); const policyID = lodashGet(this.props.route, 'params.policyID'); From ce8ed0c2b9c93a57f6f03c8201060fb8b5393e59 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Apr 2022 14:21:17 -0600 Subject: [PATCH 31/46] normalize sms logins --- src/libs/OptionsListUtils.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index b5ce082c61d4..9d47ac8157e0 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -586,13 +586,23 @@ function getOptions(reports, personalDetails, activeReportID, { } let userToInvite = null; + + // We should normalize the logins in case the user is searching for a phone number + let normalizedLogin = null; + if (Str.isValidPhone(searchValue)) { + const smsLogin = `${searchValue}${CONST.SMS.DOMAIN}`; + normalizedLogin = smsLogin.includes('+') ? smsLogin : `+${countryCodeByIP}${smsLogin}`; + } else { + normalizedLogin = searchValue; + } + if (searchValue && recentReportOptions.length === 0 && personalDetailsOptions.length === 0 && !isCurrentUser({login: searchValue}) && _.every(selectedOptions, option => option.login !== searchValue) && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue)) || Str.isValidPhone(searchValue)) - && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === searchValue.toLowerCase())) + && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === normalizedLogin.toLowerCase())) && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) ) { // If the phone number doesn't have an international code then let's prefix it with the From 9d5a7ca7c8649d2c88f227639b52dd7f3be7a9ce Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Apr 2022 14:35:10 -0600 Subject: [PATCH 32/46] update check for error message --- src/libs/OptionsListUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 9d47ac8157e0..9b7d84020855 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -587,7 +587,7 @@ function getOptions(reports, personalDetails, activeReportID, { let userToInvite = null; - // We should normalize the logins in case the user is searching for a phone number + // We should normalize logins to compare with the list of logins to exclude in case the user is searching for a phone number let normalizedLogin = null; if (Str.isValidPhone(searchValue)) { const smsLogin = `${searchValue}${CONST.SMS.DOMAIN}`; @@ -797,7 +797,7 @@ function getHeaderMessage(hasSelectableOptions, hasUserToInvite, searchValue, ma // Without a search value, it would be very confusing to see a search validation message. // Therefore, this skips the validation when there is no search value. if (searchValue && !hasSelectableOptions && !hasUserToInvite) { - if (/^\d+$/.test(searchValue)) { + if (!Str.isValidPhone(searchValue)) { return Localize.translate(preferredLocale, 'messages.errorMessageInvalidPhone'); } From feab96f7b5f1412e3079629a42f1581b8b9d9068 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Apr 2022 14:57:49 -0600 Subject: [PATCH 33/46] update error conditional --- src/libs/OptionsListUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 9b7d84020855..4f411aa1dd50 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -797,7 +797,7 @@ function getHeaderMessage(hasSelectableOptions, hasUserToInvite, searchValue, ma // Without a search value, it would be very confusing to see a search validation message. // Therefore, this skips the validation when there is no search value. if (searchValue && !hasSelectableOptions && !hasUserToInvite) { - if (!Str.isValidPhone(searchValue)) { + if (/^\d+$/.test(searchValue) && !Str.isValidPhone(searchValue)) { return Localize.translate(preferredLocale, 'messages.errorMessageInvalidPhone'); } From ac357149f09bda0b395a6ee704ba9edbb3d762f8 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Apr 2022 15:13:43 -0600 Subject: [PATCH 34/46] dry code --- src/libs/OptionsListUtils.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 4f411aa1dd50..cad6241e2e86 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -103,7 +103,8 @@ const defaultAvatarForUserToInvite = getDefaultAvatar(); */ function addSMSDomainIfPhoneNumber(login) { if (Str.isValidPhone(login) && !Str.isValidEmail(login)) { - return login + CONST.SMS.DOMAIN; + const smsLogin = login + CONST.SMS.DOMAIN; + return smsLogin.includes('+') ? smsLogin : `+${countryCodeByIP}${smsLogin}`; } return login; } @@ -587,22 +588,13 @@ function getOptions(reports, personalDetails, activeReportID, { let userToInvite = null; - // We should normalize logins to compare with the list of logins to exclude in case the user is searching for a phone number - let normalizedLogin = null; - if (Str.isValidPhone(searchValue)) { - const smsLogin = `${searchValue}${CONST.SMS.DOMAIN}`; - normalizedLogin = smsLogin.includes('+') ? smsLogin : `+${countryCodeByIP}${smsLogin}`; - } else { - normalizedLogin = searchValue; - } - if (searchValue && recentReportOptions.length === 0 && personalDetailsOptions.length === 0 && !isCurrentUser({login: searchValue}) && _.every(selectedOptions, option => option.login !== searchValue) && ((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue)) || Str.isValidPhone(searchValue)) - && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === normalizedLogin.toLowerCase())) + && (!_.find(loginOptionsToExclude, loginOptionToExclude => loginOptionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase())) && (searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) ) { // If the phone number doesn't have an international code then let's prefix it with the From ee576a2cb27164cb22b2f002e86dde46f84aabb6 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Apr 2022 16:12:34 -0600 Subject: [PATCH 35/46] remove space --- src/libs/OptionsListUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index cad6241e2e86..faff5a00f41c 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -587,7 +587,6 @@ function getOptions(reports, personalDetails, activeReportID, { } let userToInvite = null; - if (searchValue && recentReportOptions.length === 0 && personalDetailsOptions.length === 0 From a80059948ddff916c8507bc9eb353d154ca10e77 Mon Sep 17 00:00:00 2001 From: Dennis Kawawa Date: Wed, 6 Apr 2022 13:13:25 +0300 Subject: [PATCH 36/46] added CONST source attribute --- src/CONST.js | 2 ++ src/libs/actions/Report.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CONST.js b/src/CONST.js index 0ecb1aedf48a..1d310073b3bf 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -379,6 +379,8 @@ const CONST = { DECIMAL_PAD: 'decimal-pad', }, + ATTACHMENT_SOURCE_ATTRIBUTE : 'data-expensify-source', + ATTACHMENT_PICKER_TYPE: { FILE: 'file', IMAGE: 'image', diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 11bcf8850e0b..fbfe1451832c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -183,7 +183,7 @@ function getSimplifiedReportObject(report) { const createTimestamp = lodashGet(report, 'lastActionCreated', 0); const lastMessageTimestamp = moment.utc(createTimestamp).unix(); const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], ''); - const isLastMessageAttachment = /]*data-expensify-source\s*=\s*"[^"]*"[^>]*>/gi.test(lastActionMessage); + const isLastMessageAttachment = new RegExp(`]*${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}\\s*=\\s*"[^"]*"[^>]*>`,"gi").test(lastActionMessage); const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], ''); let lastMessageText = null; From 56cdb2c5c3d6a8b73603a5d9df74672249782790 Mon Sep 17 00:00:00 2001 From: Justice Arthur Date: Wed, 6 Apr 2022 14:49:26 +0000 Subject: [PATCH 37/46] fix console error when up arrow is pressed in emoji picker --- src/components/EmojiPicker/EmojiPickerMenu/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 4fe540bcc813..21ec2d724f15 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -256,6 +256,9 @@ class EmojiPickerMenu extends Component { const isHeader = e => e.header || e.spacer; do { newIndex += steps; + if (newIndex < 0) { + break; + } } while (isHeader(this.state.filteredEmojis[newIndex])); }; From 14859bf72a6d8661cceaba4de4c7733f7854cdf8 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 6 Apr 2022 10:44:39 -0600 Subject: [PATCH 38/46] remove unique filter --- src/pages/workspace/WorkspaceMembersPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index f88ebf675511..0d5e069b9fdc 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -249,7 +249,6 @@ class WorkspaceMembersPage extends React.Component { const data = _.chain(policyEmployeeList) .map(email => this.props.personalDetails[email]) .filter() - .unique() .sortBy(person => person.displayName.toLowerCase()) .value(); const policyID = lodashGet(this.props.route, 'params.policyID'); From 2ff06ce90a9413ce566f21ba1764ed87dee2c8c6 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Wed, 6 Apr 2022 12:13:33 -0700 Subject: [PATCH 39/46] Update checkDeployBlockers to look for any open checkbox except accessibility --- .github/actions/checkDeployBlockers/checkDeployBlockers.js | 2 +- .github/actions/checkDeployBlockers/index.js | 2 +- .github/workflows/finishReleaseCycle.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/checkDeployBlockers/checkDeployBlockers.js b/.github/actions/checkDeployBlockers/checkDeployBlockers.js index 89c4843cc9d3..db156651f9fe 100644 --- a/.github/actions/checkDeployBlockers/checkDeployBlockers.js +++ b/.github/actions/checkDeployBlockers/checkDeployBlockers.js @@ -16,7 +16,7 @@ const run = function () { console.log('Checking for unverified PRs or unresolved deploy blockers', data); // Check the issue description to see if there are any unfinished/un-QAed items in the checklist. - const uncheckedBoxRegex = new RegExp(`-\\s\\[\\s]\\s(?:QA|${GithubUtils.ISSUE_OR_PULL_REQUEST_REGEX.source})`); + const uncheckedBoxRegex = /-\s\[\s]\s(?!Accessibility)/; if (uncheckedBoxRegex.test(data.body)) { console.log('An unverified PR or unresolved deploy blocker was found.'); core.setOutput('HAS_DEPLOY_BLOCKERS', true); diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js index 51e878382f01..6e9a173d5614 100644 --- a/.github/actions/checkDeployBlockers/index.js +++ b/.github/actions/checkDeployBlockers/index.js @@ -26,7 +26,7 @@ const run = function () { console.log('Checking for unverified PRs or unresolved deploy blockers', data); // Check the issue description to see if there are any unfinished/un-QAed items in the checklist. - const uncheckedBoxRegex = new RegExp(`-\\s\\[\\s]\\s(?:QA|${GithubUtils.ISSUE_OR_PULL_REQUEST_REGEX.source})`); + const uncheckedBoxRegex = /-\s\[\s]\s(?!Accessibility)/; if (uncheckedBoxRegex.test(data.body)) { console.log('An unverified PR or unresolved deploy blocker was found.'); core.setOutput('HAS_DEPLOY_BLOCKERS', true); diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml index fe0eb8aff88a..8553f9b63fbb 100644 --- a/.github/workflows/finishReleaseCycle.yml +++ b/.github/workflows/finishReleaseCycle.yml @@ -47,7 +47,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number }} COMMENT: | - This issue either has unchecked QA steps or has not yet been marked with the `:shipit:` emoji of approval. + This issue either has unchecked items or has not yet been marked with the `:shipit:` emoji of approval. Reopening! # Update the production branch to trigger the production deploy. From 2e7fa66b203ca828c4f8137533b079f369839db2 Mon Sep 17 00:00:00 2001 From: Dennis Kawawa Date: Wed, 6 Apr 2022 22:14:08 +0300 Subject: [PATCH 40/46] [nit] lint fix --- src/CONST.js | 2 +- src/libs/actions/Report.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 1d310073b3bf..9a2140b11130 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -379,7 +379,7 @@ const CONST = { DECIMAL_PAD: 'decimal-pad', }, - ATTACHMENT_SOURCE_ATTRIBUTE : 'data-expensify-source', + ATTACHMENT_SOURCE_ATTRIBUTE: 'data-expensify-source', ATTACHMENT_PICKER_TYPE: { FILE: 'file', diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index fbfe1451832c..80856fc8e4c3 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -183,7 +183,7 @@ function getSimplifiedReportObject(report) { const createTimestamp = lodashGet(report, 'lastActionCreated', 0); const lastMessageTimestamp = moment.utc(createTimestamp).unix(); const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], ''); - const isLastMessageAttachment = new RegExp(`]*${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}\\s*=\\s*"[^"]*"[^>]*>`,"gi").test(lastActionMessage); + const isLastMessageAttachment = new RegExp(`]*${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}\\s*=\\s*"[^"]*"[^>]*>`, 'gi').test(lastActionMessage); const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], ''); let lastMessageText = null; From a2c2e298d535598ad5d9cdd84df641f6425b1b77 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Wed, 6 Apr 2022 14:56:18 -0700 Subject: [PATCH 41/46] Implement new checkboxes in StagingDeployCash --- .github/actions/awaitStagingDeploys/index.js | 12 ++++++ .github/actions/checkDeployBlockers/index.js | 12 ++++++ .../createOrUpdateStagingDeploy.js | 3 ++ .../createOrUpdateStagingDeploy/index.js | 15 ++++++++ .../actions/getPullRequestDetails/index.js | 12 ++++++ .github/actions/getReleaseBody/index.js | 12 ++++++ .../actions/isPullRequestMergeable/index.js | 12 ++++++ .../actions/isStagingDeployLocked/index.js | 12 ++++++ .../markPullRequestsAsDeployed/index.js | 12 ++++++ .../actions/reopenIssueWithComment/index.js | 12 ++++++ .../actions/triggerWorkflowAndWait/index.js | 12 ++++++ .github/actions/verifySignedCommits/index.js | 12 ++++++ .github/libs/GithubUtils.js | 12 ++++++ tests/unit/GithubUtilsTest.js | 38 ++++++++++++++++++- tests/unit/createOrUpdateStagingDeployTest.js | 26 ++++++++++++- 15 files changed, 212 insertions(+), 2 deletions(-) diff --git a/.github/actions/awaitStagingDeploys/index.js b/.github/actions/awaitStagingDeploys/index.js index 314a4d03295b..c6ece6eee302 100644 --- a/.github/actions/awaitStagingDeploys/index.js +++ b/.github/actions/awaitStagingDeploys/index.js @@ -226,6 +226,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -295,6 +297,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -304,6 +308,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -376,6 +382,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js index 6e9a173d5614..179dc8b4a078 100644 --- a/.github/actions/checkDeployBlockers/index.js +++ b/.github/actions/checkDeployBlockers/index.js @@ -196,6 +196,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -265,6 +267,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -274,6 +278,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -346,6 +352,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js index 56535f03b29c..43992307b912 100644 --- a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js +++ b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js @@ -85,6 +85,7 @@ const run = function () { // If we aren't sent a tag, then use the existing tag const tag = newVersion || currentStagingDeployCashData.tag; + const didVersionChange = newVersion ? newVersion !== currentStagingDeployCashData.tag : false; // Find the list of PRs merged between the last StagingDeployCash and the new version const mergedPRs = GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, tag); @@ -128,6 +129,8 @@ const run = function () { _.pluck(_.where(PRList, {isAccessible: true}), 'url'), _.pluck(deployBlockers, 'url'), _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), + didVersionChange ? false : currentStagingDeployCashData.isTimingDashboardChecked, + didVersionChange ? false : currentStagingDeployCashData.isFirebaseChecked, ); }) .then((body) => { diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js index 58ecf86f7e9d..43fda09543d8 100644 --- a/.github/actions/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/createOrUpdateStagingDeploy/index.js @@ -95,6 +95,7 @@ const run = function () { // If we aren't sent a tag, then use the existing tag const tag = newVersion || currentStagingDeployCashData.tag; + const didVersionChange = newVersion ? newVersion !== currentStagingDeployCashData.tag : false; // Find the list of PRs merged between the last StagingDeployCash and the new version const mergedPRs = GitUtils.getPullRequestsMergedBetween(previousStagingDeployCashData.tag, tag); @@ -138,6 +139,8 @@ const run = function () { _.pluck(_.where(PRList, {isAccessible: true}), 'url'), _.pluck(deployBlockers, 'url'), _.pluck(_.where(deployBlockers, {isResolved: true}), 'url'), + didVersionChange ? false : currentStagingDeployCashData.isTimingDashboardChecked, + didVersionChange ? false : currentStagingDeployCashData.isFirebaseChecked, ); }) .then((body) => { @@ -386,6 +389,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -455,6 +460,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -464,6 +471,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -536,6 +545,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/getPullRequestDetails/index.js b/.github/actions/getPullRequestDetails/index.js index 0e8fcb725620..8ef5d2f04ebf 100644 --- a/.github/actions/getPullRequestDetails/index.js +++ b/.github/actions/getPullRequestDetails/index.js @@ -267,6 +267,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -336,6 +338,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -345,6 +349,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -417,6 +423,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/getReleaseBody/index.js b/.github/actions/getReleaseBody/index.js index c8c7a0d153d3..a72d730563ff 100644 --- a/.github/actions/getReleaseBody/index.js +++ b/.github/actions/getReleaseBody/index.js @@ -185,6 +185,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -254,6 +256,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -263,6 +267,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -335,6 +341,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/isPullRequestMergeable/index.js b/.github/actions/isPullRequestMergeable/index.js index 3e98b961c4f7..b03366d0c9dd 100644 --- a/.github/actions/isPullRequestMergeable/index.js +++ b/.github/actions/isPullRequestMergeable/index.js @@ -186,6 +186,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -255,6 +257,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -264,6 +268,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -336,6 +342,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js index 4ef5e5aa3c6e..a0e47bbe0662 100644 --- a/.github/actions/isStagingDeployLocked/index.js +++ b/.github/actions/isStagingDeployLocked/index.js @@ -149,6 +149,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -218,6 +220,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -227,6 +231,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -299,6 +305,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js index 1ad199a5f4cf..4f881fae56c4 100644 --- a/.github/actions/markPullRequestsAsDeployed/index.js +++ b/.github/actions/markPullRequestsAsDeployed/index.js @@ -318,6 +318,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -387,6 +389,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -396,6 +400,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -468,6 +474,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/reopenIssueWithComment/index.js b/.github/actions/reopenIssueWithComment/index.js index 42d09f1c766e..7c0219f6e2b3 100644 --- a/.github/actions/reopenIssueWithComment/index.js +++ b/.github/actions/reopenIssueWithComment/index.js @@ -160,6 +160,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -229,6 +231,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -238,6 +242,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -310,6 +316,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/triggerWorkflowAndWait/index.js b/.github/actions/triggerWorkflowAndWait/index.js index 86a6f5039af5..a04038faa3c0 100644 --- a/.github/actions/triggerWorkflowAndWait/index.js +++ b/.github/actions/triggerWorkflowAndWait/index.js @@ -329,6 +329,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -398,6 +400,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -407,6 +411,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -479,6 +485,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/actions/verifySignedCommits/index.js b/.github/actions/verifySignedCommits/index.js index 8060ed706572..406598e3db99 100644 --- a/.github/actions/verifySignedCommits/index.js +++ b/.github/actions/verifySignedCommits/index.js @@ -149,6 +149,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -218,6 +220,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -227,6 +231,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -299,6 +305,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index be5fe177c54a..220407dd1f2f 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -109,6 +109,8 @@ class GithubUtils { labels: issue.labels, PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), + isTimingDashboardChecked: /-\s\[x]\sI checked the \[App Timing Dashboard]/.test(issue.body), + isFirebaseChecked: /-\s\[x]\sI checked \[Firebase Crashlytics]/.test(issue.body), tag, }; } catch (exception) { @@ -178,6 +180,8 @@ class GithubUtils { * @param {Array} [accessiblePRList] - The list of PR URLs which have passed the accessability check. * @param {Array} [deployBlockers] - The list of DeployBlocker URLs. * @param {Array} [resolvedDeployBlockers] - The list of DeployBlockers URLs which have been resolved. + * @param {Boolean} [isTimingDashboardChecked] + * @param {Boolean} [isFirebaseChecked] * @returns {Promise} */ static generateStagingDeployCashBody( @@ -187,6 +191,8 @@ class GithubUtils { accessiblePRList = [], deployBlockers = [], resolvedDeployBlockers = [], + isTimingDashboardChecked = false, + isFirebaseChecked = false, ) { return this.fetchAllPullRequests(_.map(PRList, this.getPullRequestNumberFromURL)) .then((data) => { @@ -259,6 +265,12 @@ class GithubUtils { }); } + issueBody += '\r\n\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isTimingDashboardChecked ? 'x' : ' '}] I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.`; + // eslint-disable-next-line max-len + issueBody += `\r\n- [${isFirebaseChecked ? 'x' : ' '}] I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.`; + issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n'; return issueBody; }) diff --git a/tests/unit/GithubUtilsTest.js b/tests/unit/GithubUtilsTest.js index ef8818a293ac..c9d44a049fc6 100644 --- a/tests/unit/GithubUtilsTest.js +++ b/tests/unit/GithubUtilsTest.js @@ -68,6 +68,8 @@ describe('GithubUtils', () => { url: 'https://api.github.com/repos/Andrew-Test-Org/Public-Test-Repo/issues/29', number: 29, deployBlockers: [], + isTimingDashboardChecked: false, + isFirebaseChecked: false, }; const expectedResponseWithDeployBlockers = {...baseExpectedResponse}; expectedResponseWithDeployBlockers.deployBlockers = [ @@ -375,6 +377,12 @@ describe('GithubUtils', () => { const lineBreakDouble = '\r\n\r\n'; const indent = ' '; const assignOctocatHubot = ' - @octocat @hubot'; + const deployerVerificationsHeader = '\r\n**Deployer verifications:**'; + // eslint-disable-next-line max-len + const timingDashboardVerification = 'I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.'; + // eslint-disable-next-line max-len + const firebaseVerification = 'I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.'; + // Valid output which will be reused in the deploy blocker tests const allVerifiedExpectedOutput = `${baseExpectedOutput}` @@ -394,6 +402,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${listStart}${basePRList[1]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[5]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[6]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -409,6 +420,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${listStart}${basePRList[1]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[5]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[6]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -424,6 +438,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${listStart}${basePRList[1]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[5]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[6]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -433,7 +450,11 @@ describe('GithubUtils', () => { githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList) .then((issueBody) => { expect(issueBody).toBe( - `${allVerifiedExpectedOutput}${lineBreakDouble}${ccApplauseLeads}`, + `${allVerifiedExpectedOutput}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + + `${lineBreakDouble}${ccApplauseLeads}`, ); }) )); @@ -446,6 +467,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${deployBlockerHeader}` + `${lineBreak}${openCheckbox}${baseDeployBlockerList[0]}` + `${lineBreak}${openCheckbox}${baseDeployBlockerList[1]}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -459,6 +483,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${deployBlockerHeader}` + `${lineBreak}${closedCheckbox}${baseDeployBlockerList[0]}` + `${lineBreak}${openCheckbox}${baseDeployBlockerList[1]}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -477,6 +504,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${deployBlockerHeader}` + `${lineBreak}${closedCheckbox}${baseDeployBlockerList[0]}` + `${lineBreak}${closedCheckbox}${baseDeployBlockerList[1]}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -495,6 +525,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${internalQAHeader}` + `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` + `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) @@ -513,6 +546,9 @@ describe('GithubUtils', () => { + `${lineBreakDouble}${internalQAHeader}` + `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` + `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, ); }) diff --git a/tests/unit/createOrUpdateStagingDeployTest.js b/tests/unit/createOrUpdateStagingDeployTest.js index 90307c3f469c..8d059a289d2d 100644 --- a/tests/unit/createOrUpdateStagingDeployTest.js +++ b/tests/unit/createOrUpdateStagingDeployTest.js @@ -102,6 +102,11 @@ const closedCheckbox = '- [x] '; const listStart = '- '; const QA = 'QA'; const accessibility = 'Accessibility'; +const deployerVerificationsHeader = '\r\n**Deployer verifications:**'; +// eslint-disable-next-line max-len +const timingDashboardVerification = 'I checked the [App Timing Dashboard](https://graphs.expensify.com/grafana/d/yj2EobAGz/app-timing?orgId=1) and verified this release does not cause a noticeable performance regression.'; +// eslint-disable-next-line max-len +const firebaseVerification = 'I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.'; const ccApplauseLeads = 'cc @Expensify/applauseleads\r\n'; const deployBlockerHeader = '\r\n**Deploy Blockers:**'; const lineBreak = '\r\n'; @@ -169,6 +174,9 @@ describe('createOrUpdateStagingDeployCash', () => { + `${lineBreakDouble}${listStart}${basePRList[5]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[6]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[7]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, }); }); @@ -189,6 +197,9 @@ describe('createOrUpdateStagingDeployCash', () => { + `${lineBreak}${openCheckbox}${basePRList[5]}` + `${lineBreak}${openCheckbox}${basePRList[8]}` + `${lineBreak}${closedCheckbox}${basePRList[9]}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${closedCheckbox}${timingDashboardVerification}` + + `${lineBreak}${closedCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, state: 'open', }; @@ -285,12 +296,22 @@ describe('createOrUpdateStagingDeployCash', () => { + `${lineBreak}${closedCheckbox}${basePRList[9]}` + `${lineBreak}${openCheckbox}${basePRList[10]}` + `${lineBreak}${openCheckbox}${basePRList[11]}` + + `${lineBreak}${deployerVerificationsHeader}` + + // Note: these will be unchecked with a new app version, and that's intentional + + `${lineBreak}${openCheckbox}${timingDashboardVerification}` + + `${lineBreak}${openCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, }); }); }); test('without NPM_VERSION input, just a new deploy blocker', () => { + mockGetInput.mockImplementation((arg) => { + if (arg === 'GITHUB_TOKEN') { + return 'fake_token'; + } + }); mockGetPullRequestsMergedBetween.mockImplementation((fromRef, toRef) => { if (fromRef === '1.0.1-0' && toRef === '1.0.2-2') { return [ @@ -335,7 +356,7 @@ describe('createOrUpdateStagingDeployCash', () => { // eslint-disable-next-line max-len html_url: `https://github.com/Expensify/App/issues/${openStagingDeployCashBefore.number}`, // eslint-disable-next-line max-len - body: `${baseExpectedOutput('1.0.2-2')}` + body: `${baseExpectedOutput('1.0.2-1')}` + `${lineBreakDouble}${listStart}${basePRList[5]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[6]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${closedCheckbox}${accessibility}` + `${lineBreakDouble}${listStart}${basePRList[7]}${lineBreak}${indent}${openCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` @@ -345,6 +366,9 @@ describe('createOrUpdateStagingDeployCash', () => { + `${lineBreak}${closedCheckbox}${basePRList[9]}` + `${lineBreak}${openCheckbox}${baseIssueList[0]}` + `${lineBreak}${openCheckbox}${baseIssueList[1]}` + + `${lineBreak}${deployerVerificationsHeader}` + + `${lineBreak}${closedCheckbox}${timingDashboardVerification}` + + `${lineBreak}${closedCheckbox}${firebaseVerification}` + `${lineBreakDouble}${ccApplauseLeads}`, }); }); From 9e1aa59a43b0d90ab26228cd9e75b3a99175627e Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Wed, 6 Apr 2022 15:03:49 -0700 Subject: [PATCH 42/46] Fix JS style --- tests/unit/GithubUtilsTest.js | 1 - tests/unit/createOrUpdateStagingDeployTest.js | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/GithubUtilsTest.js b/tests/unit/GithubUtilsTest.js index c9d44a049fc6..a5c8e8a2687f 100644 --- a/tests/unit/GithubUtilsTest.js +++ b/tests/unit/GithubUtilsTest.js @@ -383,7 +383,6 @@ describe('GithubUtils', () => { // eslint-disable-next-line max-len const firebaseVerification = 'I checked [Firebase Crashlytics](https://console.firebase.google.com/u/0/project/expensify-chat/crashlytics/app/android:com.expensify.chat/issues?state=open&time=last-seven-days&tag=all) and verified that this release does not introduce any new crashes.'; - // Valid output which will be reused in the deploy blocker tests const allVerifiedExpectedOutput = `${baseExpectedOutput}` + `${lineBreakDouble}${listStart}${basePRList[2]}${lineBreak}${indent}${closedCheckbox}${QA}${lineBreak}${indent}${openCheckbox}${accessibility}` diff --git a/tests/unit/createOrUpdateStagingDeployTest.js b/tests/unit/createOrUpdateStagingDeployTest.js index 8d059a289d2d..3d9d5400e525 100644 --- a/tests/unit/createOrUpdateStagingDeployTest.js +++ b/tests/unit/createOrUpdateStagingDeployTest.js @@ -308,9 +308,10 @@ describe('createOrUpdateStagingDeployCash', () => { test('without NPM_VERSION input, just a new deploy blocker', () => { mockGetInput.mockImplementation((arg) => { - if (arg === 'GITHUB_TOKEN') { - return 'fake_token'; + if (arg !== 'GITHUB_TOKEN') { + return; } + return 'fake_token'; }); mockGetPullRequestsMergedBetween.mockImplementation((fromRef, toRef) => { if (fromRef === '1.0.1-0' && toRef === '1.0.2-2') { From aabe51e57d46fcea1e567c43f275b7dfd1209498 Mon Sep 17 00:00:00 2001 From: Dennis Kawawa Date: Thu, 7 Apr 2022 18:57:22 +0300 Subject: [PATCH 43/46] [refactor] data-expensify-source to const --- .../HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js | 2 +- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index 6f53780a4eb4..e768e59bbf98 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -16,7 +16,7 @@ const AnchorRenderer = (props) => { const htmlAttribs = props.tnode.attributes; // An auth token is needed to download Expensify chat attachments - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); const displayName = lodashGet(props.tnode, 'domNode.children[0].data', ''); const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {}); const attrHref = htmlAttribs.href || ''; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index e1f9b356ab97..152e2f03749c 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -5,6 +5,7 @@ import AttachmentModal from '../../AttachmentModal'; import styles from '../../../styles/styles'; import ThumbnailImage from '../../ThumbnailImage'; import TouchableWithoutFocus from '../../TouchableWithoutFocus'; +import CONST from '../../../CONST'; const ImageRenderer = (props) => { const htmlAttribs = props.tnode.attributes; @@ -14,7 +15,7 @@ const ImageRenderer = (props) => { // - Chat Attachment images // // Images uploaded by the user via the app or email. - // These have a full-sized image `htmlAttribs['data-expensify-source']` + // These have a full-sized image `htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]` // and a thumbnail `htmlAttribs.src`. Both of these URLs need to have // an authToken added to them in order to control who // can see the images. @@ -26,11 +27,11 @@ const ImageRenderer = (props) => { // Concierge responder attachments are uploaded to S3 without any access // control and thus require no authToken to verify access. // - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); const originalFileName = htmlAttribs['data-name']; let previewSource = htmlAttribs.src; let source = isAttachment - ? htmlAttribs['data-expensify-source'] + ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] : htmlAttribs.src; // Update the image URL so the images can be accessed depending on the config environment From adc8f9eae5de2302f84393fc2b2d99c470e37a22 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 7 Apr 2022 18:58:10 +0200 Subject: [PATCH 44/46] Make sure to use the correct username --- .github/workflows/preDeploy.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 85ad459978f6..266f998d4f03 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -216,12 +216,18 @@ jobs: IS_EXPENSIFY_EMPLOYEE: ${{ fromJSON(steps.checkActor.outputs.isTeamMember) }} steps: + - name: Get merged pull request + id: getMergedPullRequest + uses: roryabraham/action-get-merged-pull-request@7a7a194f6ff8f3eef58c822083695a97314ebec1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Check whether the actor is member of Expensify/expensify team id: checkActor uses: tspascoal/get-user-teams-membership@baf2e6adf4c3b897bd65a7e3184305c165aec872 with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - username: ${{ github.actor }} + username: ${{ steps.getMergedPullRequest.outputs.author }} team: Expensify/expensify newContributorWelcomeMessage: From c5fc4288585c8732e7708453f326196845dabdd3 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 8 Apr 2022 07:28:33 +0000 Subject: [PATCH 45/46] Update version to 1.1.53-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b3bb898ee2e3..f49efa89d896 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -152,8 +152,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001015200 - versionName "1.1.52-0" + versionCode 1001015300 + versionName "1.1.53-0" } splits { abi { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 92b1d3b79059..c9c5c17d686b 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.52 + 1.1.53 CFBundleSignature ???? CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 1.1.52.0 + 1.1.53.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 3fd8e939d051..8511ab51dec8 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.1.52 + 1.1.53 CFBundleSignature ???? CFBundleVersion - 1.1.52.0 + 1.1.53.0 diff --git a/package-lock.json b/package-lock.json index d8a72c7252c2..0c7f4052613c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.52-0", + "version": "1.1.53-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8fbef0136519..3118c4e6c72a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.52-0", + "version": "1.1.53-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 09712f6df143df115bfe37c36e7efa8c11237548 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 8 Apr 2022 07:33:46 +0000 Subject: [PATCH 46/46] Update version to 1.1.54-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f49efa89d896..97ba80bd0d8d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -152,8 +152,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001015300 - versionName "1.1.53-0" + versionCode 1001015400 + versionName "1.1.54-0" } splits { abi { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index c9c5c17d686b..d310f3305732 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.53 + 1.1.54 CFBundleSignature ???? CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 1.1.53.0 + 1.1.54.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 8511ab51dec8..4e8be96624b7 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.1.53 + 1.1.54 CFBundleSignature ???? CFBundleVersion - 1.1.53.0 + 1.1.54.0 diff --git a/package-lock.json b/package-lock.json index 0c7f4052613c..5c7f3b504abd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.53-0", + "version": "1.1.54-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3118c4e6c72a..468a96150c38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.53-0", + "version": "1.1.54-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",