2018-04-09 15:55:38 -07:00
|
|
|
import Service from '@ember/service';
|
2020-09-29 16:04:25 -07:00
|
|
|
import { appConfigUrl } from '@datahub/shared/api/configurator/configurator';
|
2019-08-31 20:51:14 -07:00
|
|
|
import { getJSON } from '@datahub/utils/api/fetcher';
|
|
|
|
import { ApiStatus } from '@datahub/utils/api/shared';
|
2020-09-29 16:04:25 -07:00
|
|
|
import deepClone from '@datahub/utils/function/deep-clone';
|
2019-09-04 21:46:02 -07:00
|
|
|
import {
|
|
|
|
IAppConfig,
|
|
|
|
IAppConfigOrProperty,
|
|
|
|
IConfigurator,
|
|
|
|
IConfiguratorGetResponse
|
|
|
|
} from '@datahub/shared/types/configurator/configurator';
|
2018-07-24 11:01:50 -07:00
|
|
|
|
2018-04-09 15:55:38 -07:00
|
|
|
/**
|
|
|
|
* Holds the application configuration object
|
|
|
|
* @type {IAppConfig}
|
|
|
|
*/
|
2019-08-31 20:51:14 -07:00
|
|
|
let appConfig: Partial<IAppConfig> = {};
|
2018-04-09 15:55:38 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag indicating the config object has been successfully loaded from the remote endpoint
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
|
|
|
let configLoaded = false;
|
|
|
|
|
2019-09-04 21:46:02 -07:00
|
|
|
/**
|
|
|
|
* Returns a copy of the last saved configuration object if one was successfully retrieved,
|
|
|
|
* or a copy of the property on the IAppConfig object, if specified
|
|
|
|
* @template K
|
|
|
|
* @param {K} [key] if provided, the value is returned with that key on the config hash is returned
|
|
|
|
* @param {IAppConfigOrProperty<K>} [defaultValue] if provided, will default if key is not found in config
|
|
|
|
* @returns {IAppConfigOrProperty<K>}
|
|
|
|
* @memberof Configurator
|
|
|
|
*/
|
|
|
|
export const getConfig = function<K extends keyof IAppConfig | undefined>(
|
|
|
|
key?: K,
|
|
|
|
options: { useDefault?: boolean; default?: IAppConfigOrProperty<K> } = {}
|
|
|
|
): IAppConfigOrProperty<K> {
|
|
|
|
// Ensure that the application configuration has been successfully cached
|
2020-08-26 15:44:50 -07:00
|
|
|
if (!configLoaded) {
|
|
|
|
throw new Error('Please ensure you have invoked the `load` method successfully prior to calling `getConfig`.');
|
|
|
|
}
|
2019-09-04 21:46:02 -07:00
|
|
|
|
|
|
|
return typeof key === 'string' && appConfig.hasOwnProperty(key as keyof IAppConfig)
|
|
|
|
? (deepClone(appConfig[key as keyof IAppConfig]) as IAppConfigOrProperty<K>)
|
|
|
|
: options.useDefault
|
|
|
|
? (options.default as IAppConfigOrProperty<K>)
|
|
|
|
: (deepClone(appConfig) as IAppConfigOrProperty<K>);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default class Configurator extends Service implements IConfigurator {
|
2018-04-09 15:55:38 -07:00
|
|
|
/**
|
|
|
|
* Fetches the application configuration object from the provided endpoint and augments the appConfig object
|
|
|
|
* @return {Promise<IAppConfig>}
|
|
|
|
*/
|
2019-09-04 21:46:02 -07:00
|
|
|
async load(): Promise<IAppConfig> {
|
2018-04-09 15:55:38 -07:00
|
|
|
try {
|
|
|
|
const { status, config } = await getJSON<IConfiguratorGetResponse>({ url: appConfigUrl });
|
|
|
|
if (status === ApiStatus.OK) {
|
|
|
|
return (configLoaded = true) && Object.assign(appConfig, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.reject(new Error(`Configuration load failed with status: ${status}`));
|
|
|
|
} catch (e) {
|
|
|
|
configLoaded = false;
|
|
|
|
return Promise.reject(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-04 21:46:02 -07:00
|
|
|
getConfig<K extends keyof IAppConfig | undefined>(
|
2018-09-11 16:51:52 -07:00
|
|
|
key?: K,
|
2018-09-12 15:33:52 -07:00
|
|
|
options: { useDefault?: boolean; default?: IAppConfigOrProperty<K> } = {}
|
2018-09-11 16:51:52 -07:00
|
|
|
): IAppConfigOrProperty<K> {
|
2019-09-04 21:46:02 -07:00
|
|
|
return getConfig(key, options);
|
2018-04-09 15:55:38 -07:00
|
|
|
}
|
|
|
|
}
|
2019-08-31 20:51:14 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Syntactic sugar over Configurator. Instead of Configurator.getConfig('xxxx'),
|
|
|
|
* you can do, config.xxxx which is a little bit shorter. Also it will return
|
|
|
|
* undefined if the config is not found
|
|
|
|
*/
|
|
|
|
export const config: Partial<IAppConfig> = new Proxy<Partial<IAppConfig>>(appConfig, {
|
|
|
|
/**
|
|
|
|
* Proxy getter for Configurator
|
|
|
|
* @param obj {IAppConfig}
|
|
|
|
* @param prop {keyof IAppConfig}
|
|
|
|
*/
|
|
|
|
get: function<K extends keyof IAppConfig>(_: IAppConfig, prop: K): IAppConfigOrProperty<K> {
|
2019-09-04 21:46:02 -07:00
|
|
|
return getConfig(prop, {
|
2019-08-31 20:51:14 -07:00
|
|
|
useDefault: true,
|
|
|
|
default: undefined
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For testing purposes: sets a new config
|
2020-08-26 15:44:50 -07:00
|
|
|
* TODO: META-12096 Deprecate ability to set mock config and rely Mirage configs / scenarios instead
|
|
|
|
* Setting this is an anti-pattern akin to setting global state, makes tests brittle and causes random tests failures
|
|
|
|
* Tests should utilize the stubService helper in Component(Integration) tests, and / or Mirage scenarios
|
|
|
|
* in Application(Acceptance) tests
|
|
|
|
* @see https://www.ember-cli-mirage.com/docs/testing/acceptance-tests#keeping-your-tests-focused
|
|
|
|
* @see https://www.ember-cli-mirage.com/docs/testing/acceptance-tests#arrange-act-assert
|
2019-08-31 20:51:14 -07:00
|
|
|
* @param config
|
|
|
|
*/
|
2020-08-26 15:44:50 -07:00
|
|
|
export const setMockConfig = (config?: Partial<IAppConfig>): void => {
|
2019-08-31 20:51:14 -07:00
|
|
|
configLoaded = true;
|
2020-08-26 15:44:50 -07:00
|
|
|
Object.assign(appConfig, config);
|
2019-08-31 20:51:14 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For testing purposes: reset config state
|
|
|
|
*/
|
|
|
|
export const resetConfig = (): void => {
|
|
|
|
configLoaded = false;
|
|
|
|
appConfig = {};
|
|
|
|
};
|
2019-09-04 21:46:02 -07:00
|
|
|
|
|
|
|
declare module '@ember/service' {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
|
|
|
|
interface Registry {
|
|
|
|
configurator: Configurator;
|
|
|
|
}
|
|
|
|
}
|