FeatureLoader.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. 'use strict';
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2019, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. *
  8. */
  9. define(['../lib/@waca/dashboard-common/dist/core/APIFactory', '../lib/@waca/core-client/js/core-client/utils/ClassFactory', './FeatureLoaderAPI', 'underscore'], function (APIFactory, ClassFactory, FeatureLoaderAPI, _) {
  10. 'use strict';
  11. /**
  12. * Create the feature dependency map that will be passed to a given feature in the constructor
  13. * We define the getter function where we check if the feature can access the feature at init time or not.
  14. *
  15. * @param {String} featureName - Feature name
  16. * @param {Object[]} dependencyList List of dependencies that the feature requires. The list contains a list of objects that describe a feature
  17. * @param {String} dependency.name dependency name
  18. * @param {boolean} dependency.init True if the dependency is an init time dependency
  19. * @param {FeatureLoader} dependency.loader The feature loader used to access the feature
  20. * @param {Object} [loadingState] the loading state of a given feature. Used to check we are in the init phase or not
  21. * @param {Bboolean} [loadingState.loaded] Indicates if the feature is done loading or not
  22. */
  23. var createFeatureDependencies = function createFeatureDependencies(featureName, dependencyList, loadingState) {
  24. var map = {};
  25. if (dependencyList) {
  26. var _loop = function _loop(name) {
  27. var dep = dependencyList[name];
  28. Object.defineProperty(map, name, {
  29. get: function get() {
  30. if (!dep.init && !loadingState.loaded) {
  31. throw new Error('The required feature "' + dep.name + '" cannot be accessed at init time by the feature"' + featureName + '". The feature "' + dep.name + '" must be added to the initDependencies of feature "' + +featureName + '" if it is needed at init time.');
  32. }
  33. return dep.loader.getFeature(dep.name);
  34. }
  35. });
  36. };
  37. for (var name in dependencyList) {
  38. _loop(name);
  39. }
  40. }
  41. return map;
  42. };
  43. var MODULE_LOADING_ERROR = {};
  44. var FeatureLoader = function () {
  45. /**
  46. *
  47. * @param {Object} options
  48. * @param {Object} options.dashboardAPI
  49. * @param {Object} options.featureParams - parameters to be passed to the feautre when it is created
  50. * @param {String} options.collectionName - collection name that contains the features to be loaderd
  51. * @param {Object[]} options.inlineFeatures - array of inline feature to be created
  52. * @param {Object} options.lifeCycleManager - lifecyclemanager
  53. */
  54. function FeatureLoader(options) {
  55. _classCallCheck(this, FeatureLoader);
  56. this.externalFeatureLoader = options.externalFeatures;
  57. this.featureNamePrefix = options.featureNamePrefix;
  58. this.featureParams = options.featureParams;
  59. this.features = {};
  60. this.lifeCycleManager = options.lifeCycleManager;
  61. this.loadingOrder = [];
  62. this.errors = {};
  63. this.logger = options.logger || console;
  64. this.types = options.types || [];
  65. this.parentAPI = options.parentAPI;
  66. // todo - remove the featureSet -- it is not needed
  67. this.featureSet = options.featureSet;
  68. }
  69. FeatureLoader.prototype.getAPI = function getAPI() {
  70. if (!this._api) {
  71. this._api = APIFactory.createAPI(this, [FeatureLoaderAPI]);
  72. }
  73. return this._api;
  74. };
  75. FeatureLoader.prototype._getFeatureListThatInitDependsOn = function _getFeatureListThatInitDependsOn(featureName) {
  76. var list = [];
  77. for (var name in this.features) {
  78. if (this._getInitDependencies(name).indexOf(featureName) !== -1) {
  79. list.push(name);
  80. }
  81. }
  82. return list;
  83. };
  84. FeatureLoader.prototype._getFeatureModule = function _getFeatureModule(feature) {
  85. return feature.module.default && _.isFunction(feature.module.default) ? feature.module.default : feature.module;
  86. };
  87. FeatureLoader.prototype._createFeature = function _createFeature(feature) {
  88. feature.isCreated = true;
  89. var params = feature.params;
  90. var module = this._getFeatureModule(feature);
  91. feature.instance = new module(params);
  92. return feature.instance;
  93. };
  94. /**
  95. * Get the feature instance. This function will create the feature if it is hasn't been created.
  96. * We will also create any feature that has init depenency on this feature.. These dependent features might want to register themselves with this feature
  97. * This is typically when we have a "provider" type feature with the purpose of only registering itself in some other feature
  98. * These features are not typically accessed directly, so they won't get created if we don't do this trick here
  99. *
  100. *
  101. * @param {String} name
  102. */
  103. FeatureLoader.prototype._getFeatureInstance = function _getFeatureInstance(name) {
  104. var _this = this;
  105. var instance = void 0;
  106. if (this.features[name]) {
  107. if (!this.features[name].instance && !this._getError(name)) {
  108. try {
  109. //delayed creation of the feature
  110. var _instance = this._createFeature(this.features[name]);
  111. this.setParentChildRelation(name, _instance);
  112. // Create any feature that has init depenency on this feature. See comments above
  113. // TODO - fix this function to also find features that depend on thi given feature when using a feature modifier/type
  114. // example : 3 feature: A, B , C
  115. // A depends on C
  116. // B depends on C.modifier
  117. // When C is created, we want to create A and B .. but the following function only finds A
  118. this._getFeatureListThatInitDependsOn(name).forEach(function (depName) {
  119. if (!_this.features[depName].isCreated) {
  120. _this._getFeatureInstance(depName);
  121. }
  122. });
  123. } catch (e) {
  124. this._setError(name, 'Error while creating the feature:' + name, e, this.features);
  125. }
  126. }
  127. instance = this.features[name].instance;
  128. }
  129. return instance;
  130. };
  131. FeatureLoader.prototype._getFeatureTags = function _getFeatureTags(name) {
  132. return this.features[name] && this.features[name].spec && this.features[name].spec.tags || [];
  133. };
  134. FeatureLoader.prototype._getOptions = function _getOptions(name) {
  135. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  136. return features[name] && features[name].spec && features[name].spec.options;
  137. };
  138. FeatureLoader.prototype._getInitDependencies = function _getInitDependencies(name) {
  139. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  140. var initDep = features[name] && features[name].spec && features[name].spec.dependencies || [];
  141. return Array.isArray(initDep) ? initDep : Object.keys(initDep);
  142. };
  143. FeatureLoader.prototype._getRuntimeDependencies = function _getRuntimeDependencies(name) {
  144. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  145. var runtimeDep = features[name] && features[name].spec && features[name].spec.runtimeDependencies || [];
  146. return Array.isArray(runtimeDep) ? runtimeDep : Object.keys(runtimeDep);
  147. };
  148. FeatureLoader.prototype._getDependencies = function _getDependencies(name) {
  149. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  150. return this._getInitDependencies(name, features).concat(this._getRuntimeDependencies(name, features));
  151. };
  152. // Return the feature alias name if one is defined in the dependency
  153. FeatureLoader.prototype._getDependenciesPreferredName = function _getDependenciesPreferredName(depName, name) {
  154. var features = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.features;
  155. var preferredName = depName;
  156. // check init depdendencies
  157. var initDep = features[name] && features[name].spec && features[name].spec.dependencies;
  158. var runtimeDep = features[name] && features[name].spec && features[name].spec.runtimeDependencies;
  159. if (initDep && !Array.isArray(initDep) && initDep[depName]) {
  160. preferredName = initDep[depName];
  161. } else if (runtimeDep && !Array.isArray(runtimeDep) && runtimeDep[depName]) {
  162. preferredName = runtimeDep[depName];
  163. }
  164. return preferredName;
  165. };
  166. FeatureLoader.prototype._getError = function _getError(name) {
  167. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  168. return features[name] && features[name].error;
  169. };
  170. FeatureLoader.prototype.setParentChildRelation = function setParentChildRelation(name, instance) {
  171. if (this.parentAPI) {
  172. APIFactory.setParentChildRelation(this.parentAPI, instance, {
  173. propagationInfo: {
  174. namespace: name.toLowerCase(),
  175. info: {
  176. featureName: name
  177. },
  178. callStack: {
  179. name: 'getFeature',
  180. params: [name]
  181. }
  182. }
  183. });
  184. }
  185. };
  186. /**
  187. * A function to manually register a service. Could be used by the dashboard framework to register low level features.
  188. * @param {String} name
  189. * @param {Object} instance
  190. * @param {Object} spec
  191. */
  192. FeatureLoader.prototype.registerFeature = function registerFeature(name, instance, spec, skipInitialization) {
  193. var _this2 = this;
  194. this.features[name] = {
  195. instance: instance,
  196. spec: spec || {},
  197. preparePromise: Promise.resolve(name)
  198. };
  199. if (this.featureNamePrefix) {
  200. this.features[this.featureNamePrefix + '.' + name] = this.features[name];
  201. }
  202. var promise = void 0;
  203. if (skipInitialization) {
  204. promise = Promise.resolve();
  205. } else {
  206. promise = this._initializeFeature(name);
  207. }
  208. return promise.then(function () {
  209. // Hook up API events after the feature is initialized
  210. _this2.setParentChildRelation(name, instance);
  211. });
  212. };
  213. /**
  214. * Load the features from a collection json
  215. *
  216. * @param {Object[]} features
  217. * @param {Object} [featureSpecs] Feature specfications provided as paramaters to features
  218. *
  219. */
  220. FeatureLoader.prototype.loadFeaturesFromArray = function loadFeaturesFromArray(collectionItems, featureSpecs) {
  221. var _this3 = this;
  222. if (featureSpecs && Object.keys(featureSpecs).length > 0) {
  223. this.featureParams = this.featureParams ? this.featureParams : {};
  224. this.featureParams.featureSpecs = Object.assign({}, this.featureParams.featureSpecs, featureSpecs);
  225. }
  226. var promises = [];
  227. var features = {};
  228. var loadingState = {
  229. loaded: false,
  230. readyPromises: []
  231. };
  232. collectionItems.forEach(function (feature) {
  233. var identifier = feature.name || feature.id;
  234. // If the feature has types make it matches the type associated with the feature loader
  235. var skipFeature = false;
  236. if (_this3.types.length > 0 && feature.types && feature.types.length > 0) {
  237. var matchedType = _this3.types.find(function (type) {
  238. return feature.types.indexOf(type) !== -1;
  239. });
  240. skipFeature = !matchedType;
  241. }
  242. if (!skipFeature && (!_this3.featureSet || _this3.featureSet.indexOf(identifier) !== -1)) {
  243. features[identifier] = {
  244. spec: feature
  245. };
  246. if (MODULE_LOADING_ERROR[feature.class]) {
  247. // We already failed while loading this class. don't try again. Rquire.j will not hang the second time
  248. _this3._setError(identifier, 'Failed to require feature', MODULE_LOADING_ERROR[feature.class].causedBy, features);
  249. } else {
  250. promises.push(ClassFactory.loadModule(feature.class).then(function (module) {
  251. features[identifier].module = module;
  252. }).catch(function (e) {
  253. console.log(e);
  254. _this3._setError(identifier, 'Failed to require feature', e.causedBy, features);
  255. MODULE_LOADING_ERROR[feature.class] = e;
  256. }));
  257. }
  258. }
  259. });
  260. // Create the features based on the dependencies list and initialize them
  261. return Promise.all(promises).then(function () {
  262. //Validate the dependencies
  263. _this3._validateDependencies(features);
  264. var name = void 0;
  265. for (name in features) {
  266. _this3._prepareFeature(name, features, loadingState);
  267. }
  268. return Promise.all(loadingState.readyPromises).then(function () {
  269. loadingState.loaded = true;
  270. });
  271. });
  272. };
  273. /**
  274. * Return the order the features were loaded in
  275. * Used for testing to verify the order of loading
  276. */
  277. FeatureLoader.prototype.getFeatureLoadingOrder = function getFeatureLoadingOrder() {
  278. return this.loadingOrder;
  279. };
  280. FeatureLoader.prototype._setError = function _setError(featureName, message) {
  281. var details = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
  282. var features = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : this.features;
  283. if (!features[featureName]) {
  284. features[featureName] = {};
  285. }
  286. if (details instanceof ErrorEvent) {
  287. details = {
  288. message: details.message,
  289. lineno: details.lineno,
  290. filename: details.filename
  291. };
  292. }
  293. features[featureName].error = {
  294. message: message,
  295. details: details
  296. };
  297. // move the failed feature to the main list of features
  298. this.features[featureName] = features[featureName];
  299. this.logger.log('Feature loader:' + message, featureName, details);
  300. };
  301. FeatureLoader.prototype.getFeatureErrors = function getFeatureErrors() {
  302. var errors = {};
  303. var name = void 0;
  304. for (name in this.features) {
  305. if (this.features[name].error) {
  306. errors[name] = this.features[name].error;
  307. }
  308. }
  309. return errors;
  310. };
  311. FeatureLoader.prototype._prepareFeature = function _prepareFeature(name) {
  312. var _this4 = this;
  313. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  314. var loadingState = arguments[2];
  315. // If the feature is not part of the features list that are being currently loaded,
  316. // then we check the main feature list (this.features) that contains already loaded features
  317. // this could happen when a feature that loaded a part of a collection is trying to load one of its dependencies that
  318. // was previously loaded as part of another colletion.
  319. if (!features[name] && this.features[name]) {
  320. // If not found in the local feature map, then we try to global that has the loaded features.
  321. // If we find the feature in the global feature, we will switch to it.
  322. features = this.features;
  323. }
  324. // try #2 - we could be using a feature name with modifier (e.g. state.internal)
  325. // let us check if we have a modifier in the feature name .
  326. //this could happen when a feature is attempting to create a dependency and it is using a modifier in the feature name (e.g. state.internal)
  327. if (!features[name]) {
  328. var resolved = this._resolvePartialFeatureName(name, features);
  329. if (resolved) {
  330. name = resolved.name;
  331. } else {
  332. resolved = this._resolvePartialFeatureName(name, this.features);
  333. if (resolved) {
  334. name = resolved.name;
  335. features = this.features;
  336. }
  337. }
  338. }
  339. if (!features[name]) {
  340. this._setError(name, 'Attempting to load an undefined feature:' + name, '', features);
  341. }
  342. if (this._getError(name, features)) {
  343. features[name].preparePromise = Promise.resolve(name);
  344. }
  345. var preparePromise = features[name].preparePromise;
  346. if (!preparePromise) {
  347. var requiredFeatures = {};
  348. var initDepdendents = [];
  349. // Go over the init dependencies and create them first
  350. this._getInitDependencies(name, features).forEach(function (feature) {
  351. // if we depend on a feature that is not is part of the feature collection,
  352. // then check the external feature,
  353. if (_this4.isFeature(feature, features) || _this4.isFeature(feature)) {
  354. requiredFeatures[_this4._getDependenciesPreferredName(feature, name, features)] = {
  355. loader: _this4,
  356. init: true,
  357. name: feature
  358. };
  359. initDepdendents.push(_this4._prepareFeature(feature, features, loadingState));
  360. } else if (_this4.externalFeatureLoader && _this4.externalFeatureLoader.isFeature(feature)) {
  361. requiredFeatures[_this4._getDependenciesPreferredName(feature, name, features)] = {
  362. loader: _this4.externalFeatureLoader,
  363. init: true,
  364. name: feature
  365. };
  366. } else {
  367. _this4._setError(name, 'Attempting to load an undefined dependency:' + feature, '', features);
  368. }
  369. });
  370. // Go over the runtime dependencies and create them
  371. this._getRuntimeDependencies(name, features).forEach(function (feature) {
  372. // if we depend on a feature that is not is part of the feature collection,
  373. // then check the external feature,
  374. if (_this4.isFeature(feature, features) || _this4.isFeature(feature)) {
  375. requiredFeatures[_this4._getDependenciesPreferredName(feature, name, features)] = {
  376. loader: _this4,
  377. init: false,
  378. name: feature
  379. };
  380. } else if (_this4.externalFeatureLoader && _this4.externalFeatureLoader.isFeature(feature)) {
  381. requiredFeatures[_this4._getDependenciesPreferredName(feature, name, features)] = {
  382. loader: _this4.externalFeatureLoader,
  383. init: false,
  384. name: feature
  385. };
  386. } else {
  387. // Could not find the runtime dependency.. let us not consider it an error yet, maybe it is not register
  388. requiredFeatures[_this4._getDependenciesPreferredName(feature, name, features)] = {
  389. loader: _this4,
  390. init: false,
  391. name: feature
  392. };
  393. }
  394. });
  395. preparePromise = Promise.all(initDepdendents).then(function () {
  396. // Make sure that all dependencies loaded without an error
  397. var depErrors = [];
  398. _this4._getDependencies(name, features).forEach(function (featureName) {
  399. var error = _this4._getError(featureName, features);
  400. if (error) {
  401. depErrors.push(error);
  402. }
  403. });
  404. // TODO -- improve this loop and the one above
  405. var canDelayCreation = true;
  406. // If we already created the init time dependency, then we will not delay the creation of this feature.
  407. // This is because the delayed creating will happen when either:
  408. // 1 - the feature is accessed through the getter
  409. // 2 - one if its init depdendencies was created
  410. // TODO -- Possibly at one point, we want to also check external dependencies.
  411. // This might be needed when we have content feature that registers itself in a dashboard level feature in the initializer.
  412. _this4._getInitDependencies(name, features).forEach(function (featureName) {
  413. if (canDelayCreation) {
  414. canDelayCreation = !(_this4._isCreated(featureName, features) || _this4._isCreated(featureName, _this4.features));
  415. }
  416. });
  417. if (depErrors.length === 0) {
  418. // All good, create and initialize this feature if needed, otherwise we will lazy create it when requested
  419. _this4.loadingOrder.push(name);
  420. try {
  421. var params = _.extend({}, _this4.featureParams);
  422. var options = _this4._getOptions(name, features);
  423. if (options) {
  424. params.options = options;
  425. }
  426. params.features = createFeatureDependencies(name, requiredFeatures, loadingState);
  427. features[name].params = params;
  428. if (params.featureSpecs) {
  429. params.spec = params.featureSpecs[name];
  430. }
  431. // Don't delay the create if the feature has an initialize
  432. // Or if it depends on a feature that was already created -- (in case the feature is not referenced and simply registering itself in one of the dependencies)
  433. var module = _this4._getFeatureModule(features[name]);
  434. if (!canDelayCreation || module.prototype.initialize || module.prototype.getLifeCycleHandlers) {
  435. // we have to create and initialize because this feature needs some async initilize
  436. var instance = _this4._createFeature(features[name]);
  437. return _this4._initializeFeature(name, features).then(function () {
  438. // move the feature to the main list of features once initialized
  439. _this4.features[name] = features[name];
  440. if (_this4.featureNamePrefix) {
  441. _this4.features[_this4.featureNamePrefix + '.' + name] = features[name];
  442. }
  443. _this4.setParentChildRelation(name, instance);
  444. });
  445. } else {
  446. _this4.features[name] = features[name];
  447. if (_this4.featureNamePrefix) {
  448. _this4.features[_this4.featureNamePrefix + '.' + name] = features[name];
  449. }
  450. }
  451. } catch (e) {
  452. _this4._setError(name, 'Error while creating the feature:' + name, e, features);
  453. }
  454. } else {
  455. _this4._setError(name, 'Dependency failed to load', depErrors, features);
  456. }
  457. });
  458. features[name].preparePromise = preparePromise;
  459. // This is uesd by the main feature loading to tell when all features are loaded
  460. loadingState.readyPromises.push(preparePromise);
  461. }
  462. return preparePromise;
  463. };
  464. FeatureLoader.prototype._initializeFeature = function _initializeFeature(name) {
  465. var _this5 = this;
  466. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  467. var feature = features[name];
  468. var promise = null;
  469. try {
  470. var instance = feature.instance;
  471. if (typeof instance.initialize === 'function') {
  472. promise = instance.initialize();
  473. }
  474. if (promise && promise.then) {
  475. promise = promise.then(function () {
  476. _this5._postFeatureInitialize(feature);
  477. });
  478. } else {
  479. this._postFeatureInitialize(feature);
  480. }
  481. } catch (e) {
  482. this._setError(name, 'Failed to initialize feature:' + name, e, features);
  483. }
  484. if (!promise) {
  485. promise = Promise.resolve();
  486. }
  487. return promise.catch(function (e) {
  488. _this5._setError(name, 'Failed to initialize feature: ' + name, e, features);
  489. });
  490. };
  491. FeatureLoader.prototype._deregisterLifeCycleHandlers = function _deregisterLifeCycleHandlers(feature) {
  492. if (feature) {
  493. if (feature.registeredHandlers) {
  494. feature.registeredHandlers.forEach(function (handler) {
  495. return handler.remove();
  496. });
  497. }
  498. feature.registeredHandlers = [];
  499. }
  500. };
  501. // TODO - the lifecycle handlers should not be part of the feature loader
  502. // The lifeCycle manager is exposed as a feature.. so features can depend on it and register handlers directly with it.
  503. FeatureLoader.prototype._postFeatureInitialize = function _postFeatureInitialize(feature) {
  504. var _this6 = this;
  505. var instance = feature.instance;
  506. this._deregisterLifeCycleHandlers(feature);
  507. if (this.lifeCycleManager && typeof instance.getLifeCycleHandlers === 'function') {
  508. var handlers = instance.getLifeCycleHandlers();
  509. if (!Array.isArray(handlers)) {
  510. throw new Error('serviceExtension getLifeCycleHandlers method is expected to return an array');
  511. }
  512. handlers.forEach(function (handler) {
  513. if (!handler.name || !handler.action) {
  514. throw new Error('serviceExtension handlers are expected to have a name and action property');
  515. }
  516. feature.registeredHandlers.push(_this6.lifeCycleManager.registerHandler(handler.name, handler.action, handler.priority));
  517. });
  518. }
  519. };
  520. FeatureLoader.prototype._validateDependencies = function _validateDependencies() {
  521. var features = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.features;
  522. // Validate the dependencies for each step
  523. var name = void 0;
  524. for (name in features) {
  525. this._validateFeatureDependencies(name, null, features);
  526. }
  527. };
  528. FeatureLoader.prototype._validateFeatureDependencies = function _validateFeatureDependencies(featureId, currentDeps) {
  529. var _this7 = this;
  530. var features = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.features;
  531. var dependencies = currentDeps || [];
  532. if (dependencies.indexOf(featureId) !== -1) {
  533. throw new Error('The feature with id "' + featureId + '" has a a circular dependency: ' + currentDeps);
  534. }
  535. dependencies.push(featureId);
  536. this._getDependencies(featureId, features).forEach(function (depItem) {
  537. if (!_this7._getFeatureSpec(depItem, features)) {
  538. throw new Error('The feature with id "' + featureId + '" has a missing dependency "' + depItem + '"');
  539. }
  540. var clonedDepArray = dependencies.concat([]);
  541. _this7._validateFeatureDependencies(depItem, clonedDepArray, features);
  542. });
  543. };
  544. FeatureLoader.prototype._getFeatureSpec = function _getFeatureSpec(name) {
  545. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  546. return features[name] && features[name].spec || {};
  547. };
  548. FeatureLoader.prototype.setFeatureEnabled = function setFeatureEnabled(name, isEnabled) {
  549. if (this.features[name]) {
  550. if (isEnabled) {
  551. this.features[name].disabled = false;
  552. this._postFeatureInitialize(this.features[name]);
  553. } else {
  554. this.features[name].disabled = true;
  555. this._deregisterLifeCycleHandlers(this.features[name]);
  556. }
  557. }
  558. };
  559. FeatureLoader.prototype.destroy = function destroy() {
  560. var _this8 = this;
  561. _.each(_.keys(this.features), function (featureName) {
  562. // TODO -- we should have destroyFeature
  563. if (_this8.featureNamePrefix && featureName.startsWith(_this8.featureNamePrefix + '.')) {
  564. delete _this8.features[featureName];
  565. } else {
  566. if (_this8.features[featureName].instance) {
  567. _this8._deregisterLifeCycleHandlers(_this8.features[featureName]);
  568. var instance = _this8._getFeatureInstance(featureName);
  569. if (instance && instance.destroy) {
  570. try {
  571. instance.destroy();
  572. } catch (e) {
  573. _this8._setError(featureName, 'Error while destroying the feature:' + featureName, e);
  574. }
  575. }
  576. }
  577. delete _this8.features[featureName];
  578. }
  579. });
  580. for (var prop in this) {
  581. // clear everything referenced by this object in case someone is still holding on to it.
  582. if (Object.prototype.hasOwnProperty.call(this, prop)) {
  583. delete this[prop];
  584. }
  585. }
  586. };
  587. /**
  588. * Get a feature. If a feature is disabled or does not implement the getAPI function, then null will be returned.
  589. *
  590. * If the name is of the form 'myFeature.xyz', We will first try to find a feature that matches the full name,
  591. * if we can't find one, then we will look for a feature with name 'myFeature',
  592. * and we will pass the value 'xyz' to the getAPI() when we get the API object (i.e. myFeature.getAPI('xyz'))
  593. *
  594. * @param {String} name
  595. */
  596. FeatureLoader.prototype.getFeature = function getFeature(name) {
  597. var api;
  598. var instance = this._getFeatureInstance(name);
  599. var apiType = void 0;
  600. if (!instance && name) {
  601. var resolved = this._resolvePartialFeatureName(name);
  602. if (resolved) {
  603. name = resolved.name;
  604. apiType = resolved.apiType;
  605. instance = this._getFeatureInstance(name);
  606. }
  607. }
  608. if (this.isFeatureEnabled(name) && !this._getError(name) && instance.getAPI) {
  609. api = instance.getAPI(apiType);
  610. }
  611. return api;
  612. };
  613. FeatureLoader.prototype.isFeature = function isFeature(name) {
  614. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  615. var exist = features[name];
  616. if (!exist) {
  617. var resolved = this._resolvePartialFeatureName(name, features);
  618. if (resolved) {
  619. exist = features[resolved.name];
  620. }
  621. }
  622. return !!exist;
  623. };
  624. FeatureLoader.prototype._isCreated = function _isCreated(name) {
  625. var features = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  626. var exist = features[name];
  627. if (!exist) {
  628. var resolved = this._resolvePartialFeatureName(name, features);
  629. if (resolved) {
  630. exist = features[resolved.name];
  631. }
  632. }
  633. return exist && exist.instance;
  634. };
  635. FeatureLoader.prototype.getLoadedFeatures = function getLoadedFeatures() {
  636. var _this9 = this;
  637. // TODO - this is used for serialization .. it will end up creating all features even ones that were not referenced
  638. // WE can optimize to only use the feature that were created..
  639. var loadedFeatures = {};
  640. Object.keys(this.features).forEach(function (featureName) {
  641. if (!featureName.startsWith(_this9.featureNamePrefix + '.')) {
  642. var featureInstance = _this9._getFeatureInstance(featureName);
  643. if (featureInstance) {
  644. loadedFeatures[featureName] = featureInstance;
  645. }
  646. }
  647. });
  648. return loadedFeatures;
  649. };
  650. FeatureLoader.prototype._resolvePartialFeatureName = function _resolvePartialFeatureName(name) {
  651. var featuresMap = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.features;
  652. // We support the 2 cases:
  653. // "a.b.c" where "a" is the feature and "b.c" is the api type
  654. // or
  655. // "a.b.c" where "a.b" is the feature and "c" is the api type
  656. var parts = name.split('.');
  657. if (parts.length > 1) {
  658. var firstPart = parts[0];
  659. var lastPart = parts[parts.length - 1];
  660. var middlePart = parts.splice(1, parts.length - 2).join('.');
  661. name = middlePart ? firstPart + '.' + middlePart : firstPart;
  662. if (featuresMap[name]) {
  663. return {
  664. name: name,
  665. apiType: lastPart
  666. };
  667. } else {
  668. name = firstPart;
  669. if (featuresMap[name]) {
  670. return {
  671. name: name,
  672. apiType: lastPart ? middlePart + '.' + lastPart : middlePart
  673. };
  674. }
  675. }
  676. }
  677. return null;
  678. };
  679. /**
  680. * Indicate if the feature is enabled or not.
  681. * @param {boolean} name
  682. */
  683. FeatureLoader.prototype.isFeatureEnabled = function isFeatureEnabled(name) {
  684. var instance = this._getFeatureInstance(name);
  685. return instance && !this.features[name].disabled && (!instance.isEnabled || instance.isEnabled());
  686. };
  687. /**
  688. * @param tagToMatch - a tag that any feature must include in its getTags() api.
  689. * @returns any features that match the tag passed in as an array (empty array is returned if no features match).
  690. */
  691. FeatureLoader.prototype.getMatchingFeatures = function getMatchingFeatures(tagToMatch) {
  692. var _this10 = this;
  693. var matchingFeatures = [];
  694. _.each(_.keys(this.features), function (featureName) {
  695. if (_this10.isFeatureEnabled(featureName) && _this10._getFeatureTags(featureName).indexOf(tagToMatch) >= 0) {
  696. var featureAPI = _this10.getFeature(featureName);
  697. if (featureAPI) {
  698. matchingFeatures.push(featureAPI);
  699. }
  700. }
  701. });
  702. return matchingFeatures;
  703. };
  704. /**
  705. * Enable or disable a feature that matches the supplied tag
  706. * @param tagToMatch - a tag that any feature must include in its getTags() api.
  707. * @param isEnabled
  708. */
  709. FeatureLoader.prototype.setMatchingFeaturesEnabled = function setMatchingFeaturesEnabled(tagToMatch, isEnabled) {
  710. var _this11 = this;
  711. _.each(_.keys(this.features), function (featureName) {
  712. var feature = _this11.features[featureName];
  713. if (feature && _this11._getFeatureTags(featureName).indexOf(tagToMatch) >= 0) {
  714. _this11.setFeatureEnabled(featureName, isEnabled);
  715. }
  716. });
  717. };
  718. return FeatureLoader;
  719. }();
  720. return FeatureLoader;
  721. });
  722. //# sourceMappingURL=FeatureLoader.js.map