From 27394b2eb75aeeb4dca3a9075ab7da2965715e84 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 24 Jul 2019 04:11:55 -0300 Subject: [PATCH 01/60] Segregate Apps orchestrator and routes --- app/apps/client/admin/apps.js | 7 +- app/apps/client/index.js | 1 + app/apps/client/orchestrator.js | 187 +++++++++++--------------------- app/apps/client/routes.js | 50 +++++++++ package-lock.json | 180 +++++++++++++++--------------- 5 files changed, 205 insertions(+), 220 deletions(-) create mode 100644 app/apps/client/routes.js diff --git a/app/apps/client/admin/apps.js b/app/apps/client/admin/apps.js index 683df77ef6a6..f74d137cc0af 100644 --- a/app/apps/client/admin/apps.js +++ b/app/apps/client/admin/apps.js @@ -5,7 +5,7 @@ import { Template } from 'meteor/templating'; import { Tracker } from 'meteor/tracker'; import { settings } from '../../../settings'; -import { t, APIClient } from '../../../utils'; +import { t } from '../../../utils'; import { AppEvents } from '../communication'; import { Apps } from '../orchestrator'; import { SideNav } from '../../../ui-utils/client'; @@ -23,8 +23,7 @@ const sortByColumn = (array, column, inverted) => const getInstalledApps = async (instance) => { try { - const data = await APIClient.get('apps'); - const apps = data.apps.map((app) => ({ latest: app })); + const apps = (await Apps.getApps()).map((app) => ({ latest: app })); instance.apps.set(apps); } catch (e) { @@ -51,7 +50,7 @@ Template.apps.onCreated(function() { getInstalledApps(instance); try { - APIClient.get('apps?categories=true').then((data) => instance.categories.set(data)); + Apps.getCategories().then((categories) => instance.categories.set(categories)); } catch (e) { toastr.error((e.xhr.responseJSON && e.xhr.responseJSON.error) || e.message); } diff --git a/app/apps/client/index.js b/app/apps/client/index.js index 652c46ff95ee..eb500b0a58af 100644 --- a/app/apps/client/index.js +++ b/app/apps/client/index.js @@ -10,5 +10,6 @@ import './admin/appLogs'; import './admin/appManage'; import './admin/appWhatIsIt.html'; import './admin/appWhatIsIt'; +import './routes'; export { Apps } from './orchestrator'; diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index fb4de6700997..e172dccc6eab 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -1,7 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { BlazeLayout } from 'meteor/kadira:blaze-layout'; import { TAPi18next } from 'meteor/tap:i18n'; +import toastr from 'toastr'; import { AppWebsocketReceiver } from './communication'; import { Utilities } from '../lib/misc/Utilities'; @@ -10,85 +9,64 @@ import { AdminBox } from '../../ui-utils'; import { CachedCollectionManager } from '../../ui-cached-collection'; import { hasAtLeastOnePermission } from '../../authorization'; -export let Apps; +const createDeferredValue = () => { + let resolve; + let reject; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + return [promise, resolve, reject]; +}; class AppClientOrchestrator { constructor() { - this._isLoaded = false; - this._isEnabled = false; - this._loadingResolve; - this._refreshLoading(); - } - - isLoaded() { - return this._isLoaded; - } - - isEnabled() { - return this._isEnabled; - } - - getLoadingPromise() { - if (this._isLoaded) { - return Promise.resolve(this._isEnabled); - } - - return this._loadingPromise; + this.isLoaded = false; + [this.deferredIsEnabled, this.setEnabled] = createDeferredValue(); } - load(isEnabled) { - this._isEnabled = isEnabled; - - // It was already loaded, so let's load it again - if (this._isLoaded) { - this._refreshLoading(); - } else { + load = (isEnabled) => { + if (!this.isLoaded) { this.ws = new AppWebsocketReceiver(this); - this._addAdminMenuOption(); + this.registerAdminMenuItems(); + this.isLoaded = true; } - Meteor.defer(() => { - this._loadLanguages().then(() => { - this._loadingResolve(this._isEnabled); - this._isLoaded = true; - }); - }); - } + this.setEnabled(isEnabled); - getWsListener() { - return this.ws; - } + // Since the deferred value (a promise) is immutable after resolved, + // it need to be recreated to resolve a new value + [this.deferredIsEnabled, this.setEnabled] = createDeferredValue(); - _refreshLoading() { - this._loadingPromise = new Promise((resolve) => { - this._loadingResolve = resolve; + Meteor.defer(async () => { + await this._loadLanguages(); + this.setEnabled(isEnabled); }); } - _addAdminMenuOption() { + getWsListener = () => this.ws + + // TODO: move this method to somewhere else + registerAdminMenuItems = () => { AdminBox.addOption({ icon: 'cube', href: 'apps', i18nLabel: 'Apps', - permissionGranted() { - return hasAtLeastOnePermission(['manage-apps']); - }, + permissionGranted: () => hasAtLeastOnePermission(['manage-apps']), }); AdminBox.addOption({ icon: 'cube', href: 'marketplace', i18nLabel: 'Marketplace', - permissionGranted() { - return hasAtLeastOnePermission(['manage-apps']); - }, + permissionGranted: () => hasAtLeastOnePermission(['manage-apps']), }); } - _loadLanguages() { - return APIClient.get('apps/languages').then((info) => { - info.apps.forEach((rlInfo) => this.parseAndLoadLanguages(rlInfo.languages, rlInfo.id)); - }); + async _loadLanguages() { + const apps = await this.getAppsLanguages(); + apps.forEach(({ id, languages }) => this.parseAndLoadLanguages(languages, id)); } parseAndLoadLanguages(languages, id) { @@ -106,84 +84,41 @@ class AppClientOrchestrator { }); } - async getAppApis(appId) { - const result = await APIClient.get(`apps/${ appId }/apis`); - return result.apis; + isEnabled = () => this.deferredIsEnabled + + getApps = async () => { + const { apps } = await APIClient.get('apps'); + return apps; + } + + getAppApis = async (appId) => { + const { apis } = await APIClient.get(`apps/${ appId }/apis`); + return apis; + } + + getCategories = async () => { + const categories = await APIClient.get('apps?categories=true'); + return categories; + } + + getAppsLanguages = async () => { + const { apps } = await APIClient.get('apps/languages'); + return apps; } } -Meteor.startup(function _rlClientOrch() { - Apps = new AppClientOrchestrator(); +export const Apps = new AppClientOrchestrator(); +Meteor.startup(() => { CachedCollectionManager.onLogin(() => { Meteor.call('apps/is-enabled', (error, isEnabled) => { + if (error) { + console.error(error); + toastr.error(error.message); + return; + } + Apps.load(isEnabled); }); }); }); - -const appsRouteAction = function _theRealAction(whichCenter) { - Meteor.defer(() => Apps.getLoadingPromise().then((isEnabled) => { - if (isEnabled) { - BlazeLayout.render('main', { center: whichCenter, old: true }); // TODO remove old - } else { - FlowRouter.go('app-what-is-it'); - } - })); -}; - -// Bah, this has to be done *before* `Meteor.startup` -FlowRouter.route('/admin/marketplace', { - name: 'marketplace', - action() { - appsRouteAction('marketplace'); - }, -}); - -FlowRouter.route('/admin/marketplace/:itemId', { - name: 'app-manage', - action() { - appsRouteAction('appManage'); - }, -}); - -FlowRouter.route('/admin/apps', { - name: 'apps', - action() { - appsRouteAction('apps'); - }, -}); - -FlowRouter.route('/admin/app/install', { - name: 'app-install', - action() { - appsRouteAction('appInstall'); - }, -}); - -FlowRouter.route('/admin/apps/:appId', { - name: 'app-manage', - action() { - appsRouteAction('appManage'); - }, -}); - -FlowRouter.route('/admin/apps/:appId/logs', { - name: 'app-logs', - action() { - appsRouteAction('appLogs'); - }, -}); - -FlowRouter.route('/admin/app/what-is-it', { - name: 'app-what-is-it', - action() { - Meteor.defer(() => Apps.getLoadingPromise().then((isEnabled) => { - if (isEnabled) { - FlowRouter.go('apps'); - } else { - BlazeLayout.render('main', { center: 'appWhatIsIt' }); - } - })); - }, -}); diff --git a/app/apps/client/routes.js b/app/apps/client/routes.js new file mode 100644 index 000000000000..09c42da4b83f --- /dev/null +++ b/app/apps/client/routes.js @@ -0,0 +1,50 @@ +import { FlowRouter } from 'meteor/kadira:flow-router'; +import { BlazeLayout } from 'meteor/kadira:blaze-layout'; + +import { Apps } from './orchestrator'; + +FlowRouter.route('/admin/apps/what-is-it', { + name: 'apps-what-is-it', + action: async () => { + // TODO: render loading indicator + if (await Apps.isEnabled()) { + FlowRouter.go('apps'); + } else { + BlazeLayout.render('main', { center: 'appWhatIsIt' }); + } + }, +}); + +const createAppsRouteAction = (centerTemplate) => async () => { + // TODO: render loading indicator + if (await Apps.isEnabled()) { + BlazeLayout.render('main', { center: centerTemplate, old: true }); // TODO remove old + } else { + FlowRouter.go('apps-what-is-it'); + } +}; + +FlowRouter.route('/admin/marketplace', { + name: 'marketplace', + action: createAppsRouteAction('marketplace'), +}); + +FlowRouter.route('/admin/apps', { + name: 'apps', + action: createAppsRouteAction('apps'), +}); + +FlowRouter.route('/admin/apps/install', { + name: 'app-install', + action: createAppsRouteAction('appInstall'), +}); + +FlowRouter.route('/admin/apps/:appId', { + name: 'app-manage', + action: createAppsRouteAction('appManage'), +}); + +FlowRouter.route('/admin/apps/:appId/logs', { + name: 'app-logs', + action: createAppsRouteAction('appLogs'), +}); diff --git a/package-lock.json b/package-lock.json index db79e61460ba..826b3743c6dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -758,7 +758,7 @@ }, "@types/events": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" }, "@types/express": { @@ -1684,7 +1684,7 @@ }, "axios": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "requires": { "follow-redirects": "^1.3.0", @@ -2022,7 +2022,7 @@ }, "babel-plugin-add-module-exports": { "version": "0.2.1", - "resolved": "http://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", "dev": true }, @@ -2043,79 +2043,79 @@ }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", "dev": true }, "babel-plugin-syntax-async-generators": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", "dev": true }, "babel-plugin-syntax-class-constructor-call": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", "dev": true }, "babel-plugin-syntax-class-properties": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", "dev": true }, "babel-plugin-syntax-decorators": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", "dev": true }, "babel-plugin-syntax-do-expressions": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz", "integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=", "dev": true }, "babel-plugin-syntax-dynamic-import": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", "dev": true }, "babel-plugin-syntax-exponentiation-operator": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", "dev": true }, "babel-plugin-syntax-export-extensions": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", "dev": true }, "babel-plugin-syntax-flow": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", "dev": true }, "babel-plugin-syntax-function-bind": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz", "integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=", "dev": true }, "babel-plugin-syntax-jsx": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", "dev": true }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, @@ -2512,7 +2512,7 @@ }, "babel-preset-es2015": { "version": "6.3.13", - "resolved": "http://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.3.13.tgz", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.3.13.tgz", "integrity": "sha1-l9zn7ykuGMubK3VF2AxZPCjZUX8=", "dev": true, "requires": { @@ -2540,7 +2540,7 @@ }, "babel-preset-react": { "version": "6.3.13", - "resolved": "http://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.3.13.tgz", + "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.3.13.tgz", "integrity": "sha1-E9VeBqZfqqoHw5v2Op2DbgMhFvo=", "dev": true, "requires": { @@ -2554,7 +2554,7 @@ }, "babel-preset-stage-0": { "version": "6.3.13", - "resolved": "http://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.3.13.tgz", + "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.3.13.tgz", "integrity": "sha1-eKN8VvCzmI8qeZMtywzrj/N3sNE=", "dev": true, "requires": { @@ -3461,7 +3461,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", @@ -3845,7 +3845,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -4201,7 +4201,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", @@ -4304,7 +4304,7 @@ }, "chimp": { "version": "0.51.1", - "resolved": "http://registry.npmjs.org/chimp/-/chimp-0.51.1.tgz", + "resolved": "https://registry.npmjs.org/chimp/-/chimp-0.51.1.tgz", "integrity": "sha1-6hIbzfJsidV/jvNBlUDPPCeaPMU=", "dev": true, "requires": { @@ -4350,7 +4350,7 @@ "dependencies": { "async": { "version": "0.9.2", - "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", "dev": true }, @@ -4422,7 +4422,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -4474,7 +4474,7 @@ }, "progress": { "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, @@ -4497,7 +4497,7 @@ }, "chokidar": { "version": "1.6.1", - "resolved": "http://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz", "integrity": "sha1-L0RHq16W5Q+z14n9kNTHLg5McMI=", "dev": true, "requires": { @@ -4749,7 +4749,7 @@ }, "colors": { "version": "1.1.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, @@ -5221,7 +5221,7 @@ "dependencies": { "core-js": { "version": "1.2.7", - "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" } } @@ -5615,7 +5615,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -5645,7 +5645,7 @@ }, "deprecate": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz", "integrity": "sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg=" }, "deprecated-decorator": { @@ -5697,7 +5697,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -5764,7 +5764,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } @@ -6092,7 +6092,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { "es6-promise": "^4.0.3" @@ -6504,7 +6504,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "evp_bytestokey": { @@ -6905,7 +6905,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { @@ -7048,13 +7048,13 @@ "dependencies": { "lodash": { "version": "2.4.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", "dev": true }, "underscore.string": { "version": "2.3.3", - "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", "dev": true } @@ -7988,7 +7988,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-value": { @@ -8211,7 +8211,7 @@ "dependencies": { "minimist": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", "dev": true } @@ -9066,7 +9066,7 @@ }, "hapi": { "version": "8.8.0", - "resolved": "http://registry.npmjs.org/hapi/-/hapi-8.8.0.tgz", + "resolved": "https://registry.npmjs.org/hapi/-/hapi-8.8.0.tgz", "integrity": "sha1-h+N6Bum0meiXkOLcERqpZotuYX8=", "dev": true, "requires": { @@ -9136,7 +9136,7 @@ }, "catbox": { "version": "4.3.0", - "resolved": "http://registry.npmjs.org/catbox/-/catbox-4.3.0.tgz", + "resolved": "https://registry.npmjs.org/catbox/-/catbox-4.3.0.tgz", "integrity": "sha1-IiN3vWfxKRrA4l0AAC0GWp3385o=", "dev": true, "requires": { @@ -9233,7 +9233,7 @@ }, "joi": { "version": "6.4.1", - "resolved": "http://registry.npmjs.org/joi/-/joi-6.4.1.tgz", + "resolved": "https://registry.npmjs.org/joi/-/joi-6.4.1.tgz", "integrity": "sha1-9Q9CRTVgBo5jg9oVrC0w3Xzra24=", "dev": true, "requires": { @@ -9245,7 +9245,7 @@ "dependencies": { "isemail": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz", "integrity": "sha1-4Mj23D9HCX53dzlcaJYnGqJWw7U=", "dev": true }, @@ -9278,7 +9278,7 @@ "dependencies": { "mime-db": { "version": "1.14.0", - "resolved": "http://registry.npmjs.org/mime-db/-/mime-db-1.14.0.tgz", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.14.0.tgz", "integrity": "sha1-1WHxC27mbbUflK5leilRp0IX7YM=", "dev": true } @@ -9878,7 +9878,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -10214,7 +10214,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -10375,7 +10375,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-object": { @@ -10530,7 +10530,7 @@ }, "isemail": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=" }, "isexe": { @@ -10566,7 +10566,7 @@ }, "jasmine-core": { "version": "2.99.1", - "resolved": "http://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", "dev": true }, @@ -10766,7 +10766,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -11054,7 +11054,7 @@ }, "promise": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/promise/-/promise-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", "integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=", "optional": true, "requires": { @@ -11680,7 +11680,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { @@ -11984,7 +11984,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -12006,7 +12006,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -12162,7 +12162,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minimist-options": { @@ -12250,7 +12250,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -12608,7 +12608,7 @@ }, "ncp": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, @@ -12916,7 +12916,7 @@ }, "npm-install-package": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=", "dev": true }, @@ -13161,7 +13161,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { "lcid": "^1.0.0" @@ -13494,7 +13494,7 @@ }, "es6-promise": { "version": "4.0.5", - "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", "integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=", "dev": true }, @@ -13550,7 +13550,7 @@ }, "progress": { "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, @@ -13598,7 +13598,7 @@ }, "tough-cookie": { "version": "2.3.4", - "resolved": "http://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { @@ -14701,7 +14701,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -14717,7 +14717,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -14814,7 +14814,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -14926,7 +14926,7 @@ }, "regjsgen": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "dev": true }, @@ -15112,7 +15112,7 @@ }, "requestretry": { "version": "1.5.0", - "resolved": "http://registry.npmjs.org/requestretry/-/requestretry-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.5.0.tgz", "integrity": "sha1-7RV7ulNSbt6z7DKo5wSkmYvs5ic=", "dev": true, "requires": { @@ -15238,7 +15238,7 @@ }, "rimraf": { "version": "2.4.5", - "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "requires": { "glob": "^6.0.1" @@ -15333,7 +15333,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" @@ -15355,7 +15355,7 @@ }, "sax": { "version": "1.2.1", - "resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, "schema-inspector": { @@ -15368,7 +15368,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" } } @@ -15453,7 +15453,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -16176,7 +16176,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -16743,7 +16743,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -17439,7 +17439,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" } } @@ -18048,7 +18048,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", @@ -18152,7 +18152,7 @@ }, "xolvio-ddp": { "version": "0.12.3", - "resolved": "http://registry.npmjs.org/xolvio-ddp/-/xolvio-ddp-0.12.3.tgz", + "resolved": "https://registry.npmjs.org/xolvio-ddp/-/xolvio-ddp-0.12.3.tgz", "integrity": "sha1-NqarlhKyQLWg0cCoNJCK8XwLjwI=", "dev": true, "requires": { @@ -18177,7 +18177,7 @@ }, "async": { "version": "0.9.2", - "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", "dev": true }, @@ -18189,7 +18189,7 @@ }, "bl": { "version": "0.9.5", - "resolved": "http://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", "dev": true, "requires": { @@ -18198,7 +18198,7 @@ }, "bluebird": { "version": "2.11.0", - "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", "dev": true }, @@ -18210,7 +18210,7 @@ }, "combined-stream": { "version": "0.0.7", - "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", "dev": true, "requires": { @@ -18231,7 +18231,7 @@ }, "form-data": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", "integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=", "dev": true, "requires": { @@ -18271,13 +18271,13 @@ }, "mime-db": { "version": "1.12.0", - "resolved": "http://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=", "dev": true }, "mime-types": { "version": "2.0.14", - "resolved": "http://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", "dev": true, "requires": { @@ -18304,7 +18304,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -18316,7 +18316,7 @@ }, "request": { "version": "2.53.0", - "resolved": "http://registry.npmjs.org/request/-/request-2.53.0.tgz", + "resolved": "https://registry.npmjs.org/request/-/request-2.53.0.tgz", "integrity": "sha1-GAo66St7Y5gC5PlUXdj83rcddgw=", "dev": true, "requires": { @@ -18355,7 +18355,7 @@ }, "xolvio-fiber-utils": { "version": "2.0.3", - "resolved": "http://registry.npmjs.org/xolvio-fiber-utils/-/xolvio-fiber-utils-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/xolvio-fiber-utils/-/xolvio-fiber-utils-2.0.3.tgz", "integrity": "sha1-vsjXDHQGGjFjFbun0w0lyz6C3FA=", "dev": true, "requires": { @@ -18373,7 +18373,7 @@ }, "xolvio-jasmine-expect": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/xolvio-jasmine-expect/-/xolvio-jasmine-expect-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/xolvio-jasmine-expect/-/xolvio-jasmine-expect-1.1.0.tgz", "integrity": "sha1-vCud1ghCMR8EV59agtzqaisxnH0=", "dev": true, "requires": { @@ -18434,7 +18434,7 @@ }, "yargs": { "version": "3.32.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "requires": { "camelcase": "^2.0.1", From 74d244fbfa4bfa83bbbd71c9e78b830a79c843ae Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 24 Jul 2019 10:35:54 -0300 Subject: [PATCH 02/60] Properly handle errors on orchestrator --- app/apps/client/orchestrator.js | 49 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index e172dccc6eab..a34aa151018b 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -26,7 +26,7 @@ class AppClientOrchestrator { [this.deferredIsEnabled, this.setEnabled] = createDeferredValue(); } - load = (isEnabled) => { + load = async (isEnabled) => { if (!this.isLoaded) { this.ws = new AppWebsocketReceiver(this); this.registerAdminMenuItems(); @@ -39,15 +39,12 @@ class AppClientOrchestrator { // it need to be recreated to resolve a new value [this.deferredIsEnabled, this.setEnabled] = createDeferredValue(); - Meteor.defer(async () => { - await this._loadLanguages(); - this.setEnabled(isEnabled); - }); + await this.loadLanguagesResourceBundles(); + this.setEnabled(isEnabled); } getWsListener = () => this.ws - // TODO: move this method to somewhere else registerAdminMenuItems = () => { AdminBox.addOption({ icon: 'cube', @@ -64,23 +61,30 @@ class AppClientOrchestrator { }); } - async _loadLanguages() { - const apps = await this.getAppsLanguages(); - apps.forEach(({ id, languages }) => this.parseAndLoadLanguages(languages, id)); + handleError = (error) => { + console.error(error); + if (hasAtLeastOnePermission(['manage-apps'])) { + toastr.error(error.message); + } } - parseAndLoadLanguages(languages, id) { - Object.entries(languages).forEach(([language, translations]) => { - try { - translations = Object.entries(translations).reduce((newTranslations, [key, value]) => { - newTranslations[Utilities.getI18nKeyForApp(key, id)] = value; - return newTranslations; - }, {}); - - TAPi18next.addResourceBundle(language, 'project', translations); - } catch (e) { - // Failed to parse the json - } + loadLanguagesResourceBundles = async () => { + const apps = await this.getAppsLanguages(); + apps.forEach(({ id, languages }) => { + Object.entries(languages).forEach(([language, translations]) => { + try { + // Translations keys must be scoped under app id + const scopedTranslations = Object.entries(translations) + .reduce((translations, [key, value]) => { + translations[Utilities.getI18nKeyForApp(key, id)] = value; + return translations; + }, {}); + + TAPi18next.addResourceBundle(language, 'project', scopedTranslations); + } catch (error) { + this.handleError(error); + } + }); }); } @@ -113,8 +117,7 @@ Meteor.startup(() => { CachedCollectionManager.onLogin(() => { Meteor.call('apps/is-enabled', (error, isEnabled) => { if (error) { - console.error(error); - toastr.error(error.message); + Apps.handleError(error); return; } From 4ec6b209459f5e897490d19c908b253fad9a0d5f Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 24 Jul 2019 12:48:51 -0300 Subject: [PATCH 03/60] Add i18n module to handle Apps translations --- app/apps/client/communication/index.js | 4 +-- app/apps/client/communication/websockets.js | 8 ++--- app/apps/client/i18n.js | 38 +++++++++++++++++++++ app/apps/client/orchestrator.js | 30 ++++------------ 4 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 app/apps/client/i18n.js diff --git a/app/apps/client/communication/index.js b/app/apps/client/communication/index.js index 8878b65fcf21..321bbb7f15b7 100644 --- a/app/apps/client/communication/index.js +++ b/app/apps/client/communication/index.js @@ -1,3 +1 @@ -import { AppWebsocketReceiver, AppEvents } from './websockets'; - -export { AppWebsocketReceiver, AppEvents }; +export { AppWebsocketReceiver, AppEvents } from './websockets'; diff --git a/app/apps/client/communication/websockets.js b/app/apps/client/communication/websockets.js index 1da0efecccf2..3d53745976a3 100644 --- a/app/apps/client/communication/websockets.js +++ b/app/apps/client/communication/websockets.js @@ -26,8 +26,8 @@ export class AppWebsocketReceiver { this.listeners = {}; - Object.keys(AppEvents).forEach((v) => { - this.listeners[AppEvents[v]] = []; + Object.values(AppEvents).forEach((v) => { + this.listeners[v] = []; }); } @@ -52,10 +52,6 @@ export class AppWebsocketReceiver { } onAppAdded(appId) { - APIClient.get(`apps/${ appId }/languages`).then((result) => { - this.orch.parseAndLoadLanguages(result.languages, appId); - }); - this.listeners[AppEvents.APP_ADDED].forEach((listener) => listener(appId)); } diff --git a/app/apps/client/i18n.js b/app/apps/client/i18n.js new file mode 100644 index 000000000000..3a33d17f111b --- /dev/null +++ b/app/apps/client/i18n.js @@ -0,0 +1,38 @@ +import { TAPi18next } from 'meteor/tap:i18n'; + +import { Apps } from './orchestrator'; +import { Utilities } from '../lib/misc/Utilities'; +import { AppEvents } from './communication'; + + +export const loadAppI18nResources = (appId, languages) => { + Object.entries(languages).forEach(([language, translations]) => { + try { + // Translations keys must be scoped under app id + const scopedTranslations = Object.entries(translations) + .reduce((translations, [key, value]) => { + translations[Utilities.getI18nKeyForApp(key, appId)] = value; + return translations; + }, {}); + + TAPi18next.addResourceBundle(language, 'project', scopedTranslations); + } catch (error) { + Apps.handleError(error); + } + }); +}; + +const handleAppAdded = async (appId) => { + const languages = await Apps.getAppLanguages(appId); + loadAppI18nResources(appId, languages); +}; + +export const handleI18nResources = async () => { + const apps = await Apps.getAppsLanguages(); + apps.forEach(({ id, languages }) => { + loadAppI18nResources(id, languages); + }); + + Apps.getWsListener().unregisterListener(AppEvents.APP_ADDED, handleAppAdded); + Apps.getWsListener().registerListener(AppEvents.APP_ADDED, handleAppAdded); +}; diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index a34aa151018b..c88595046936 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -1,13 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { TAPi18next } from 'meteor/tap:i18n'; import toastr from 'toastr'; import { AppWebsocketReceiver } from './communication'; -import { Utilities } from '../lib/misc/Utilities'; import { APIClient } from '../../utils'; import { AdminBox } from '../../ui-utils'; import { CachedCollectionManager } from '../../ui-cached-collection'; import { hasAtLeastOnePermission } from '../../authorization'; +import { handleI18nResources } from './i18n'; const createDeferredValue = () => { let resolve; @@ -39,7 +38,7 @@ class AppClientOrchestrator { // it need to be recreated to resolve a new value [this.deferredIsEnabled, this.setEnabled] = createDeferredValue(); - await this.loadLanguagesResourceBundles(); + await handleI18nResources(); this.setEnabled(isEnabled); } @@ -68,26 +67,6 @@ class AppClientOrchestrator { } } - loadLanguagesResourceBundles = async () => { - const apps = await this.getAppsLanguages(); - apps.forEach(({ id, languages }) => { - Object.entries(languages).forEach(([language, translations]) => { - try { - // Translations keys must be scoped under app id - const scopedTranslations = Object.entries(translations) - .reduce((translations, [key, value]) => { - translations[Utilities.getI18nKeyForApp(key, id)] = value; - return translations; - }, {}); - - TAPi18next.addResourceBundle(language, 'project', scopedTranslations); - } catch (error) { - this.handleError(error); - } - }); - }); - } - isEnabled = () => this.deferredIsEnabled getApps = async () => { @@ -105,6 +84,11 @@ class AppClientOrchestrator { return categories; } + getAppLanguages = async (appId) => { + const { languages } = await APIClient.get(`apps/${ appId }/languages`); + return languages; + } + getAppsLanguages = async () => { const { apps } = await APIClient.get('apps/languages'); return apps; From a8b4b6120b1be131a88854a9462cba0a68e871fc Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 24 Jul 2019 17:12:49 -0300 Subject: [PATCH 04/60] Update apps template --- app/apps/assets/stylesheets/apps.css | 36 ++++ app/apps/client/admin/apps.html | 53 +++--- app/apps/client/admin/apps.js | 268 ++++++++++++--------------- app/apps/client/admin/helpers.js | 110 +++++++++++ app/apps/client/orchestrator.js | 16 ++ 5 files changed, 310 insertions(+), 173 deletions(-) create mode 100644 app/apps/client/admin/helpers.js diff --git a/app/apps/assets/stylesheets/apps.css b/app/apps/assets/stylesheets/apps.css index 121b1cbc9314..50db18ddbd2e 100644 --- a/app/apps/assets/stylesheets/apps.css +++ b/app/apps/assets/stylesheets/apps.css @@ -382,6 +382,42 @@ } } } + + &__app-menu-trigger { + + position: relative; + + display: flex; + flex: 0 0 auto; + + padding: 0; + + font-size: 0.875rem; + line-height: 1.25rem; + align-items: center; + appearance: none; + + &:active { + transform: translateY(2px); + + opacity: 0.9; + } + + &:active::before { + top: -2px; + } + + &::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + content: ""; + cursor: pointer; + } + } } @keyframes play90 { diff --git a/app/apps/client/admin/apps.html b/app/apps/client/admin/apps.html index 1f48fa8716c0..2941880ff0b0 100644 --- a/app/apps/client/admin/apps.html +++ b/app/apps/client/admin/apps.html @@ -2,16 +2,17 @@
{{#header sectionName="Apps" hideHelp=true fixedHeight=true fullpage=true}}
- - {{#if appsDevelopmentMode}} - {{/if}}
{{/header}} +
@@ -33,33 +34,33 @@ {{#requiresPermission 'manage-apps'}} - {{#table fixed='true' onScroll=onTableScroll onResize=onTableResize onSort=onTableSort}} + {{#table fixed='true' onScroll=handleTableScroll onResize=handleTableResize onSort=handleTableSort}} - +
{{_ "Name"}} {{> icon icon=(sortIcon 'name')}}
-
{{_ "Details"}}
+
{{_ "Details"}}
- {{#each apps}} - + {{#each app in apps}} +
- {{#if latest.iconFileData}} -
+ {{#if app.iconFileData}} +
{{else}} -
+
{{/if}}
- {{latest.name}} + {{app.name}} - {{#if latest.author.name}} - by {{latest.author.name}} + {{#if app.author.name}} + by {{app.author.name}} {{/if}}
@@ -67,31 +68,29 @@
- - {{#if latest.summary}} - {{latest.summary}} + + {{#if app.summary}} + {{app.summary}} {{else}} - {{latest.description}} + {{app.description}} {{/if}} - {{#if latest.summary}} - - {{latest.description}} - - {{/if}} - - {{#each category in latest.categories}} + {{!-- + {{#each category in app.categories}} {{category}} {{/each}} - + --}}
+
{{/each}} {{#if isLoading}} - {{> loading}} + {{> loading}} {{/if}} diff --git a/app/apps/client/admin/apps.js b/app/apps/client/admin/apps.js index f74d137cc0af..4873a1e514bb 100644 --- a/app/apps/client/admin/apps.js +++ b/app/apps/client/admin/apps.js @@ -1,205 +1,181 @@ import toastr from 'toastr'; -import { ReactiveVar } from 'meteor/reactive-var'; +import { ReactiveDict } from 'meteor/reactive-dict'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Template } from 'meteor/templating'; import { Tracker } from 'meteor/tracker'; import { settings } from '../../../settings'; -import { t } from '../../../utils'; import { AppEvents } from '../communication'; import { Apps } from '../orchestrator'; import { SideNav } from '../../../ui-utils/client'; +import { t } from '../../../utils/client'; +import { handleAPIError, triggerAppPopoverMenu } from './helpers'; -const ENABLED_STATUS = ['auto_enabled', 'manually_enabled']; -const enabled = ({ status }) => ENABLED_STATUS.includes(status); -const sortByColumn = (array, column, inverted) => - array.sort((a, b) => { - if (a.latest[column] < b.latest[column] && !inverted) { - return -1; - } - return 1; +Template.apps.onCreated(function() { + this.state = new ReactiveDict({ + apps: [], + isLoading: false, + searchText: '', + sortedColumn: 'name', + isAscendingOrder: true, + + // TODO: to use these fields + page: 0, + itemsPerPage: 0, + wasEndReached: false, }); -const getInstalledApps = async (instance) => { - try { - const apps = (await Apps.getApps()).map((app) => ({ latest: app })); + const loadApps = async () => { + try { + const apps = await Apps.getApps(); + this.state.set('apps', apps); + } catch (error) { + handleAPIError(error); + } - instance.apps.set(apps); - } catch (e) { - toastr.error((e.xhr.responseJSON && e.xhr.responseJSON.error) || e.message); - } + this.state.set('isLoading', false); + }; - instance.isLoading.set(false); - instance.ready.set(true); -}; + loadApps(); -Template.apps.onCreated(function() { - const instance = this; - this.ready = new ReactiveVar(false); - this.apps = new ReactiveVar([]); - this.categories = new ReactiveVar([]); - this.searchText = new ReactiveVar(''); - this.searchSortBy = new ReactiveVar('name'); - this.sortDirection = new ReactiveVar('asc'); - this.limit = new ReactiveVar(0); - this.page = new ReactiveVar(0); - this.end = new ReactiveVar(false); - this.isLoading = new ReactiveVar(true); - - getInstalledApps(instance); - - try { - Apps.getCategories().then((categories) => instance.categories.set(categories)); - } catch (e) { - toastr.error((e.xhr.responseJSON && e.xhr.responseJSON.error) || e.message); - } - - instance.onAppAdded = function _appOnAppAdded() { - // ToDo: fix this formatting data to add an app to installedApps array without to fetch all - - // fetch(`${ HOST }/v1/apps/${ appId }`).then((result) => { - // const installedApps = instance.installedApps.get(); - - // installedApps.push({ - // latest: result.app, - // }); - // instance.installedApps.set(installedApps); - // }); + this.handleAppAdded = async (appId) => { + try { + const app = await Apps.getApp(appId); + this.state.set('apps', [...this.state.get('apps'), app]); + } catch (error) { + handleAPIError(error); + } }; - instance.onAppRemoved = function _appOnAppRemoved(appId) { - const apps = instance.apps.get(); + this.handleAppRemoved = (appId) => { + this.state.set('apps', this.state.get('apps').filter(({ id }) => id !== appId)); + }; - let index = -1; - apps.find((item, i) => { - if (item.id === appId) { - index = i; - return true; - } - return false; - }); + this.handleAppStatusChange = ({ appId, status }) => { + const apps = this.state.get('apps'); + const app = apps.find(({ id }) => id === appId); + if (!app) { + return; + } - apps.splice(index, 1); - instance.apps.set(apps); + app.status = status; + this.state.set('apps', apps); + toastr.info(t(`App_status_${ status }`), app.name); }; - Apps.getWsListener().registerListener(AppEvents.APP_ADDED, instance.onAppAdded); - Apps.getWsListener().registerListener(AppEvents.APP_REMOVED, instance.onAppAdded); + Apps.getWsListener().registerListener(AppEvents.APP_ADDED, this.handleAppAdded); + Apps.getWsListener().registerListener(AppEvents.APP_REMOVED, this.handleAppRemoved); + Apps.getWsListener().registerListener(AppEvents.APP_STATUS_CHANGE, this.handleAppStatusChange); }); Template.apps.onDestroyed(function() { - const instance = this; + Apps.getWsListener().unregisterListener(AppEvents.APP_ADDED, this.handleAppAdded); + Apps.getWsListener().unregisterListener(AppEvents.APP_REMOVED, this.handleAppRemoved); + Apps.getWsListener().unregisterListener(AppEvents.APP_STATUS_CHANGE, this.handleAppStatusChange); +}); - Apps.getWsListener().unregisterListener(AppEvents.APP_ADDED, instance.onAppAdded); - Apps.getWsListener().unregisterListener(AppEvents.APP_REMOVED, instance.onAppAdded); +Template.apps.onRendered(() => { + Tracker.afterFlush(() => { + SideNav.setFlex('adminFlex'); + SideNav.openFlex(); + }); }); Template.apps.helpers({ - isReady() { - if (Template.instance().ready != null) { - return Template.instance().ready.get(); - } - - return false; - }, - apps() { - const instance = Template.instance(); - const searchText = instance.searchText.get().toLowerCase(); - const sortColumn = instance.searchSortBy.get(); - const inverted = instance.sortDirection.get() === 'desc'; - return sortByColumn(instance.apps.get().filter((app) => app.latest.name.toLowerCase().includes(searchText)), sortColumn, inverted); - }, - categories() { - return Template.instance().categories.get(); - }, - appsDevelopmentMode() { + isDevelopmentModeEnabled() { return settings.get('Apps_Framework_Development_Mode') === true; }, - parseStatus(status) { - return t(`App_status_${ status }`); - }, - isActive(status) { - return enabled({ status }); - }, - sortIcon(key) { - const { - sortDirection, - searchSortBy, - } = Template.instance(); - - return key === searchSortBy.get() && sortDirection.get() !== 'asc' ? 'sort-up' : 'sort-down'; - }, - searchSortBy(key) { - return Template.instance().searchSortBy.get() === key; - }, isLoading() { - return Template.instance().isLoading.get(); + return Template.instance().state.get('isLoading'); }, - onTableScroll() { - const instance = Template.instance(); - if (instance.loading || instance.end.get()) { + handleTableScroll() { + const { state } = Template.instance(); + if (state.get('isLoading') || state.get('wasEndReached')) { return; } - return function(currentTarget) { - if (currentTarget.offsetHeight + currentTarget.scrollTop >= currentTarget.scrollHeight - 100) { - return instance.page.set(instance.page.get() + 1); + + return ({ offsetHeight, scrollTop, scrollHeight }) => { + const shouldGoToNextPage = offsetHeight + scrollTop >= scrollHeight - 100; + if (shouldGoToNextPage) { + return state.set('page', state.get('page') + 1); } }; }, - onTableResize() { - const { limit } = Template.instance(); + handleTableResize() { + const { state } = Template.instance(); return function() { - limit.set(Math.ceil((this.$('.table-scroll').height() / 40) + 5)); + const $table = this.$('.table-scroll'); + state.set('itemsPerPage', Math.ceil(($table.height() / 40) + 5)); }; }, - onTableSort() { - const { end, page, sortDirection, searchSortBy } = Template.instance(); - return function(type) { - end.set(false); - page.set(0); - - if (searchSortBy.get() === type) { - sortDirection.set(sortDirection.get() === 'asc' ? 'desc' : 'asc'); + handleTableSort() { + const { state } = Template.instance(); + + return (sortedColumn) => { + state.set({ + page: 0, + wasEndReached: false, + }); + + if (state.get('sortedColumn') === sortedColumn) { + state.set('isAscendingOrder', !state.get('isAscendingOrder')); return; } - searchSortBy.set(type); - sortDirection.set('asc'); + state.set({ + sortedColumn, + isAscendingOrder: true, + }); }; }, - formatCategories(categories = []) { - return categories.join(', '); + isSortingBy(column) { + return Template.instance().state.get('sortedColumn') === column; }, -}); + sortIcon(column) { + const { state } = Template.instance(); -Template.apps.events({ - 'click .manage'() { - const rl = this; + return column === state.get('sortedColumn') && state.get('isAscendingOrder') ? 'sort-down' : 'sort-up'; + }, + apps() { + const { state } = Template.instance(); + const apps = state.get('apps'); + const searchText = state.get('searchText').toLocaleLowerCase(); + const sortedColumn = state.get('sortedColumn'); + const isAscendingOrder = state.get('isAscendingOrder'); + const sortingFactor = isAscendingOrder ? 1 : -1; - if (rl && rl.latest && rl.latest.id) { - FlowRouter.go(`/admin/apps/${ rl.latest.id }?version=${ rl.latest.version }`); - } + return apps + .filter(({ name }) => name.toLocaleLowerCase().includes(searchText)) + .sort(({ [sortedColumn]: a }, { [sortedColumn]: b }) => sortingFactor * String(a).localeCompare(String(b))); }, - 'click [data-button="install_app"]'() { +}); + +Template.apps.events({ + 'click .js-marketplace'() { FlowRouter.go('marketplace'); }, - 'click [data-button="upload_app"]'() { + 'click .js-upload'() { FlowRouter.go('app-install'); }, - 'keyup .js-search'(e, t) { - t.searchText.set(e.currentTarget.value); + 'submit .js-search-form'(event) { + event.stopPropagation(); + return false; }, - 'submit .js-search-form'(e) { - e.preventDefault(); - e.stopPropagation(); + 'input .js-search'(event, instance) { + instance.state.set('searchText', event.currentTarget.value); }, -}); + 'click .js-manage'(event) { + event.stopPropagation(); + const { id: appId, version } = event.currentTarget.dataset; + FlowRouter.go('app-manage', { appId }, { version }); + }, + 'click .js-menu'(event, instance) { + event.stopPropagation(); + const { currentTarget } = event; -Template.apps.onRendered(() => { - Tracker.afterFlush(() => { - SideNav.setFlex('adminFlex'); - SideNav.openFlex(); - }); + const app = instance.state.get('apps').find(({ id }) => id === currentTarget.dataset.id); + triggerAppPopoverMenu(app, currentTarget, instance); + }, }); diff --git a/app/apps/client/admin/helpers.js b/app/apps/client/admin/helpers.js new file mode 100644 index 000000000000..47cf2a2bf5cb --- /dev/null +++ b/app/apps/client/admin/helpers.js @@ -0,0 +1,110 @@ +import toastr from 'toastr'; + +import { modal, popover } from '../../../ui-utils/client'; +import { t } from '../../../utils/client'; +import { Apps } from '../orchestrator'; + +const promptAppDeactivation = (callback) => { + modal.open({ + text: t('Apps_Marketplace_Deactivate_App_Prompt'), + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Yes'), + cancelButtonText: t('No'), + closeOnConfirm: true, + html: false, + }, (confirmed) => { + if (!confirmed) { + return; + } + callback(); + }); +}; + +const promptAppUninstall = (callback) => { + modal.open({ + text: t('Apps_Marketplace_Uninstall_App_Prompt'), + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Yes'), + cancelButtonText: t('No'), + closeOnConfirm: true, + html: false, + }, (confirmed) => { + if (!confirmed) { + return; + } + callback(); + }); +}; + +export const handleAPIError = (error) => { + console.error(error); + const message = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; + toastr.error(message); +}; + +export const triggerAppPopoverMenu = (app, currentTarget, instance) => { + if (!app) { + return; + } + + const handleDeactivate = () => promptAppDeactivation(async () => { + try { + await Apps.disableApp(app.id); + } catch (error) { + handleAPIError(error); + } + }); + + const handleActivate = async () => { + try { + await Apps.enableApp(app.id); + } catch (error) { + handleAPIError(error); + } + }; + + const handleUninstall = () => promptAppUninstall(async () => { + try { + await Apps.uninstallApp(app.id); + } catch (error) { + handleAPIError(error); + } + }); + + const isAppEnabled = ['auto_enabled', 'manually_enabled'].includes(app.status); + + popover.open({ + currentTarget, + instance, + columns: [{ + groups: [ + { + items: [ + isAppEnabled + ? { + icon: 'ban', + name: t('Deactivate'), + modifier: 'alert', + action: handleDeactivate, + } + : { + icon: 'check', + name: t('Activate'), + action: handleActivate, + }, + { + icon: 'trash', + name: t('Uninstall'), + modifier: 'alert', + action: handleUninstall, + }, + ], + }, + ], + }], + }); +}; diff --git a/app/apps/client/orchestrator.js b/app/apps/client/orchestrator.js index c88595046936..1f55e0b2fa71 100644 --- a/app/apps/client/orchestrator.js +++ b/app/apps/client/orchestrator.js @@ -74,6 +74,11 @@ class AppClientOrchestrator { return apps; } + getApp = async (appId) => { + const { app } = await APIClient.get(`apps/${ appId }`); + return app; + } + getAppApis = async (appId) => { const { apis } = await APIClient.get(`apps/${ appId }/apis`); return apis; @@ -93,6 +98,17 @@ class AppClientOrchestrator { const { apps } = await APIClient.get('apps/languages'); return apps; } + + setAppState = async (appId, status) => { + const { status: effectiveStatus } = await APIClient.post(`apps/${ appId }/status`, { status }); + return effectiveStatus; + } + + enableApp = (appId) => this.setAppState(appId, 'manually_enabled') + + disableApp = (appId) => this.setAppState(appId, 'manually_disabled') + + uninstallApp = (appId) => APIClient.delete(`apps/${ appId }`); } export const Apps = new AppClientOrchestrator(); From 9af34c3ddb462ed2f00c896e031a62301eddbc05 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Wed, 24 Jul 2019 20:39:01 -0300 Subject: [PATCH 05/60] Update marketplace template --- app/apps/client/admin/apps.html | 2 +- app/apps/client/admin/apps.js | 22 +- app/apps/client/admin/helpers.js | 97 +++- app/apps/client/admin/marketplace.html | 66 +-- app/apps/client/admin/marketplace.js | 636 ++++++++----------------- app/apps/client/index.js | 1 - app/apps/client/orchestrator.js | 18 + 7 files changed, 363 insertions(+), 479 deletions(-) diff --git a/app/apps/client/admin/apps.html b/app/apps/client/admin/apps.html index 2941880ff0b0..424fcf410a7c 100644 --- a/app/apps/client/admin/apps.html +++ b/app/apps/client/admin/apps.html @@ -47,7 +47,7 @@ {{#each app in apps}} - +
{{#if app.iconFileData}} diff --git a/app/apps/client/admin/apps.js b/app/apps/client/admin/apps.js index 4873a1e514bb..468e62d814fb 100644 --- a/app/apps/client/admin/apps.js +++ b/app/apps/client/admin/apps.js @@ -1,8 +1,8 @@ -import toastr from 'toastr'; -import { ReactiveDict } from 'meteor/reactive-dict'; import { FlowRouter } from 'meteor/kadira:flow-router'; +import { ReactiveDict } from 'meteor/reactive-dict'; import { Template } from 'meteor/templating'; import { Tracker } from 'meteor/tracker'; +import toastr from 'toastr'; import { settings } from '../../../settings'; import { AppEvents } from '../communication'; @@ -11,11 +11,13 @@ import { SideNav } from '../../../ui-utils/client'; import { t } from '../../../utils/client'; import { handleAPIError, triggerAppPopoverMenu } from './helpers'; +import './apps.html'; + Template.apps.onCreated(function() { this.state = new ReactiveDict({ apps: [], - isLoading: false, + isLoading: true, searchText: '', sortedColumn: 'name', isAscendingOrder: true, @@ -26,7 +28,7 @@ Template.apps.onCreated(function() { wasEndReached: false, }); - const loadApps = async () => { + (async () => { try { const apps = await Apps.getApps(); this.state.set('apps', apps); @@ -35,9 +37,7 @@ Template.apps.onCreated(function() { } this.state.set('isLoading', false); - }; - - loadApps(); + })(); this.handleAppAdded = async (appId) => { try { @@ -166,9 +166,13 @@ Template.apps.events({ 'input .js-search'(event, instance) { instance.state.set('searchText', event.currentTarget.value); }, - 'click .js-manage'(event) { + 'click .js-manage'(event, instance) { event.stopPropagation(); - const { id: appId, version } = event.currentTarget.dataset; + const { currentTarget } = event; + const { + id: appId, + version, + } = instance.state.get('apps').find(({ id }) => id === currentTarget.dataset.id); FlowRouter.go('app-manage', { appId }, { version }); }, 'click .js-menu'(event, instance) { diff --git a/app/apps/client/admin/helpers.js b/app/apps/client/admin/helpers.js index 47cf2a2bf5cb..a8814da96f8b 100644 --- a/app/apps/client/admin/helpers.js +++ b/app/apps/client/admin/helpers.js @@ -1,9 +1,33 @@ +import { FlowRouter } from 'meteor/kadira:flow-router'; import toastr from 'toastr'; import { modal, popover } from '../../../ui-utils/client'; import { t } from '../../../utils/client'; import { Apps } from '../orchestrator'; + +export const handleAPIError = (error) => { + console.error(error); + const message = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; + toastr.error(message); +}; + +export const promptSubscription = async (app, callback, cancelCallback) => { + let data = null; + try { + data = await Apps.buildExternalUrl(app.id, app.purchaseType); + } catch (error) { + handleAPIError(error); + return; + } + + modal.open({ + allowOutsideClick: false, + data, + template: 'iframeModal', + }, callback, cancelCallback); +}; + const promptAppDeactivation = (callback) => { modal.open({ text: t('Apps_Marketplace_Deactivate_App_Prompt'), @@ -40,17 +64,19 @@ const promptAppUninstall = (callback) => { }); }; -export const handleAPIError = (error) => { - console.error(error); - const message = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; - toastr.error(message); -}; - export const triggerAppPopoverMenu = (app, currentTarget, instance) => { if (!app) { return; } + const handleSubscription = () => promptSubscription(app, async () => { + try { + await Apps.installApp(app.id, app.marketplaceVersion); + } catch (error) { + handleAPIError(error); + } + }); + const handleDeactivate = () => promptAppDeactivation(async () => { try { await Apps.disableApp(app.id); @@ -75,6 +101,7 @@ export const triggerAppPopoverMenu = (app, currentTarget, instance) => { } }); + const canAppBeSubscribed = app.purchaseType === 'subscription'; const isAppEnabled = ['auto_enabled', 'manually_enabled'].includes(app.status); popover.open({ @@ -82,6 +109,15 @@ export const triggerAppPopoverMenu = (app, currentTarget, instance) => { instance, columns: [{ groups: [ + ...canAppBeSubscribed ? [{ + items: [ + { + icon: 'card', + name: t('Subscription'), + action: handleSubscription, + }, + ], + }] : [], { items: [ isAppEnabled @@ -108,3 +144,52 @@ export const triggerAppPopoverMenu = (app, currentTarget, instance) => { }], }); }; + +export const promptMarketplaceLogin = () => { + modal.open({ + title: t('Apps_Marketplace_Login_Required_Title'), + text: t('Apps_Marketplace_Login_Required_Description'), + type: 'info', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Login'), + cancelButtonText: t('Cancel'), + closeOnConfirm: true, + html: false, + }, (confirmed) => { + if (confirmed) { + FlowRouter.go('cloud-config'); + } + }); +}; + +export const triggerButtonLoadingState = (button) => { + const icon = button.querySelector('.rc-icon use'); + const iconHref = icon.getAttribute('href'); + + button.classList.add('loading'); + button.disabled = true; + icon.setAttribute('href', '#icon-loading'); + + return () => { + button.classList.remove('loading'); + button.disabled = false; + icon.setAttribute('href', iconHref); + }; +}; + +export const formatPrice = (price) => `\$${ Number.parseFloat(price).toFixed(2) }`; + +export const formatPricingPlan = (pricingPlan) => { + const perUser = pricingPlan.isPerSeat && pricingPlan.tiers && pricingPlan.tiers.length; + + const pricingPlanTranslationString = [ + 'Apps_Marketplace_pricingPlan', + pricingPlan.strategy, + perUser && 'perUser', + ].filter(Boolean).join('_'); + + return t(pricingPlanTranslationString, { + price: formatPrice(pricingPlan.price), + }); +}; diff --git a/app/apps/client/admin/marketplace.html b/app/apps/client/admin/marketplace.html index c335573f1811..ebe34fa2099e 100644 --- a/app/apps/client/admin/marketplace.html +++ b/app/apps/client/admin/marketplace.html @@ -1,12 +1,13 @@