diff --git a/.github/actions/bumpVersion/index.js b/.github/actions/bumpVersion/index.js index 4d28f7d1c163..1443315c1480 100644 --- a/.github/actions/bumpVersion/index.js +++ b/.github/actions/bumpVersion/index.js @@ -1629,7 +1629,7 @@ function _objectWithoutProperties(source, excluded) { return target; } -const VERSION = "3.2.4"; +const VERSION = "3.3.1"; class Octokit { constructor(options = {}) { @@ -1638,6 +1638,7 @@ class Octokit { baseUrl: request.request.endpoint.DEFAULTS.baseUrl, headers: {}, request: Object.assign({}, options.request, { + // @ts-ignore internal usage only, no need to type hook: hook.bind(null, "request") }), mediaType: { @@ -2133,7 +2134,7 @@ function withDefaults(oldDefaults, newDefaults) { }); } -const VERSION = "6.0.10"; +const VERSION = "6.0.11"; const userAgent = `octokit-endpoint.js/${VERSION} ${universalUserAgent.getUserAgent()}`; // DEFAULTS has all properties set that EndpointOptions has, except url. // So we use RequestParameters and add method as additional required property. @@ -2216,7 +2217,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); var request = __nccwpck_require__(6234); var universalUserAgent = __nccwpck_require__(5030); -const VERSION = "4.5.8"; +const VERSION = "4.6.1"; class GraphqlError extends Error { constructor(request, response) { @@ -2239,10 +2240,18 @@ class GraphqlError extends Error { } const NON_VARIABLE_OPTIONS = ["method", "baseUrl", "url", "headers", "request", "query", "mediaType"]; +const FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"]; const GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; function graphql(request, query, options) { - if (typeof query === "string" && options && "query" in options) { - return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + if (options) { + if (typeof query === "string" && "query" in options) { + return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + } + + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; + return Promise.reject(new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`)); + } } const parsedOptions = typeof query === "string" ? Object.assign({ @@ -3713,7 +3722,7 @@ var isPlainObject = __nccwpck_require__(9062); var nodeFetch = _interopDefault(__nccwpck_require__(467)); var requestError = __nccwpck_require__(537); -const VERSION = "5.4.12"; +const VERSION = "5.4.14"; function getBufferResponse(response) { return response.arrayBuffer(); @@ -3966,51 +3975,51 @@ module.exports.Collection = Hook.Collection /***/ 5549: /***/ ((module) => { -module.exports = addHook +module.exports = addHook; -function addHook (state, kind, name, hook) { - var orig = hook +function addHook(state, kind, name, hook) { + var orig = hook; if (!state.registry[name]) { - state.registry[name] = [] + state.registry[name] = []; } - if (kind === 'before') { + if (kind === "before") { hook = function (method, options) { return Promise.resolve() .then(orig.bind(null, options)) - .then(method.bind(null, options)) - } + .then(method.bind(null, options)); + }; } - if (kind === 'after') { + if (kind === "after") { hook = function (method, options) { - var result + var result; return Promise.resolve() .then(method.bind(null, options)) .then(function (result_) { - result = result_ - return orig(result, options) + result = result_; + return orig(result, options); }) .then(function () { - return result - }) - } + return result; + }); + }; } - if (kind === 'error') { + if (kind === "error") { hook = function (method, options) { return Promise.resolve() .then(method.bind(null, options)) .catch(function (error) { - return orig(error, options) - }) - } + return orig(error, options); + }); + }; } state.registry[name].push({ hook: hook, - orig: orig - }) + orig: orig, + }); } @@ -4019,33 +4028,32 @@ function addHook (state, kind, name, hook) { /***/ 4670: /***/ ((module) => { -module.exports = register +module.exports = register; -function register (state, name, method, options) { - if (typeof method !== 'function') { - throw new Error('method for before hook must be a function') +function register(state, name, method, options) { + if (typeof method !== "function") { + throw new Error("method for before hook must be a function"); } if (!options) { - options = {} + options = {}; } if (Array.isArray(name)) { return name.reverse().reduce(function (callback, name) { - return register.bind(null, state, name, callback, options) - }, method)() + return register.bind(null, state, name, callback, options); + }, method)(); } - return Promise.resolve() - .then(function () { - if (!state.registry[name]) { - return method(options) - } + return Promise.resolve().then(function () { + if (!state.registry[name]) { + return method(options); + } - return (state.registry[name]).reduce(function (method, registered) { - return registered.hook.bind(null, method, options) - }, method)() - }) + return state.registry[name].reduce(function (method, registered) { + return registered.hook.bind(null, method, options); + }, method)(); + }); } @@ -4054,22 +4062,24 @@ function register (state, name, method, options) { /***/ 6819: /***/ ((module) => { -module.exports = removeHook +module.exports = removeHook; -function removeHook (state, name, method) { +function removeHook(state, name, method) { if (!state.registry[name]) { - return + return; } var index = state.registry[name] - .map(function (registered) { return registered.orig }) - .indexOf(method) + .map(function (registered) { + return registered.orig; + }) + .indexOf(method); if (index === -1) { - return + return; } - state.registry[name].splice(index, 1) + state.registry[name].splice(index, 1); } diff --git a/.github/actions/createOrUpdateStagingDeploy/action.yml b/.github/actions/createOrUpdateStagingDeploy/action.yml index 4277a29587c6..4f73ac487371 100644 --- a/.github/actions/createOrUpdateStagingDeploy/action.yml +++ b/.github/actions/createOrUpdateStagingDeploy/action.yml @@ -1,12 +1,12 @@ name: 'Create or Update StagingDeployCash' description: 'Creates a new StagingDeployCash issue if there is not one open, or updates the existing one.' inputs: - NPM_VERSION: - description: The new NPM version of the StagingDeployCash issue. - required: true GITHUB_TOKEN: description: Auth token for Expensify.cash Github required: true + NPM_VERSION: + description: The new NPM version of the StagingDeployCash issue. + required: false NEW_PULL_REQUESTS: description: A comma-separated list of pull request URLs to add to the open StagingDeployCash issue. required: false diff --git a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js index 259eff10e07c..e8703fb7885a 100644 --- a/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js +++ b/.github/actions/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js @@ -5,7 +5,7 @@ const moment = require('moment'); const GithubUtils = require('../../libs/GithubUtils'); const GitUtils = require('../../libs/GitUtils'); -const newVersion = core.getInput('NPM_VERSION', {required: true}); +const newVersion = core.getInput('NPM_VERSION'); const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); const githubUtils = new GithubUtils(octokit); @@ -42,8 +42,8 @@ githubUtils.getStagingDeployCash() // Unexpected error! console.error('Unexpected error occurred finding the StagingDeployCash!' - + ' There may have been more than one open StagingDeployCash found,' - + ' or there was some other problem with the Github API request.', err); + + ' There may have been more than one open StagingDeployCash found,' + + ' or there was some other problem with the Github API request.', err); core.setFailed(err); }) .then((githubResponse) => { diff --git a/.github/actions/createOrUpdateStagingDeploy/index.js b/.github/actions/createOrUpdateStagingDeploy/index.js index a222120ea722..c19c698b4df6 100644 --- a/.github/actions/createOrUpdateStagingDeploy/index.js +++ b/.github/actions/createOrUpdateStagingDeploy/index.js @@ -15,7 +15,7 @@ const moment = __nccwpck_require__(9623); const GithubUtils = __nccwpck_require__(7999); const GitUtils = __nccwpck_require__(669); -const newVersion = core.getInput('NPM_VERSION', {required: true}); +const newVersion = core.getInput('NPM_VERSION'); const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); const githubUtils = new GithubUtils(octokit); @@ -52,8 +52,8 @@ githubUtils.getStagingDeployCash() // Unexpected error! console.error('Unexpected error occurred finding the StagingDeployCash!' - + ' There may have been more than one open StagingDeployCash found,' - + ' or there was some other problem with the Github API request.', err); + + ' There may have been more than one open StagingDeployCash found,' + + ' or there was some other problem with the Github API request.', err); core.setFailed(err); }) .then((githubResponse) => { @@ -352,22 +352,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { let issueNumber; return this.getStagingDeployCash() .then(({ url, + tag: oldTag, PRList: oldPRs, deployBlockers: oldDeployBlockers, }) => { issueNumber = GithubUtils.getIssueNumberFromURL(url); + // If we aren't sent a tag, then use the existing tag + const tag = _.isEmpty(newTag) ? oldTag : newTag; + const PRList = _.sortBy( _.union(oldPRs, _.map(newPRs, URL => ({ url: URL, @@ -386,7 +390,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), @@ -1859,7 +1863,7 @@ function _objectWithoutProperties(source, excluded) { return target; } -const VERSION = "3.2.4"; +const VERSION = "3.3.1"; class Octokit { constructor(options = {}) { @@ -1868,6 +1872,7 @@ class Octokit { baseUrl: request.request.endpoint.DEFAULTS.baseUrl, headers: {}, request: Object.assign({}, options.request, { + // @ts-ignore internal usage only, no need to type hook: hook.bind(null, "request") }), mediaType: { @@ -2363,7 +2368,7 @@ function withDefaults(oldDefaults, newDefaults) { }); } -const VERSION = "6.0.10"; +const VERSION = "6.0.11"; const userAgent = `octokit-endpoint.js/${VERSION} ${universalUserAgent.getUserAgent()}`; // DEFAULTS has all properties set that EndpointOptions has, except url. // So we use RequestParameters and add method as additional required property. @@ -2446,7 +2451,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); var request = __nccwpck_require__(6234); var universalUserAgent = __nccwpck_require__(5030); -const VERSION = "4.5.8"; +const VERSION = "4.6.1"; class GraphqlError extends Error { constructor(request, response) { @@ -2469,10 +2474,18 @@ class GraphqlError extends Error { } const NON_VARIABLE_OPTIONS = ["method", "baseUrl", "url", "headers", "request", "query", "mediaType"]; +const FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"]; const GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; function graphql(request, query, options) { - if (typeof query === "string" && options && "query" in options) { - return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + if (options) { + if (typeof query === "string" && "query" in options) { + return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + } + + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; + return Promise.reject(new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`)); + } } const parsedOptions = typeof query === "string" ? Object.assign({ @@ -3943,7 +3956,7 @@ var isPlainObject = __nccwpck_require__(9062); var nodeFetch = _interopDefault(__nccwpck_require__(467)); var requestError = __nccwpck_require__(537); -const VERSION = "5.4.12"; +const VERSION = "5.4.14"; function getBufferResponse(response) { return response.arrayBuffer(); @@ -4196,51 +4209,51 @@ module.exports.Collection = Hook.Collection /***/ 5549: /***/ ((module) => { -module.exports = addHook +module.exports = addHook; -function addHook (state, kind, name, hook) { - var orig = hook +function addHook(state, kind, name, hook) { + var orig = hook; if (!state.registry[name]) { - state.registry[name] = [] + state.registry[name] = []; } - if (kind === 'before') { + if (kind === "before") { hook = function (method, options) { return Promise.resolve() .then(orig.bind(null, options)) - .then(method.bind(null, options)) - } + .then(method.bind(null, options)); + }; } - if (kind === 'after') { + if (kind === "after") { hook = function (method, options) { - var result + var result; return Promise.resolve() .then(method.bind(null, options)) .then(function (result_) { - result = result_ - return orig(result, options) + result = result_; + return orig(result, options); }) .then(function () { - return result - }) - } + return result; + }); + }; } - if (kind === 'error') { + if (kind === "error") { hook = function (method, options) { return Promise.resolve() .then(method.bind(null, options)) .catch(function (error) { - return orig(error, options) - }) - } + return orig(error, options); + }); + }; } state.registry[name].push({ hook: hook, - orig: orig - }) + orig: orig, + }); } @@ -4249,33 +4262,32 @@ function addHook (state, kind, name, hook) { /***/ 4670: /***/ ((module) => { -module.exports = register +module.exports = register; -function register (state, name, method, options) { - if (typeof method !== 'function') { - throw new Error('method for before hook must be a function') +function register(state, name, method, options) { + if (typeof method !== "function") { + throw new Error("method for before hook must be a function"); } if (!options) { - options = {} + options = {}; } if (Array.isArray(name)) { return name.reverse().reduce(function (callback, name) { - return register.bind(null, state, name, callback, options) - }, method)() + return register.bind(null, state, name, callback, options); + }, method)(); } - return Promise.resolve() - .then(function () { - if (!state.registry[name]) { - return method(options) - } + return Promise.resolve().then(function () { + if (!state.registry[name]) { + return method(options); + } - return (state.registry[name]).reduce(function (method, registered) { - return registered.hook.bind(null, method, options) - }, method)() - }) + return state.registry[name].reduce(function (method, registered) { + return registered.hook.bind(null, method, options); + }, method)(); + }); } @@ -4284,22 +4296,24 @@ function register (state, name, method, options) { /***/ 6819: /***/ ((module) => { -module.exports = removeHook +module.exports = removeHook; -function removeHook (state, name, method) { +function removeHook(state, name, method) { if (!state.registry[name]) { - return + return; } var index = state.registry[name] - .map(function (registered) { return registered.orig }) - .indexOf(method) + .map(function (registered) { + return registered.orig; + }) + .indexOf(method); if (index === -1) { - return + return; } - state.registry[name].splice(index, 1) + state.registry[name].splice(index, 1); } diff --git a/.github/actions/getReleasePullRequestList/index.js b/.github/actions/getReleasePullRequestList/index.js index e98ba6d6f88b..0cdff738ab98 100644 --- a/.github/actions/getReleasePullRequestList/index.js +++ b/.github/actions/getReleasePullRequestList/index.js @@ -1390,7 +1390,7 @@ function _objectWithoutProperties(source, excluded) { return target; } -const VERSION = "3.2.4"; +const VERSION = "3.3.1"; class Octokit { constructor(options = {}) { @@ -1399,6 +1399,7 @@ class Octokit { baseUrl: request.request.endpoint.DEFAULTS.baseUrl, headers: {}, request: Object.assign({}, options.request, { + // @ts-ignore internal usage only, no need to type hook: hook.bind(null, "request") }), mediaType: { @@ -1894,7 +1895,7 @@ function withDefaults(oldDefaults, newDefaults) { }); } -const VERSION = "6.0.10"; +const VERSION = "6.0.11"; const userAgent = `octokit-endpoint.js/${VERSION} ${universalUserAgent.getUserAgent()}`; // DEFAULTS has all properties set that EndpointOptions has, except url. // So we use RequestParameters and add method as additional required property. @@ -1977,7 +1978,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); var request = __nccwpck_require__(6234); var universalUserAgent = __nccwpck_require__(5030); -const VERSION = "4.5.8"; +const VERSION = "4.6.1"; class GraphqlError extends Error { constructor(request, response) { @@ -2000,10 +2001,18 @@ class GraphqlError extends Error { } const NON_VARIABLE_OPTIONS = ["method", "baseUrl", "url", "headers", "request", "query", "mediaType"]; +const FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"]; const GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; function graphql(request, query, options) { - if (typeof query === "string" && options && "query" in options) { - return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + if (options) { + if (typeof query === "string" && "query" in options) { + return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + } + + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; + return Promise.reject(new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`)); + } } const parsedOptions = typeof query === "string" ? Object.assign({ @@ -3474,7 +3483,7 @@ var isPlainObject = __nccwpck_require__(9062); var nodeFetch = _interopDefault(__nccwpck_require__(467)); var requestError = __nccwpck_require__(537); -const VERSION = "5.4.12"; +const VERSION = "5.4.14"; function getBufferResponse(response) { return response.arrayBuffer(); @@ -3727,51 +3736,51 @@ module.exports.Collection = Hook.Collection /***/ 5549: /***/ ((module) => { -module.exports = addHook +module.exports = addHook; -function addHook (state, kind, name, hook) { - var orig = hook +function addHook(state, kind, name, hook) { + var orig = hook; if (!state.registry[name]) { - state.registry[name] = [] + state.registry[name] = []; } - if (kind === 'before') { + if (kind === "before") { hook = function (method, options) { return Promise.resolve() .then(orig.bind(null, options)) - .then(method.bind(null, options)) - } + .then(method.bind(null, options)); + }; } - if (kind === 'after') { + if (kind === "after") { hook = function (method, options) { - var result + var result; return Promise.resolve() .then(method.bind(null, options)) .then(function (result_) { - result = result_ - return orig(result, options) + result = result_; + return orig(result, options); }) .then(function () { - return result - }) - } + return result; + }); + }; } - if (kind === 'error') { + if (kind === "error") { hook = function (method, options) { return Promise.resolve() .then(method.bind(null, options)) .catch(function (error) { - return orig(error, options) - }) - } + return orig(error, options); + }); + }; } state.registry[name].push({ hook: hook, - orig: orig - }) + orig: orig, + }); } @@ -3780,33 +3789,32 @@ function addHook (state, kind, name, hook) { /***/ 4670: /***/ ((module) => { -module.exports = register +module.exports = register; -function register (state, name, method, options) { - if (typeof method !== 'function') { - throw new Error('method for before hook must be a function') +function register(state, name, method, options) { + if (typeof method !== "function") { + throw new Error("method for before hook must be a function"); } if (!options) { - options = {} + options = {}; } if (Array.isArray(name)) { return name.reverse().reduce(function (callback, name) { - return register.bind(null, state, name, callback, options) - }, method)() + return register.bind(null, state, name, callback, options); + }, method)(); } - return Promise.resolve() - .then(function () { - if (!state.registry[name]) { - return method(options) - } + return Promise.resolve().then(function () { + if (!state.registry[name]) { + return method(options); + } - return (state.registry[name]).reduce(function (method, registered) { - return registered.hook.bind(null, method, options) - }, method)() - }) + return state.registry[name].reduce(function (method, registered) { + return registered.hook.bind(null, method, options); + }, method)(); + }); } @@ -3815,22 +3823,24 @@ function register (state, name, method, options) { /***/ 6819: /***/ ((module) => { -module.exports = removeHook +module.exports = removeHook; -function removeHook (state, name, method) { +function removeHook(state, name, method) { if (!state.registry[name]) { - return + return; } var index = state.registry[name] - .map(function (registered) { return registered.orig }) - .indexOf(method) + .map(function (registered) { + return registered.orig; + }) + .indexOf(method); if (index === -1) { - return + return; } - state.registry[name].splice(index, 1) + state.registry[name].splice(index, 1); } diff --git a/.github/actions/isStagingDeployLocked/index.js b/.github/actions/isStagingDeployLocked/index.js index 61127cabc765..39a47d553a54 100644 --- a/.github/actions/isStagingDeployLocked/index.js +++ b/.github/actions/isStagingDeployLocked/index.js @@ -6,22 +6,33 @@ module.exports = /******/ var __webpack_modules__ = ({ /***/ 608: -/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { const _ = __nccwpck_require__(4987); const core = __nccwpck_require__(2186); const github = __nccwpck_require__(5438); const GithubUtils = __nccwpck_require__(7999); -const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); -const githubUtils = new GithubUtils(octokit); +const run = function () { + const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); + const githubUtils = new GithubUtils(octokit); -githubUtils.getStagingDeployCash() - .then(({labels}) => core.setOutput('IS_LOCKED', _.contains(_.pluck(labels, 'name'), '🔐 LockCashDeploys 🔐'))) - .catch((err) => { - console.warn('No open StagingDeployCash found, continuing...', err); - core.setOutput('IS_LOCKED', false); - }); + return githubUtils.getStagingDeployCash() + .then(({labels}) => { + console.log(`Found StagingDeployCash with labels: ${_.pluck(labels, 'name')}`); + core.setOutput('IS_LOCKED', _.contains(_.pluck(labels, 'name'), '🔐 LockCashDeploys 🔐')); + }) + .catch((err) => { + console.warn('No open StagingDeployCash found, continuing...', err); + core.setOutput('IS_LOCKED', false); + }); +}; + +if (require.main === require.cache[eval('__filename')]) { + run(); +} + +module.exports = run; /***/ }), @@ -267,22 +278,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { let issueNumber; return this.getStagingDeployCash() .then(({ url, + tag: oldTag, PRList: oldPRs, deployBlockers: oldDeployBlockers, }) => { issueNumber = GithubUtils.getIssueNumberFromURL(url); + // If we aren't sent a tag, then use the existing tag + const tag = _.isEmpty(newTag) ? oldTag : newTag; + const PRList = _.sortBy( _.union(oldPRs, _.map(newPRs, URL => ({ url: URL, @@ -301,7 +316,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), @@ -1774,7 +1789,7 @@ function _objectWithoutProperties(source, excluded) { return target; } -const VERSION = "3.2.4"; +const VERSION = "3.3.1"; class Octokit { constructor(options = {}) { @@ -1783,6 +1798,7 @@ class Octokit { baseUrl: request.request.endpoint.DEFAULTS.baseUrl, headers: {}, request: Object.assign({}, options.request, { + // @ts-ignore internal usage only, no need to type hook: hook.bind(null, "request") }), mediaType: { @@ -2278,7 +2294,7 @@ function withDefaults(oldDefaults, newDefaults) { }); } -const VERSION = "6.0.10"; +const VERSION = "6.0.11"; const userAgent = `octokit-endpoint.js/${VERSION} ${universalUserAgent.getUserAgent()}`; // DEFAULTS has all properties set that EndpointOptions has, except url. // So we use RequestParameters and add method as additional required property. @@ -2361,7 +2377,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); var request = __nccwpck_require__(6234); var universalUserAgent = __nccwpck_require__(5030); -const VERSION = "4.5.8"; +const VERSION = "4.6.1"; class GraphqlError extends Error { constructor(request, response) { @@ -2384,10 +2400,18 @@ class GraphqlError extends Error { } const NON_VARIABLE_OPTIONS = ["method", "baseUrl", "url", "headers", "request", "query", "mediaType"]; +const FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"]; const GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; function graphql(request, query, options) { - if (typeof query === "string" && options && "query" in options) { - return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + if (options) { + if (typeof query === "string" && "query" in options) { + return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + } + + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; + return Promise.reject(new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`)); + } } const parsedOptions = typeof query === "string" ? Object.assign({ @@ -3858,7 +3882,7 @@ var isPlainObject = __nccwpck_require__(9062); var nodeFetch = _interopDefault(__nccwpck_require__(467)); var requestError = __nccwpck_require__(537); -const VERSION = "5.4.12"; +const VERSION = "5.4.14"; function getBufferResponse(response) { return response.arrayBuffer(); @@ -4111,51 +4135,51 @@ module.exports.Collection = Hook.Collection /***/ 5549: /***/ ((module) => { -module.exports = addHook +module.exports = addHook; -function addHook (state, kind, name, hook) { - var orig = hook +function addHook(state, kind, name, hook) { + var orig = hook; if (!state.registry[name]) { - state.registry[name] = [] + state.registry[name] = []; } - if (kind === 'before') { + if (kind === "before") { hook = function (method, options) { return Promise.resolve() .then(orig.bind(null, options)) - .then(method.bind(null, options)) - } + .then(method.bind(null, options)); + }; } - if (kind === 'after') { + if (kind === "after") { hook = function (method, options) { - var result + var result; return Promise.resolve() .then(method.bind(null, options)) .then(function (result_) { - result = result_ - return orig(result, options) + result = result_; + return orig(result, options); }) .then(function () { - return result - }) - } + return result; + }); + }; } - if (kind === 'error') { + if (kind === "error") { hook = function (method, options) { return Promise.resolve() .then(method.bind(null, options)) .catch(function (error) { - return orig(error, options) - }) - } + return orig(error, options); + }); + }; } state.registry[name].push({ hook: hook, - orig: orig - }) + orig: orig, + }); } @@ -4164,33 +4188,32 @@ function addHook (state, kind, name, hook) { /***/ 4670: /***/ ((module) => { -module.exports = register +module.exports = register; -function register (state, name, method, options) { - if (typeof method !== 'function') { - throw new Error('method for before hook must be a function') +function register(state, name, method, options) { + if (typeof method !== "function") { + throw new Error("method for before hook must be a function"); } if (!options) { - options = {} + options = {}; } if (Array.isArray(name)) { return name.reverse().reduce(function (callback, name) { - return register.bind(null, state, name, callback, options) - }, method)() + return register.bind(null, state, name, callback, options); + }, method)(); } - return Promise.resolve() - .then(function () { - if (!state.registry[name]) { - return method(options) - } + return Promise.resolve().then(function () { + if (!state.registry[name]) { + return method(options); + } - return (state.registry[name]).reduce(function (method, registered) { - return registered.hook.bind(null, method, options) - }, method)() - }) + return state.registry[name].reduce(function (method, registered) { + return registered.hook.bind(null, method, options); + }, method)(); + }); } @@ -4199,22 +4222,24 @@ function register (state, name, method, options) { /***/ 6819: /***/ ((module) => { -module.exports = removeHook +module.exports = removeHook; -function removeHook (state, name, method) { +function removeHook(state, name, method) { if (!state.registry[name]) { - return + return; } var index = state.registry[name] - .map(function (registered) { return registered.orig }) - .indexOf(method) + .map(function (registered) { + return registered.orig; + }) + .indexOf(method); if (index === -1) { - return + return; } - state.registry[name].splice(index, 1) + state.registry[name].splice(index, 1); } diff --git a/.github/actions/isStagingDeployLocked/isStagingDeployLocked.js b/.github/actions/isStagingDeployLocked/isStagingDeployLocked.js index f09d3616171d..fd3464f8d6da 100644 --- a/.github/actions/isStagingDeployLocked/isStagingDeployLocked.js +++ b/.github/actions/isStagingDeployLocked/isStagingDeployLocked.js @@ -3,12 +3,23 @@ const core = require('@actions/core'); const github = require('@actions/github'); const GithubUtils = require('../../libs/GithubUtils'); -const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); -const githubUtils = new GithubUtils(octokit); - -githubUtils.getStagingDeployCash() - .then(({labels}) => core.setOutput('IS_LOCKED', _.contains(_.pluck(labels, 'name'), '🔐 LockCashDeploys 🔐'))) - .catch((err) => { - console.warn('No open StagingDeployCash found, continuing...', err); - core.setOutput('IS_LOCKED', false); - }); +const run = function () { + const octokit = github.getOctokit(core.getInput('GITHUB_TOKEN', {required: true})); + const githubUtils = new GithubUtils(octokit); + + return githubUtils.getStagingDeployCash() + .then(({labels}) => { + console.log(`Found StagingDeployCash with labels: ${_.pluck(labels, 'name')}`); + core.setOutput('IS_LOCKED', _.contains(_.pluck(labels, 'name'), '🔐 LockCashDeploys 🔐')); + }) + .catch((err) => { + console.warn('No open StagingDeployCash found, continuing...', err); + core.setOutput('IS_LOCKED', false); + }); +}; + +if (require.main === module) { + run(); +} + +module.exports = run; diff --git a/.github/actions/markPullRequestsAsDeployed/index.js b/.github/actions/markPullRequestsAsDeployed/index.js index 2076df296212..16f50a2e15ec 100644 --- a/.github/actions/markPullRequestsAsDeployed/index.js +++ b/.github/actions/markPullRequestsAsDeployed/index.js @@ -31,10 +31,10 @@ const githubUtils = new GithubUtils(octokit); prList.forEach((pr) => { githubUtils.createComment(github.context.repo.repo, pr, message, octokit) .then(() => { - console.log(`Comment created on #${pr} successfully`); + console.log(`Comment created on #${pr} successfully 🎉`); }) .catch((err) => { - console.log(`Unable to write comment on #${pr}`); + console.log(`Unable to write comment on #${pr} 😞`); core.setFailed(err.message); }); }); @@ -283,22 +283,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { let issueNumber; return this.getStagingDeployCash() .then(({ url, + tag: oldTag, PRList: oldPRs, deployBlockers: oldDeployBlockers, }) => { issueNumber = GithubUtils.getIssueNumberFromURL(url); + // If we aren't sent a tag, then use the existing tag + const tag = _.isEmpty(newTag) ? oldTag : newTag; + const PRList = _.sortBy( _.union(oldPRs, _.map(newPRs, URL => ({ url: URL, @@ -317,7 +321,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), @@ -1790,7 +1794,7 @@ function _objectWithoutProperties(source, excluded) { return target; } -const VERSION = "3.2.4"; +const VERSION = "3.3.1"; class Octokit { constructor(options = {}) { @@ -1799,6 +1803,7 @@ class Octokit { baseUrl: request.request.endpoint.DEFAULTS.baseUrl, headers: {}, request: Object.assign({}, options.request, { + // @ts-ignore internal usage only, no need to type hook: hook.bind(null, "request") }), mediaType: { @@ -2294,7 +2299,7 @@ function withDefaults(oldDefaults, newDefaults) { }); } -const VERSION = "6.0.10"; +const VERSION = "6.0.11"; const userAgent = `octokit-endpoint.js/${VERSION} ${universalUserAgent.getUserAgent()}`; // DEFAULTS has all properties set that EndpointOptions has, except url. // So we use RequestParameters and add method as additional required property. @@ -2377,7 +2382,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); var request = __nccwpck_require__(6234); var universalUserAgent = __nccwpck_require__(5030); -const VERSION = "4.5.8"; +const VERSION = "4.6.1"; class GraphqlError extends Error { constructor(request, response) { @@ -2400,10 +2405,18 @@ class GraphqlError extends Error { } const NON_VARIABLE_OPTIONS = ["method", "baseUrl", "url", "headers", "request", "query", "mediaType"]; +const FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"]; const GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; function graphql(request, query, options) { - if (typeof query === "string" && options && "query" in options) { - return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + if (options) { + if (typeof query === "string" && "query" in options) { + return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`)); + } + + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue; + return Promise.reject(new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`)); + } } const parsedOptions = typeof query === "string" ? Object.assign({ @@ -3874,7 +3887,7 @@ var isPlainObject = __nccwpck_require__(9062); var nodeFetch = _interopDefault(__nccwpck_require__(467)); var requestError = __nccwpck_require__(537); -const VERSION = "5.4.12"; +const VERSION = "5.4.14"; function getBufferResponse(response) { return response.arrayBuffer(); @@ -4127,51 +4140,51 @@ module.exports.Collection = Hook.Collection /***/ 5549: /***/ ((module) => { -module.exports = addHook +module.exports = addHook; -function addHook (state, kind, name, hook) { - var orig = hook +function addHook(state, kind, name, hook) { + var orig = hook; if (!state.registry[name]) { - state.registry[name] = [] + state.registry[name] = []; } - if (kind === 'before') { + if (kind === "before") { hook = function (method, options) { return Promise.resolve() .then(orig.bind(null, options)) - .then(method.bind(null, options)) - } + .then(method.bind(null, options)); + }; } - if (kind === 'after') { + if (kind === "after") { hook = function (method, options) { - var result + var result; return Promise.resolve() .then(method.bind(null, options)) .then(function (result_) { - result = result_ - return orig(result, options) + result = result_; + return orig(result, options); }) .then(function () { - return result - }) - } + return result; + }); + }; } - if (kind === 'error') { + if (kind === "error") { hook = function (method, options) { return Promise.resolve() .then(method.bind(null, options)) .catch(function (error) { - return orig(error, options) - }) - } + return orig(error, options); + }); + }; } state.registry[name].push({ hook: hook, - orig: orig - }) + orig: orig, + }); } @@ -4180,33 +4193,32 @@ function addHook (state, kind, name, hook) { /***/ 4670: /***/ ((module) => { -module.exports = register +module.exports = register; -function register (state, name, method, options) { - if (typeof method !== 'function') { - throw new Error('method for before hook must be a function') +function register(state, name, method, options) { + if (typeof method !== "function") { + throw new Error("method for before hook must be a function"); } if (!options) { - options = {} + options = {}; } if (Array.isArray(name)) { return name.reverse().reduce(function (callback, name) { - return register.bind(null, state, name, callback, options) - }, method)() + return register.bind(null, state, name, callback, options); + }, method)(); } - return Promise.resolve() - .then(function () { - if (!state.registry[name]) { - return method(options) - } + return Promise.resolve().then(function () { + if (!state.registry[name]) { + return method(options); + } - return (state.registry[name]).reduce(function (method, registered) { - return registered.hook.bind(null, method, options) - }, method)() - }) + return state.registry[name].reduce(function (method, registered) { + return registered.hook.bind(null, method, options); + }, method)(); + }); } @@ -4215,22 +4227,24 @@ function register (state, name, method, options) { /***/ 6819: /***/ ((module) => { -module.exports = removeHook +module.exports = removeHook; -function removeHook (state, name, method) { +function removeHook(state, name, method) { if (!state.registry[name]) { - return + return; } var index = state.registry[name] - .map(function (registered) { return registered.orig }) - .indexOf(method) + .map(function (registered) { + return registered.orig; + }) + .indexOf(method); if (index === -1) { - return + return; } - state.registry[name].splice(index, 1) + state.registry[name].splice(index, 1); } diff --git a/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js b/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js index 11d5e98d88c1..46ed3c18df80 100644 --- a/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js +++ b/.github/actions/markPullRequestsAsDeployed/markPullRequestsAsDeployed.js @@ -21,10 +21,10 @@ const githubUtils = new GithubUtils(octokit); prList.forEach((pr) => { githubUtils.createComment(github.context.repo.repo, pr, message, octokit) .then(() => { - console.log(`Comment created on #${pr} successfully`); + console.log(`Comment created on #${pr} successfully 🎉`); }) .catch((err) => { - console.log(`Unable to write comment on #${pr}`); + console.log(`Unable to write comment on #${pr} 😞`); core.setFailed(err.message); }); }); diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js index 8ddcbc0bae5b..d28b221fd858 100644 --- a/.github/libs/GithubUtils.js +++ b/.github/libs/GithubUtils.js @@ -236,22 +236,26 @@ class GithubUtils { /** * Updates the existing open StagingDeployCash issue. * - * @param {String} newTag + * @param {String} [newTag] * @param {Array} newPRs * @param {Array} newDeployBlockers * @returns {Promise} * @throws {Error} If the StagingDeployCash could not be found or updated. */ - updateStagingDeployCash(newTag, newPRs, newDeployBlockers) { + updateStagingDeployCash(newTag = '', newPRs, newDeployBlockers) { let issueNumber; return this.getStagingDeployCash() .then(({ url, + tag: oldTag, PRList: oldPRs, deployBlockers: oldDeployBlockers, }) => { issueNumber = GithubUtils.getIssueNumberFromURL(url); + // If we aren't sent a tag, then use the existing tag + const tag = _.isEmpty(newTag) ? oldTag : newTag; + const PRList = _.sortBy( _.union(oldPRs, _.map(newPRs, URL => ({ url: URL, @@ -270,7 +274,7 @@ class GithubUtils { ); return this.generateStagingDeployCashBody( - newTag, + tag, _.pluck(PRList, 'url'), _.pluck(_.where(PRList, {isVerified: true}), 'url'), _.pluck(deployBlockers, 'url'), diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index b1faf6401c64..e21ee6f8ffa1 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -8,9 +8,9 @@ on: status: {} jobs: - automerge: + master: runs-on: ubuntu-latest - + if: github.event.pull_request.base.ref == 'master' && github.actor == 'OSBotify' && github.event.label.name == 'automerge' steps: - name: Export Files Changed id: changed @@ -24,13 +24,14 @@ jobs: uses: hmarr/auto-approve-action@7782c7e2bdf62b4d79bdcded8332808fd2f179cd with: github-token: ${{ secrets.GITHUB_TOKEN }} - if: github.event.label.name == 'automerge' && github.actor == 'OSBotify' && steps.changed.outputs.files_updated == 'android/app/build.gradle ios/ExpensifyCash/Info.plist ios/ExpensifyCashTests/Info.plist package-lock.json package.json' && steps.changed.outputs.files_created == '' && steps.changed.outputs.files_deleted == '' + if: github.event.pull_request.mergeable && steps.changed.outputs.files_updated == 'android/app/build.gradle ios/ExpensifyCash/Info.plist ios/ExpensifyCashTests/Info.plist package-lock.json package.json' && steps.changed.outputs.files_created == '' && steps.changed.outputs.files_deleted == '' - name: Check for an auto merge # Version: 0.12.0 uses: pascalgn/automerge-action@c9bd1823770819dc8fb8a5db2d11a3a95fbe9b07 + if: github.event.pull_request.mergeable && github.event.pull_request.mergeable_state == 'clean' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} # This Slack step is duplicated in all workflows, if you make a change to this step, make sure to update all # the other workflows with the same change @@ -52,3 +53,85 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + staging: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref == 'staging' && github.actor == 'OSBotify' && github.event.label.name == 'automerge' + steps: + - name: Check for an auto approve + # Version: 2.0.0 + uses: hmarr/auto-approve-action@7782c7e2bdf62b4d79bdcded8332808fd2f179cd + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + if: github.event.pull_request.mergeable && github.event.pull_request.head.ref == 'master' + + - name: Check PR mergable states + run: echo "Mergeable - ${{ github.event.pull_request.mergeable }} Clean - ${{ github.event.pull_request.mergeable_state }}" + + # TODO: make sure PR is in "clean" mergeable_state + - name: Check for an auto merge + # Version: 0.12.0 + uses: pascalgn/automerge-action@c9bd1823770819dc8fb8a5db2d11a3a95fbe9b07 + if: github.event.pull_request.mergeable + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # This Slack step is duplicated in all workflows, if you make a change to this step, make sure to update all + # the other workflows with the same change + - uses: 8398a7/action-slack@v3 + name: Job failed Slack notification + if: ${{ failure() }} + with: + status: custom + fields: workflow, repo + custom_payload: | + { + channel: '#announce', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 ${process.env.AS_REPO} failed on ${process.env.AS_WORKFLOW} workflow 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + production: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref == 'production' && github.actor == 'OSBotify' && github.event.label.name == 'automerge' + steps: + - name: Check for an auto approve + # Version: 2.0.0 + uses: hmarr/auto-approve-action@7782c7e2bdf62b4d79bdcded8332808fd2f179cd + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + if: github.event.pull_request.mergeable && github.event.pull_request.head.ref == 'staging' + + - name: Check for an auto merge + # Version: 0.12.0 + uses: pascalgn/automerge-action@c9bd1823770819dc8fb8a5db2d11a3a95fbe9b07 + if: github.event.pull_request.mergeable && github.event.pull_request.mergeable_state == 'clean' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # This Slack step is duplicated in all workflows, if you make a change to this step, make sure to update all + # the other workflows with the same change + - uses: 8398a7/action-slack@v3 + name: Job failed Slack notification + if: ${{ failure() }} + with: + status: custom + fields: workflow, repo + custom_payload: | + { + channel: '#announce', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 ${process.env.AS_REPO} failed on ${process.env.AS_WORKFLOW} workflow 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 1ab328213886..fb259a26b899 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -10,8 +10,11 @@ jobs: CLA: runs-on: ubuntu-latest # This job only runs for pull request comments or pull request target events (not issue comments) - if: github.event.issue.pull_request || github.event_name == 'pull_request_target' + # It does not run for pull requests created by OSBotify + if: ${{ github.event.issue.pull_request || (github.event_name == 'pull_request_target' && github.event.pull_request.user.login != 'OSBotify') }} steps: + # TODO: remove this first run step + - run: echo ${{ github.event.pull_request.user.login }} - uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 id: sign with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000000..4bf63e49e95a --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,57 @@ +name: Deploy code to staging or production + +on: + push: + branches: [staging, production] + +jobs: + validate: + runs-on: ubuntu-latest + outputs: + isAutomergePR: ${{ steps.isAutomergePR.outputs.IS_AUTOMERGE_PR }} + + steps: + - name: Get merged pull request + id: getMergedPullRequest + uses: actions-ecosystem/action-get-merged-pull-request@59afe90821bb0b555082ce8ff1e36b03f91553d9 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if merged pull request was an automatic version bump PR + id: isAutomergePR + run: echo "::set-output name=IS_AUTOMERGE_PR::${{ contains(steps.getMergedPullRequest.outputs.labels, 'automerge') && github.actor == 'OSBotify' }}" + + deployStaging: + runs-on: ubuntu-latest + needs: validate + if: ${{ needs.validate.outputs.isAutomergePR == 'true' && github.ref == 'refs/heads/staging' }} + + steps: + - name: Checkout staging + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + ref: staging + token: ${{ secrets.OS_BOTIFY_TOKEN }} + + - name: Tag version + run: git tag $(npm run print-version --silent) + + - name: 🚀 Push tags to trigger staging deploy 🚀 + run: git push --tags + + deployProduction: + runs-on: ubuntu-latest + needs: validate + if: ${{ needs.validate.outputs.isAutomergePR == 'true' && github.ref == 'refs/heads/production' }} + + steps: + - name: Checkout production + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + ref: production + token: ${{ secrets.OS_BOTIFY_TOKEN }} + + - name: 🚀 Create release to trigger production deploy 🚀 + run: echo "Create release with version $(npm run print-version --silent)" + + diff --git a/.github/workflows/deployBlocker.yml b/.github/workflows/deployBlocker.yml new file mode 100644 index 000000000000..ff09218e0bba --- /dev/null +++ b/.github/workflows/deployBlocker.yml @@ -0,0 +1,50 @@ +name: Update Deploy Blockers + +on: + pull_request: + types: + - labeled + issues: + types: + - labeled + +jobs: + deployBlocker: + runs-on: ubuntu-latest + if: ${{ github.event.label.name == 'DeployBlockerCash' }} + + steps: + - name: Update StagingDeployCash with issue + uses: Expensify/Expensify.cash/.github/actions/createOrUpdateStagingDeploy@master + if: ${{ github.event_name == 'issues' }} + with: + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + NEW_DEPLOY_BLOCKERS: ${{ github.event.issue.html_url }} + + - name: Update StagingDeployCash with issue + uses: Expensify/Expensify.cash/.github/actions/createOrUpdateStagingDeploy@master + if: ${{ github.event_name == 'pull_request' }} + with: + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + NEW_DEPLOY_BLOCKERS: ${{ github.event.pull_request.html_url }} + + # This Slack step is duplicated in all workflows, if you make a change to this step, make sure to update all + # the other workflows with the same change + - uses: 8398a7/action-slack@v3 + name: Job failed Slack notification + if: ${{ failure() }} + with: + status: custom + fields: workflow, repo + custom_payload: | + { + channel: '#announce', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 ${process.env.AS_REPO} failed on ${process.env.AS_WORKFLOW} workflow 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 1c0afae35981..523b17acac0f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -3,6 +3,7 @@ name: E2E iOS Tests on: pull_request: types: [opened, synchronize] + branches-ignore: [staging, production] env: DEVELOPER_DIR: /Applications/Xcode_12.3.app/Contents/Developer diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bf0016694f7a..9fee19b8a69c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,6 +3,7 @@ name: Lint JavaScript on: pull_request: types: [opened, synchronize] + branches-ignore: [staging, production] jobs: lint: diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 26573145d53c..a341cf5a04d0 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -10,6 +10,7 @@ jobs: outputs: mergedPullRequest: ${{ steps.getMergedPullRequest.outputs.number }} isStagingDeployLocked: ${{ steps.isStagingDeployLocked.outputs.IS_LOCKED }} + isVersionBumpPR: ${{ steps.isVersionBumpPR.outputs.IS_VERSION_BUMP_PR }} steps: # Version: 2.3.4 @@ -30,10 +31,14 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + - name: Check if merged pull request was an automatic version bump PR + id: isVersionBumpPR + run: echo "::set-output name=IS_VERSION_BUMP_PR::${{ contains(steps.getMergedPullRequest.outputs.labels, 'automerge') && github.actor == 'OSBotify' }}" + skipDeploy: runs-on: ubuntu-latest needs: chooseDeployActions - if: ${{ needs.chooseDeployActions.outputs.isStagingDeployLocked == 'true' }} + if: ${{ needs.chooseDeployActions.outputs.isStagingDeployLocked == 'true' && needs.chooseDeployActions.outputs.isVersionBumpPR == 'false' }} steps: - name: Comment on deferred PR @@ -47,7 +52,9 @@ jobs: version: runs-on: macos-latest needs: chooseDeployActions - if: ${{ needs.chooseDeployActions.outputs.isStagingDeployLocked == 'false' }} + if: ${{ needs.chooseDeployActions.outputs.isVersionBumpPR == 'false' }} + outputs: + newVersion: ${{ steps.bumpVersion.outputs.NEW_VERSION }} steps: # Version: 2.3.4 @@ -79,7 +86,7 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - - name: Commit and tag new version + - name: Commit new version run: | git add \ ./package.json \ @@ -88,10 +95,6 @@ jobs: ./ios/ExpensifyCash/Info.plist \ ./ios/ExpensifyCashTests/Info.plist git commit -m "Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }}" - git tag ${{ steps.bumpVersion.outputs.NEW_VERSION }} - - - name: Push tags - run: git push --tags - name: Create Pull Request (master) uses: peter-evans/create-pull-request@09b9ac155b0d5ad7d8d157ed32158c1b73689109 @@ -104,17 +107,6 @@ jobs: body: Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }} labels: automerge - - name: Create Pull Request (staging) - uses: peter-evans/create-pull-request@09b9ac155b0d5ad7d8d157ed32158c1b73689109 - with: - token: ${{ secrets.OS_BOTIFY_TOKEN }} - author: OSBotify - base: staging - branch: version-bump-${{ github.sha }} - title: Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }} on staging - body: Update version to ${{ steps.bumpVersion.outputs.NEW_VERSION }} - labels: automerge - - name: Update StagingDeployCash uses: Expensify/Expensify.cash/.github/actions/createOrUpdateStagingDeploy@master with: @@ -142,3 +134,31 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + updateStaging: + runs-on: ubuntu-latest + needs: chooseDeployActions + if: ${{ needs.chooseDeployActions.outputs.isVersionBumpPR == 'true' && needs.chooseDeployActions.outputs.isStagingDeployLocked == 'false' }} + + steps: + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + fetch-depth: 0 + token: ${{ secrets.OS_BOTIFY_TOKEN }} + + - name: Checkout master branch + run: git checkout master + + - name: Set Staging Version + run: echo "STAGING_VERSION=$(npm run print-version --silent)" >> $GITHUB_ENV + + - name: Create Pull Request + # Version: 2.4.3 + uses: repo-sync/pull-request@33777245b1aace1a58c87a29c90321aa7a74bd7d + with: + source_branch: master + destination_branch: staging + pr_label: automerge + github_token: ${{ secrets.OS_BOTIFY_TOKEN }} + pr_title: Update version to ${{ env.STAGING_VERSION }} on staging + pr_body: Update version to ${{ env.STAGING_VERSION }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f14b32ab963..7addc4e18c98 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ name: Jest Unit Tests on: pull_request: types: [opened, synchronize] + branches-ignore: [staging, production] jobs: test: diff --git a/.github/workflows/verifyGithubActionBuilds.yml b/.github/workflows/verifyGithubActionBuilds.yml index 70e2cb9e7dba..21fa3213259b 100644 --- a/.github/workflows/verifyGithubActionBuilds.yml +++ b/.github/workflows/verifyGithubActionBuilds.yml @@ -3,6 +3,7 @@ name: Verify Github Action Builds on: pull_request: types: [opened, synchronize] + branches-ignore: [staging, production] jobs: verify: diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml index 9727d77f3849..2cb062700dd9 100644 --- a/.github/workflows/verifyPodfile.yml +++ b/.github/workflows/verifyPodfile.yml @@ -3,6 +3,7 @@ name: Verify Podfile on: pull_request: types: [opened, synchronize] + branches-ignore: [staging, production] jobs: verify: diff --git a/android/app/build.gradle b/android/app/build.gradle index 75d19da4a53a..588f8d85f4dd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -148,8 +148,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001000246 - versionName "1.0.2-46" + versionCode 1001000262 + versionName "1.0.2-62" } splits { abi { diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index 6b4e6127b9e5..7a30366e119c 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 1.0.2.46 + 1.0.2.62 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index e474557f66bf..af16fd08e471 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.2.46 + 1.0.2.62 diff --git a/package-lock.json b/package-lock.json index a90ea796daa2..98274c18daeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.2-46", + "version": "1.0.2-62", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3122,35 +3122,36 @@ } }, "@octokit/auth-token": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.4.tgz", - "integrity": "sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz", + "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==", "dev": true, "requires": { - "@octokit/types": "^6.0.0" + "@octokit/types": "^6.0.3" } }, "@octokit/core": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.4.tgz", - "integrity": "sha512-d9dTsqdePBqOn7aGkyRFe7pQpCXdibSJ5SFnrTr0axevObZrpz3qkWm7t/NjYv5a66z6vhfteriaq4FRz3e0Qg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.3.1.tgz", + "integrity": "sha512-Dc5NNQOYjgZU5S1goN6A/E500yXOfDUFRGQB8/2Tl16AcfvS3H9PudyOe3ZNE/MaVyHPIfC0htReHMJb1tMrvw==", "dev": true, "requires": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", "@octokit/request": "^5.4.12", + "@octokit/request-error": "^2.0.5", "@octokit/types": "^6.0.3", - "before-after-hook": "^2.1.0", + "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "@octokit/endpoint": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.10.tgz", - "integrity": "sha512-9+Xef8nT7OKZglfkOMm7IL6VwxXUQyR7DUSU0LH/F7VNqs8vyd7es5pTfz9E7DwUIx7R3pGscxu1EBhYljyu7Q==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz", + "integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==", "dev": true, "requires": { - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "is-plain-object": "^5.0.0", "universal-user-agent": "^6.0.0" }, @@ -3164,13 +3165,13 @@ } }, "@octokit/graphql": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.8.tgz", - "integrity": "sha512-WnCtNXWOrupfPJgXe+vSmprZJUr0VIu14G58PMlkWGj3cH+KLZEfKMmbUQ6C3Wwx6fdhzVW1CD5RTnBdUHxhhA==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.1.tgz", + "integrity": "sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==", "dev": true, "requires": { "@octokit/request": "^5.3.0", - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "universal-user-agent": "^6.0.0" } }, @@ -3206,14 +3207,14 @@ } }, "@octokit/request": { - "version": "5.4.12", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.12.tgz", - "integrity": "sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz", + "integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.0.0", - "@octokit/types": "^6.0.3", + "@octokit/types": "^6.7.1", "deprecation": "^2.0.0", "is-plain-object": "^5.0.0", "node-fetch": "^2.6.1", @@ -3221,6 +3222,21 @@ "universal-user-agent": "^6.0.0" }, "dependencies": { + "@octokit/openapi-types": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-5.3.2.tgz", + "integrity": "sha512-NxF1yfYOUO92rCx3dwvA2onF30Vdlg7YUkMVXkeptqpzA3tRLplThhFleV/UKWFgh7rpKu1yYRbvNDUtzSopKA==", + "dev": true + }, + "@octokit/types": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.12.2.tgz", + "integrity": "sha512-kCkiN8scbCmSq+gwdJV0iLgHc0O/GTPY1/cffo9kECu1MvatLPh9E+qFhfRIktKfHEA6ZYvv6S1B4Wnv3bi3pA==", + "dev": true, + "requires": { + "@octokit/openapi-types": "^5.3.2" + } + }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -3230,12 +3246,12 @@ } }, "@octokit/request-error": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.4.tgz", - "integrity": "sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz", + "integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==", "dev": true, "requires": { - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } @@ -6152,9 +6168,9 @@ } }, "before-after-hook": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", - "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.0.tgz", + "integrity": "sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ==", "dev": true }, "big-integer": { @@ -11058,15 +11074,14 @@ } }, "expensify-common": { - "version": "git+https://github.com/Expensify/expensify-common.git#58ed3e9e813a9bc1cf0dd4626ce90ef35110369c", - "from": "git+https://github.com/Expensify/expensify-common.git#58ed3e9e813a9bc1cf0dd4626ce90ef35110369c", + "version": "git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84", + "from": "git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84", "requires": { "classnames": "2.2.5", "clipboard": "2.0.4", "html-entities": "^1.3.1", "jquery": "3.3.1", - "lodash.get": "4.4.2", - "lodash.has": "4.5.2", + "lodash": "4.17.21", "prop-types": "15.7.2", "react": "16.12.0", "react-dom": "16.12.0", @@ -11075,6 +11090,11 @@ "underscore": "1.9.1" }, "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "react": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", diff --git a/package.json b/package.json index f14e29afb689..70523f3d2328 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.2-46", + "version": "1.0.2-62", "author": "Expensify, Inc.", "homepage": "https://expensify.cash", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -47,7 +47,7 @@ "electron-log": "^4.2.4", "electron-serve": "^1.0.0", "electron-updater": "^4.3.4", - "expensify-common": "git+https://github.com/Expensify/expensify-common.git#58ed3e9e813a9bc1cf0dd4626ce90ef35110369c", + "expensify-common": "git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84", "file-loader": "^6.0.0", "html-entities": "^1.3.1", "lodash.get": "^4.4.2", @@ -93,6 +93,7 @@ "@babel/preset-flow": "^7.12.13", "@babel/preset-react": "^7.10.4", "@babel/runtime": "^7.11.2", + "@octokit/core": "^3.3.1", "@octokit/rest": "^18.3.5", "@react-native-community/eslint-config": "^2.0.0", "@svgr/webpack": "^5.5.0", diff --git a/src/components/FAB.js b/src/components/FAB.js index c526b66e40af..b79f070ae41e 100644 --- a/src/components/FAB.js +++ b/src/components/FAB.js @@ -55,12 +55,12 @@ class FAB extends PureComponent { const backgroundColor = this.animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [themeColors.buttonSuccessBG, themeColors.sidebar], + outputRange: [themeColors.buttonSuccessBG, themeColors.buttonDefaultBG], }); const fill = this.animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [themeColors.componentBG, themeColors.icon], + outputRange: [themeColors.componentBG, themeColors.heading], }); return ( diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index 35a0a52cd4a8..51884d6fdd6b 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -14,12 +14,22 @@ class BaseModal extends PureComponent { constructor(props) { super(props); + this.hideModalAndRemoveEventListeners = this.hideModalAndRemoveEventListeners.bind(this); this.subscribeToKeyEvents = this.subscribeToKeyEvents.bind(this); this.unsubscribeFromKeyEvents = this.unsubscribeFromKeyEvents.bind(this); } componentWillUnmount() { + this.hideModalAndRemoveEventListeners(); + } + + /** + * Hides modal and unsubscribes from key event listeners + */ + hideModalAndRemoveEventListeners() { this.unsubscribeFromKeyEvents(); + setModalVisibility(false); + this.props.onModalHide(); } /** @@ -65,11 +75,7 @@ class BaseModal extends PureComponent { this.subscribeToKeyEvents(); setModalVisibility(true); }} - onModalHide={() => { - this.unsubscribeFromKeyEvents(); - setModalVisibility(false); - this.props.onModalHide(); - }} + onModalHide={this.hideModalAndRemoveEventListeners} onSwipeComplete={this.props.onClose} swipeDirection={swipeDirection} isVisible={this.props.isVisible} diff --git a/src/libs/API.js b/src/libs/API.js index f26a81e9ba09..852dee8497d8 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -31,7 +31,6 @@ function isAuthTokenRequired(command) { 'Graphite_Timer', 'Authenticate', 'GetAccountStatus', - 'SetGithubUsername', 'SetPassword', 'User_SignUp', 'ResendValidateCode', @@ -540,17 +539,6 @@ function ResendValidateCode(parameters) { return Network.post(commandName, parameters); } -/** - * @param {Object} parameters - * @param {String} parameters.githubUsername - * @returns {Promise} - */ -function SetGithubUsername(parameters) { - const commandName = 'SetGithubUsername'; - requireParameters(['email', 'githubUsername'], parameters, commandName); - return Network.post(commandName, parameters); -} - /** * @param {Object} parameters * @param {String} parameters.password @@ -654,7 +642,6 @@ export { Report_TogglePinned, Report_UpdateLastRead, ResendValidateCode, - SetGithubUsername, SetNameValuePair, SetPassword, UpdateAccount, diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js index 98bcd8617148..78134543662c 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.js @@ -322,6 +322,13 @@ function isSubscribed(channelName) { * @param {Object} payload */ function sendEvent(channelName, eventName, payload) { + // Check to see if we are subscribed to this channel before sending the event. Sending client events over channels + // we are not subscribed too will throw errors and cause reconnection attempts. Subscriptions are not instant and + // can happen later than we expect. + if (!isSubscribed(channelName)) { + return; + } + socket.send_event(eventName, payload, channelName); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 3a746aef0156..47af1083b7c9 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -244,6 +244,7 @@ function removeOptimisticActions(reportID) { function updateReportWithNewAction(reportID, reportAction) { const newMaxSequenceNumber = reportAction.sequenceNumber; const isFromCurrentUser = reportAction.actorAccountID === currentUserAccountID; + const lastReadSequenceNumber = lastReadSequenceNumbers[reportID] || 0; // When handling an action from the current users we can assume that their // last read actionID has been updated in the server but not necessarily reflected @@ -257,14 +258,21 @@ function updateReportWithNewAction(reportID, reportAction) { // Always merge the reportID into Onyx // If the report doesn't exist in Onyx yet, then all the rest of the data will be filled out // by handleReportChanged - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + const updatedReportObject = { reportID, - unreadActionCount: newMaxSequenceNumber - (lastReadSequenceNumbers[reportID] || 0), + unreadActionCount: newMaxSequenceNumber - lastReadSequenceNumber, maxSequenceNumber: reportAction.sequenceNumber, - lastMessageTimestamp: reportAction.timestamp, - lastMessageText: messageText, - lastActorEmail: reportAction.actorEmail, - }); + }; + + // If the report action from pusher is a higher sequence number than we know about (meaning it has come from + // a chat participant in another application), then the last message text and author needs to be updated as well + if (newMaxSequenceNumber > lastReadSequenceNumber) { + updatedReportObject.lastMessageTimestamp = reportAction.timestamp; + updatedReportObject.lastMessageText = messageText; + updatedReportObject.lastActorEmail = reportAction.actorEmail; + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, updatedReportObject); const reportActionsToMerge = {}; if (reportAction.clientID) { @@ -576,17 +584,24 @@ function fetchAll(shouldRedirectToReport = true, shouldRecordHomePageTiming = fa function addAction(reportID, text, file) { // Convert the comment from MD into HTML because that's how it is stored in the database const parser = new ExpensiMark(); - const htmlComment = parser.replace(text); + const commentText = parser.replace(text); const isAttachment = _.isEmpty(text) && file !== undefined; // The new sequence number will be one higher than the highest const highestSequenceNumber = reportMaxSequenceNumbers[reportID] || 0; const newSequenceNumber = highestSequenceNumber + 1; + const htmlForNewComment = isAttachment ? 'Uploading Attachment...' : commentText; + + // Remove HTML from text when applying optimistic offline comment + const textForNewComment = isAttachment ? '[Attachment]' + : htmlForNewComment.replace(/<[^>]*>?/gm, ''); // Update the report in Onyx to have the new sequence number Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { maxSequenceNumber: newSequenceNumber, lastMessageTimestamp: moment().unix(), + lastMessageText: textForNewComment, + lastActorEmail: currentUserEmail, }); // Generate a clientID so we can save the optimistic action to storage with the clientID as key. Later, we will @@ -629,11 +644,8 @@ function addAction(reportID, text, file) { message: [ { type: 'COMMENT', - html: isAttachment ? 'Uploading Attachment...' : htmlComment, - - // Remove HTML from text when applying optimistic offline comment - text: isAttachment ? '[Attachment]' - : htmlComment.replace(/<[^>]*>?/gm, ''), + html: htmlForNewComment, + text: textForNewComment, }, ], isFirstItem: false, @@ -645,7 +657,7 @@ function addAction(reportID, text, file) { API.Report_AddComment({ reportID, - reportComment: htmlComment, + reportComment: htmlForNewComment, file, clientID: optimisticReportActionID, diff --git a/src/libs/actions/Session.js b/src/libs/actions/Session.js index 58635755c393..a00ae68d0eda 100644 --- a/src/libs/actions/Session.js +++ b/src/libs/actions/Session.js @@ -86,7 +86,6 @@ function fetchAccountDetails(login) { }); Onyx.merge(ONYXKEYS.ACCOUNT, { accountExists: response.accountExists, - canAccessExpensifyCash: response.canAccessExpensifyCash, requiresTwoFactorAuth: response.requiresTwoFactorAuth, }); @@ -183,30 +182,6 @@ function signIn(password, twoFactorAuthCode) { }); } -/** - * Puts the github username into Onyx so that it can be used when creating accounts or logins - * - * @param {String} username - */ -function setGitHubUsername(username) { - Onyx.merge(ONYXKEYS.ACCOUNT, {error: '', loading: true}); - - API.SetGithubUsername({email: credentials.login, githubUsername: username}) - .then((response) => { - if (response.jsonCode === 200) { - Onyx.merge(ONYXKEYS.CREDENTIALS, {githubUsername: username}); - Onyx.merge(ONYXKEYS.ACCOUNT, {canAccessExpensifyCash: true}); - return; - } - - // This request can fail if an invalid GitHub username was entered - Onyx.merge(ONYXKEYS.ACCOUNT, {error: 'Please enter a valid GitHub username'}); - }) - .finally(() => { - Onyx.merge(ONYXKEYS.ACCOUNT, {loading: false}); - }); -} - /** * Resend the validation link to the user that is validating their account * this happens in the createAccount() flow @@ -259,7 +234,6 @@ function setPassword(password, validateCode) { export { fetchAccountDetails, - setGitHubUsername, setPassword, signIn, signOut, diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 7d1b6afb9fc9..7194df06a922 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -48,7 +48,7 @@ class ReportActionCompose extends React.Component { this.updateComment = this.updateComment.bind(this); this.debouncedSaveReportComment = _.debounce(this.debouncedSaveReportComment.bind(this), 1000, false); - this.debouncedBroadcastUserIsTyping = _.debounce(() => broadcastUserIsTyping(props.reportID), 100, true); + this.debouncedBroadcastUserIsTyping = _.debounce(this.debouncedBroadcastUserIsTyping.bind(this), 100, true); this.submitForm = this.submitForm.bind(this); this.triggerSubmitShortcut = this.triggerSubmitShortcut.bind(this); this.submitForm = this.submitForm.bind(this); @@ -106,6 +106,14 @@ class ReportActionCompose extends React.Component { saveReportComment(this.props.reportID, comment || ''); } + /** + * Broadcast that the user is typing. We debounce this method in the constructor to limit how often we publish + * client events. + */ + debouncedBroadcastUserIsTyping() { + broadcastUserIsTyping(this.props.reportID); + } + /** * Update the value of the comment in Onyx * diff --git a/src/pages/iou/IOUBillPage.js b/src/pages/iou/IOUBillPage.js index 7e1350450c48..065f00165775 100644 --- a/src/pages/iou/IOUBillPage.js +++ b/src/pages/iou/IOUBillPage.js @@ -1,7 +1,12 @@ import React from 'react'; import IOUModal from './IOUModal'; +import ScreenWrapper from '../../components/ScreenWrapper'; export default props => ( - // eslint-disable-next-line react/jsx-props-no-spreading - + + {() => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )} + ); diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 2bc55d90264a..8bfaacead947 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -108,7 +108,7 @@ class IOUModal extends Component {
diff --git a/src/pages/iou/IOURequestPage.js b/src/pages/iou/IOURequestPage.js index 57f5a52ddc99..67ee9d0f4dd4 100644 --- a/src/pages/iou/IOURequestPage.js +++ b/src/pages/iou/IOURequestPage.js @@ -1,7 +1,12 @@ import React from 'react'; import IOUModal from './IOUModal'; +import ScreenWrapper from '../../components/ScreenWrapper'; export default props => ( - // eslint-disable-next-line react/jsx-props-no-spreading - + + {() => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )} + ); diff --git a/src/pages/settings/PaymentsPage.js b/src/pages/settings/PaymentsPage.js index 258bf2c96dde..565ebb89ba79 100644 --- a/src/pages/settings/PaymentsPage.js +++ b/src/pages/settings/PaymentsPage.js @@ -1,24 +1,109 @@ import React from 'react'; -import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; -import Navigation from '../../libs/Navigation/Navigation'; +import {Pressable, TextInput, View} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import CONST from '../../CONST'; +import ONYXKEYS from '../../ONYXKEYS'; import ROUTES from '../../ROUTES'; +import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; +import Text from '../../components/Text'; import ScreenWrapper from '../../components/ScreenWrapper'; +import NameValuePair from '../../libs/actions/NameValuePair'; +import {fetch} from '../../libs/actions/User'; +import Navigation from '../../libs/Navigation/Navigation'; +import styles from '../../styles/styles'; + +const propTypes = { + payPalMeUsername: PropTypes.string, +}; + +const defaultProps = { + payPalMeUsername: '', +}; + +class PaymentsPage extends React.Component { + constructor(props) { + super(props); + + this.state = { + payPalMeUsername: props.payPalMeUsername, + }; + this.setPayPalMeUsername = this.setPayPalMeUsername.bind(this); + } + + componentDidMount() { + fetch(); + } + + componentDidUpdate(prevProps) { + if (prevProps.payPalMeUsername !== this.props.payPalMeUsername) { + // Suppressing because this is within a conditional, and hence we won't run into an infinite loop + // eslint-disable-next-line react/no-did-update-set-state + this.setState({payPalMeUsername: this.props.payPalMeUsername}); + } + } + + /** + * Sets the payPalMeUsername for the current user + */ + setPayPalMeUsername() { + NameValuePair.set(CONST.NVP.PAYPAL_ME_ADDRESS, this.state.payPalMeUsername, ONYXKEYS.NVP_PAYPAL_ME_ADDRESS); + } + + render() { + return ( + + {() => ( + <> + Navigation.navigate(ROUTES.SETTINGS)} + onCloseButtonPress={() => Navigation.dismissModal()} + /> + + + + Enter your username to get paid back via PayPal. + + + PayPal.me/ + + this.setState({payPalMeUsername: text})} + /> + + [ + styles.button, + styles.buttonSuccess, + styles.mt3, + hovered && styles.buttonSuccessHovered, + ]} + > + + Add PayPal Account + + + + -const PaymentsPage = () => ( - - {() => ( - <> - Navigation.navigate(ROUTES.SETTINGS)} - onCloseButtonPress={() => Navigation.dismissModal()} - /> - - )} - -); + )} + + ); + } +} +PaymentsPage.propTypes = propTypes; +PaymentsPage.defaultProps = defaultProps; PaymentsPage.displayName = 'PaymentsPage'; -export default PaymentsPage; +export default withOnyx({ + payPalMeUsername: { + key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, + }, +})(PaymentsPage); diff --git a/src/pages/signin/GithubUsernameForm.js b/src/pages/signin/GithubUsernameForm.js deleted file mode 100644 index fd6c625c1745..000000000000 --- a/src/pages/signin/GithubUsernameForm.js +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import { - Text, TextInput, View, -} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import PropTypes from 'prop-types'; -import styles from '../../styles/styles'; -import ButtonWithLoader from '../../components/ButtonWithLoader'; -import {setGitHubUsername} from '../../libs/actions/Session'; -import ONYXKEYS from '../../ONYXKEYS'; -import ChangeExpensifyLoginLink from './ChangeExpensifyLoginLink'; - -const propTypes = { - /* Onyx Props */ - - // The details about the account that the user is signing in with - account: PropTypes.shape({ - // Whether or not a sign on form is loading (being submitted) - loading: PropTypes.bool, - }), -}; - -const defaultProps = { - account: {}, -}; - -class GithubUsernameForm extends React.Component { - constructor(props) { - super(props); - - this.validateAndSubmitForm = this.validateAndSubmitForm.bind(this); - - this.state = { - formError: false, - githubUsername: '', - }; - } - - /** - * Check that all the form fields are valid, then trigger the submit callback - */ - validateAndSubmitForm() { - if (!this.state.githubUsername.trim()) { - this.setState({formError: 'Please enter your GitHub username'}); - return; - } - - this.setState({ - formError: null, - }); - - // Save the github username to their account - setGitHubUsername(this.state.githubUsername); - } - - render() { - return ( - <> - - - GitHub Username - this.setState({githubUsername: text})} - onSubmitEditing={this.validateAndSubmitForm} - autoCapitalize="none" - autoFocus - /> - - - - - - {this.state.formError && ( - - {this.state.formError} - - )} - - - - - You're on the waitlist! - - - Thanks for adding yourself to the waitlist. If you're a developer, just enter your - {' '} - GitHub username, and we'll grant you instant access to the dev-only beta. Otherwise, - {' '} - you're all set -- we'll let you know when to check back. - - - - ); - } -} - -GithubUsernameForm.propTypes = propTypes; -GithubUsernameForm.defaultProps = defaultProps; - -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, -})(GithubUsernameForm); diff --git a/src/pages/signin/LoginForm/LoginFormNarrow.js b/src/pages/signin/LoginForm/LoginFormNarrow.js index ba6c78b1b012..08ecb0aee47d 100644 --- a/src/pages/signin/LoginForm/LoginFormNarrow.js +++ b/src/pages/signin/LoginForm/LoginFormNarrow.js @@ -8,10 +8,8 @@ import _ from 'underscore'; import styles from '../../../styles/styles'; import themeColors from '../../../styles/themes/default'; import ButtonWithLoader from '../../../components/ButtonWithLoader'; -import openURLInNewTab from '../../../libs/openURLInNewTab'; import {fetchAccountDetails} from '../../../libs/actions/Session'; import welcomeScreenshot from '../../../../assets/images/welcome-screenshot.png'; -import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { @@ -64,7 +62,7 @@ class LoginFormNarrow extends React.Component { return ( - Sign up for the waitlist + Enter your phone or email: this.setState({login: text})} onSubmitEditing={this.validateAndSubmitForm} autoCapitalize="none" - placeholder="Email or phone" + placeholder="Phone or Email" placeholderTextColor={themeColors.textSupporting} /> @@ -105,31 +103,14 @@ class LoginFormNarrow extends React.Component { /> - - - With Expensify.cash, chat and payments are the same thing. Launching Summer 2021, - {' '} - join the waitlist to be first in line! - - - - Attention Open Source Developers: + With Expensify.cash, chat and payments are the same thing. - Enter your GitHub handle on the next page to skip the wait and join our dev-only beta; - {' '} - help build tomorrow and - {' '} - openURLInNewTab(CONST.UPWORK_URL)} - > - earn cash - + Money talks. And now that chat and payments are in one place, it's also easy. {' '} - today! + Your payments get to you as fast as you can get your point across. diff --git a/src/pages/signin/LoginForm/LoginFormWide.js b/src/pages/signin/LoginForm/LoginFormWide.js index 1672aad35132..070c77c5c1b9 100644 --- a/src/pages/signin/LoginForm/LoginFormWide.js +++ b/src/pages/signin/LoginForm/LoginFormWide.js @@ -6,8 +6,6 @@ import _ from 'underscore'; import {fetchAccountDetails} from '../../../libs/actions/Session'; import styles from '../../../styles/styles'; import ButtonWithLoader from '../../../components/ButtonWithLoader'; -import openURLInNewTab from '../../../libs/openURLInNewTab'; -import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; const propTypes = { @@ -61,7 +59,7 @@ class LoginFormWide extends React.Component { <> - Sign up for the waitlist + Enter your phone or email: this.setState({login: text})} onSubmitEditing={this.validateAndSubmitForm} autoCapitalize="none" - placeholder="Email or phone" + placeholder="Phone or Email" autoFocus /> @@ -96,31 +94,14 @@ class LoginFormWide extends React.Component { - - - With Expensify.cash, chat and payments are the same thing. Launching Summer 2021, - {' '} - join the waitlist to be first in line! - - - - Attention Open Source Developers: + With Expensify.cash, chat and payments are the same thing. - Enter your Github handle on the next page to skip the wait and join our dev-only beta; - {' '} - help build tomorrow and - {' '} - openURLInNewTab(CONST.UPWORK_URL)} - > - earn cash - + Money talks. And now that chat and payments are in one place, it's also easy. {' '} - today! + Your payments get to you as fast as you can get your point across. diff --git a/src/pages/signin/PasswordForm.js b/src/pages/signin/PasswordForm.js index 55dbdebceffc..a393ecd0b925 100644 --- a/src/pages/signin/PasswordForm.js +++ b/src/pages/signin/PasswordForm.js @@ -19,9 +19,6 @@ const propTypes = { // Whether or not the account already exists accountExists: PropTypes.bool, - // Whether or not there have been chat reports shared with this user - canAccessExpensifyCash: PropTypes.bool, - // Whether or not two factor authentication is required requiresTwoFactorAuth: PropTypes.bool, diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 6bcf13b9f2a5..7c805d6f58f4 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -10,7 +10,6 @@ import styles from '../../styles/styles'; import updateUnread from '../../libs/UnreadIndicatorUpdater/updateUnread/index'; import SignInPageLayout from './SignInPageLayout'; import LoginForm from './LoginForm'; -import GithubUsernameForm from './GithubUsernameForm'; import PasswordForm from './PasswordForm'; import ResendValidationForm from './ResendValidationForm'; @@ -22,9 +21,6 @@ const propTypes = { // Whether or not the account already exists accountExists: PropTypes.bool, - // Whether or not there have been chat reports shared with this user - canAccessExpensifyCash: PropTypes.bool, - // Error to display when there is an account error returned error: PropTypes.string, }), @@ -32,7 +28,6 @@ const propTypes = { // The credentials of the person signing in credentials: PropTypes.shape({ login: PropTypes.string, - githubUsername: PropTypes.string, password: PropTypes.string, twoFactorAuthCode: PropTypes.string, }), @@ -61,26 +56,12 @@ class SignInPage extends Component { // - A login has not been entered yet const showLoginForm = !this.props.credentials.login; - // Show the GitHub username form if - // - A login has been entered - // - AND they do not have access to this app yet - // - AND the user hasn't entered a GitHub username yet - // - AND a password hasn't been entered yet - const showGithubUsernameForm = this.props.credentials.login - && !this.props.account.canAccessExpensifyCash - && !this.props.credentials.githubUsername - && !this.props.credentials.password; - // Show the password form if // - A login has been entered // - AND a GitHub username has been entered OR they already have access to expensify cash // - AND an account exists already for this login // - AND a password hasn't been entered yet const showPasswordForm = this.props.credentials.login - && ( - this.props.credentials.githubUsername - || this.props.account.canAccessExpensifyCash - ) && this.props.account.accountExists && !this.props.credentials.password; @@ -89,10 +70,6 @@ class SignInPage extends Component { // - AND a GitHub username has been entered OR they already have access to this app // - AND an account did not exist for that login const showResendValidationLinkForm = this.props.credentials.login - && ( - this.props.credentials.githubUsername - || this.props.account.canAccessExpensifyCash - ) && !this.props.account.accountExists; return ( @@ -101,8 +78,6 @@ class SignInPage extends Component { {showLoginForm && } - {showGithubUsernameForm && } - {showPasswordForm && } {showResendValidationLinkForm && } diff --git a/tests/unit/GithubUtilsTest.js b/tests/unit/GithubUtilsTest.js index e21175d5427f..3a5a3bd409fe 100644 --- a/tests/unit/GithubUtilsTest.js +++ b/tests/unit/GithubUtilsTest.js @@ -173,6 +173,53 @@ describe('GithubUtils', () => { }); }); }); + + test('updates issue with just deploy blockers', () => { + const issueBefore = { + url: 'https://api.github.com/repos/Expensify/Expensify.cash/issues/29', + title: 'Test StagingDeployCash', + labels: [ + { + id: 2783847782, + node_id: 'MDU6TGFiZWwyNzgzODQ3Nzgy', + url: 'https://api.github.com/repos/Expensify/Expensify.cash/labels/StagingDeployCash', + name: 'StagingDeployCash', + color: '6FC269', + default: false, + description: '', + }, + ], + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/1.0.1-0...1.0.1-47\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n', + }; + + const octokit = new Octokit(); + octokit.repos.listTags = jest.fn().mockResolvedValue({ + data: [ + {name: '1.0.1-0'}, + {name: '1.0.1-47'}, + {name: '1.0.1-48'}, + ], + }); + octokit.issues.listForRepo = jest.fn().mockResolvedValue({data: [issueBefore]}); + octokit.issues.update = jest.fn().mockImplementation(arg => Promise.resolve(arg)); + const githubUtils = new GithubUtils(octokit); + + return githubUtils.updateStagingDeployCash( + undefined, + undefined, + ['https://github.com/Expensify/Expensify.cash/pull/24', + 'https://github.com/Expensify/Expensify.cash/issues/25'], + ).then((result) => { + expect(result).toStrictEqual({ + owner: GithubUtils.GITHUB_OWNER, + repo: GithubUtils.EXPENSIFY_CASH_REPO, + issue_number: 29, + // eslint-disable-next-line max-len + body: '**Release Version:** `1.0.1-47`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/1.0.1-0...1.0.1-47\r\n\r\n**This release contains changes from the following pull requests:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/21\r\n- [x] https://github.com/Expensify/Expensify.cash/pull/22\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/23\r\n\r\n**Deploy Blockers:**\r\n- [ ] https://github.com/Expensify/Expensify.cash/pull/24\r\n- [ ] https://github.com/Expensify/Expensify.cash/issues/25\r\n\r\ncc @Expensify/applauseleads\r\n', + }); + }); + }); }); describe('getPullRequestNumberFromURL', () => { diff --git a/tests/unit/isStagingDeployLockedTest.js b/tests/unit/isStagingDeployLockedTest.js new file mode 100644 index 000000000000..32f93013bd70 --- /dev/null +++ b/tests/unit/isStagingDeployLockedTest.js @@ -0,0 +1,51 @@ +const core = require('@actions/core'); +const run = require('../../.github/actions/isStagingDeployLocked/isStagingDeployLocked'); + +beforeEach(() => { + jest.resetModules(); + + // Mock GITHUB_TOKEN which is required before every test + process.env.INPUT_GITHUB_TOKEN = 'fake_token'; +}); + +afterEach(() => { + // Remove mock GITHUB_TOKEN after every test + delete process.env.INPUT_GITHUB_TOKEN; +}); + +// Our mock function that we can adjust in each test +let mockGetStagingDeployCash = jest.fn(); + +// Mock the entire GitHubUtils module +jest.mock('../../.github/libs/GithubUtils', () => jest.fn().mockImplementation(() => ({ + getStagingDeployCash: mockGetStagingDeployCash, +}))); + +describe('isStagingDeployLockedTest', () => { + describe('GitHub action run function', () => { + test('Test returning empty result', () => { + // Mock the return value of GitHubUtils.getStagingDeployCash() to return an empty object + mockGetStagingDeployCash = jest.fn().mockResolvedValue({}); + const setOutputMock = jest.spyOn(core, 'setOutput'); + const isStagingDeployLocked = run(); + return isStagingDeployLocked.then(() => { + expect(setOutputMock).toHaveBeenCalledWith('IS_LOCKED', false); + }); + }); + + test('Test returning valid locked issue', () => { + // Mocking the minimum amount of data required for a found issue with the correct label + const mockData = { + labels: [{name: '🔐 LockCashDeploys 🔐'}], + }; + + // Mock the return value of GitHubUtils.getStagingDeployCash() to return the correct label + mockGetStagingDeployCash = jest.fn().mockResolvedValue(mockData); + const setOutputMock = jest.spyOn(core, 'setOutput'); + const isStagingDeployLocked = run(); + return isStagingDeployLocked.then(() => { + expect(setOutputMock).toHaveBeenCalledWith('IS_LOCKED', true); + }); + }); + }); +}); diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js index 10563e916aa3..7c1ecf410b7d 100644 --- a/tests/utils/TestHelper.js +++ b/tests/utils/TestHelper.js @@ -19,7 +19,6 @@ function signInWithTestUser(accountID, login, password = 'Password1', authToken HttpUtils.xhr.mockImplementation(() => Promise.resolve({ jsonCode: 200, accountExists: true, - canAccessExpensifyCash: true, requiresTwoFactorAuth: false, normalizedLogin: login, }));