UNPKG

d2

Version:

Javascript library for DHIS2

445 lines (410 loc) 15.3 kB
/** * @module d2 * * @description * Module that contains the entry points for working with the d2 instance. Most of the api related functionality will be available through this package. * * The most important functions from the module are {@link module:d2.init|init} and {@link module:d2.init|getInstance} these are used to create and to get * a hold of the initialized instance of {@link module:d2.init~d2|d2}. The initialized instance is the object that allows you access most of d2's functionality. * * @example * import { init } from 'd2/lib/d2'; * * init({ baseUrl: 'https://play.dhis2.org/demo/api/27/' }) * .then(d2 => console.log(d2.currentUser.name)); */ import 'isomorphic-fetch' import { pick, Deferred, updateAPIUrlWithBaseUrlVersionNumber, } from './lib/utils' import Logger from './logger/Logger' import model from './model' import Api from './api/Api' import System from './system/System' import I18n from './i18n/I18n' import Config from './config' import CurrentUser from './current-user/CurrentUser' import { fieldsForSchemas } from './model/config' import DataStore from './datastore/DataStore' import Analytics from './analytics/Analytics' import GeoFeatures from './geofeatures/GeoFeatures' let firstRun = true let deferredD2Init = Deferred.create() const preInitConfig = Config.create() /** * Utility function to load the app manifest. * * The manifest well be enhanced with a `getBaseUrl()` utility function that will return the base url of the DHIS2 instance. * This is a simple getter for the `activities.dhis.href` property on the manifest. * * @example * import { getManifest } from 'd2/lib/d2'; * * getManifest() * .then(manifest => { * console.log(manifest.getBaseUrl()); * }); * * @param {string} url The location of the manifest. Generally this is located in the root of your app folder. (e.g. './manifest.webapp) * @param {Api} [ApiClass] An implementation of the Api class that will be used to fetch the manifest. * * @returns {Promise} Returns a Promise to the DHIS2 app manifest with the added `getBaseUrl` method. */ export function getManifest(url, ApiClass = Api) { const api = ApiClass.getApi() api.setBaseUrl('') const manifestUtilities = { getBaseUrl() { return this.activities.dhis.href }, } return api .get(`${url}`) .then(manifest => Object.assign({}, manifest, manifestUtilities)) } /** * @function getUserSettings * * @returns {Promise} A promise to the current user settings * * @description * The object that is the result of the promise will have the following properties * * @example * import {getUserSettings} from 'd2/lib/d2'; * * getUserSettings() * .then(userSettings => { * console.log(userSettings); * }); */ export function getUserSettings(ApiClass = Api) { const api = ApiClass.getApi() if (firstRun) { Config.processPreInitConfig(preInitConfig, api) } return api.get('userSettings') } function getModelRequests(api, schemaNames) { const modelRequests = [] const loadSchemaForName = schemaName => api.get(`schemas/${schemaName}`, { fields: fieldsForSchemas }) if (Array.isArray(schemaNames)) { const individualSchemaRequests = schemaNames .map(loadSchemaForName) .concat([]) const schemasPromise = Promise.all( individualSchemaRequests ).then(schemas => ({ schemas })) modelRequests.push(schemasPromise) if (schemaNames.length > 0) { // If schemas are loaded, attributes should be as well modelRequests.push( api.get('attributes', { fields: ':all,optionSet[:all,options[:all]]', paging: false, }) ) } else { // Otherwise, just return an empty list of attributes modelRequests.push({ attributes: [] }) } } else { // If no schemas are specified, load all schemas and attributes modelRequests.push(api.get('schemas', { fields: fieldsForSchemas })) modelRequests.push( api.get('attributes', { fields: ':all,optionSet[:all,options[:all]]', paging: false, }) ) } return modelRequests } /** * Init function that used to initialise {@link module:d2.init~d2|d2}. This will load the schemas from the DHIS2 api and configure your {@link module:d2.init~d2|d2} instance. * * The `config` object that can be passed into init can have the following properties: * * baseUrl: Set this when the url is something different then `/api`. If you are running your dhis instance in a subdirectory of the actual domain * for example http://localhost/dhis/ you should set the base url to `/dhis/api` * * unauthorizedCb: A callback function that is called whenever a API-request encounters a 401 - Unauthorized response. * The function is called with (request, response) - the request object that failed, and the parsed response from the server. * * @param {Object} initConfig Configuration object that will be used to configure to define D2 Setting. * See the description for more information on the available settings. * @returns {Promise.<D2>} A promise that resolves with the intialized {@link init~d2|d2} object. * * @example * import init from 'd2'; * * init({baseUrl: '/dhis/api'}) * .then((d2) => { * console.log(d2.model.dataElement.list()); * }); */ export function init(initConfig, ApiClass = Api, logger = Logger.getLogger()) { const api = ApiClass.getApi() const config = Config.create(preInitConfig, initConfig) /** * @namespace */ const d2 = { /** * @description * This is the entry point for the modelDefinitions that were loaded. To start interacting with the metadata api * you would pick a modelDefinition from this object to interact with. * * @type {Object.<string, ModelDefinition>} * @instance */ models: undefined, /** * Collection of the {@link module:model} classes * * @deprecated There is probably no point to expose this. * @instance */ model, // TODO: Remove (Breaking) /** * Api class that is used throughout the api interaction. This can be used to get hold of the module:Api singleton. * * @example * d2.Api.getApi() // Returns the api object * .get('resources') // Do a get request for /api/resources * .then(resources => { * console.log(resources); * }); * * @see {@link module:api~Api#getApi} * * @instance */ Api: ApiClass, /** * System instance to interact with system information like system settings, system info etc. * * @example * console.log(d2.system.version.major); // 2 for DHIS 2.27 * * @see {@link module:system/System~System|System} * @instance */ system: System.getSystem(), /** * I18n instance with the loaded translations. * * Usually used for retrieving translations for a given key using `getTranslation(key: string)` * * @example * d2.i18n.getTranslation('success'); // Returns "Success" for the english locale * * @see {@link module:i18n~I18n#getTranslation|getTranslation} * * @instance */ i18n: I18n.getI18n(), /** * Instance of the DataStore class for interaction with the dataStore api. * * @see {@link module:datastore.DataStore DataStore} * * @instance */ dataStore: DataStore.getDataStore(), /** * Analytics instance for requesting analytics data from various endpoints. * * @example * d2.analytics.aggregate * .addDimensions([ * 'dx:Uvn6LCg7dVU;OdiHJayrsKo', * 'pe:LAST_4_QUARTERS', * 'ou:lc3eMKXaEfw;PMa2VCrupOd', * ]) * .addFilter('pe:2016Q1;2016Q2') * .getRawData({ * startDate: '2017-10-01', * endDate: '2017-10-31' * }) * .then(console.log) * * @see {@link module:analytics.Analytics Analytics} * @instance */ analytics: Analytics.getAnalytics(), /* * GeoFeatures instance * * @see {@link module:geoFeatures.GeoFeatures GeoFeatures} * @instance */ geoFeatures: GeoFeatures.getGeoFeatures(), } // Process the config in a the config class to keep all config calls together. Config.processConfigForD2(config, d2) // Because when importing the getInstance method in dependencies the getInstance could run before // init we have to resolve the current promise on first run and for consecutive ones replace the // old one with a fresh promise. if (firstRun) { firstRun = false } else { deferredD2Init = Deferred.create() } const modelRequests = getModelRequests(api, config.schemas) const userRequests = [ api.get('me', { fields: ':all,organisationUnits[id],userGroups[id],userCredentials[:all,!user,userRoles[id]', }), api.get('me/authorization'), getUserSettings(ApiClass), ] const systemRequests = [api.get('system/info'), api.get('apps')] return Promise.all([ ...modelRequests, ...userRequests, ...systemRequests, d2.i18n.load(), ]) .then(res => { const responses = { schemas: pick('schemas')(res[0]), attributes: pick('attributes')(res[1]), currentUser: res[2], authorities: res[3], userSettings: res[4], systemInfo: res[5], apps: res[6], } responses.schemas // We only deal with metadata schemas .filter(schema => schema.metadata) // TODO: Remove this when the schemas endpoint is versioned or shows the correct urls for the requested version // The schemas endpoint is not versioned which will result into the modelDefinitions always using the // "default" endpoint, we therefore modify the endpoint url based on the given baseUrl. .map(schema => { schema.apiEndpoint = updateAPIUrlWithBaseUrlVersionNumber( schema.apiEndpoint, config.baseUrl ) return schema }) .forEach(schema => { // Attributes that do not have values do not by default get returned with the data, // therefore we need to grab the attributes that are attached to this particular schema to be able to know about them const schemaAttributes = responses.attributes.filter( attributeDescriptor => { const attributeNameFilter = [ schema.singular, 'Attribute', ].join('') return ( attributeDescriptor[attributeNameFilter] === true ) } ) if ( !Object.prototype.hasOwnProperty.call( d2.models, schema.singular ) ) { d2.models.add( model.ModelDefinition.createFromSchema( schema, schemaAttributes ) ) } }) /** * An instance of {@link module:current-user/CurrentUser~CurrentUser|CurrentUser} * * The currentUser can be used to retrieve data related to the currentUser. * * These things primarily include: * - currentUser properties retrieved from `/api/me` * - Lazily request collections related to the user such as * - userRoles * - userGroups * - organisationUnits * - dataViewOrganisationUnits * - authorities * - userSettings * - utility methods for ACL * * @example * d2.currentUser.canCreate(d2.models.dataElement); // Returns true when the user can create either a private/public dataElement * d2.currentUser.canCreate(d2.models.organisationUnit); // Returns true the user can create an organisationUnit * * @see {@link module:current-user/CurrentUser~CurrentUser|CurrentUser} * @instance */ d2.currentUser = CurrentUser.create( responses.currentUser, responses.authorities, d2.models, responses.userSettings ) d2.system.setSystemInfo(responses.systemInfo) d2.system.setInstalledApps(responses.apps) deferredD2Init.resolve(d2) return deferredD2Init.promise }) .catch(error => { logger.error( 'Unable to get schemas from the api', JSON.stringify(error), error ) deferredD2Init.reject('Unable to get schemas from the DHIS2 API') return deferredD2Init.promise }) } /** * This function can be used to retrieve the `singleton` instance of d2. The instance is being created by calling * the `init` method. * * @returns {Promise.<D2>} A promise to the initialized {@link module:d2.init~d2|d2} instance. * * @example * import {init, getInstance} from 'd2'; * * init({baseUrl: '/dhis2/api/'}); * getInstance() * .then(d2 => { * d2.models.dataElement.list(); * // and all your other d2 magic. * }); */ export function getInstance() { return deferredD2Init.promise } export function setInstance(d2) { deferredD2Init.resolve(d2) } /** * Can be used to set config options before initialisation of d2. * * @example * import {config, init} from 'd2'; * * config.baseUrl = '/demo/api'; * config.i18n.sources.add('i18n/systemsettingstranslations.properties'); * * init() * .then(d2 => { * d2.system.settings.all() * .then(systemSettings => Object.keys()) * .then(systemSettingsKey => { * d2.i18n.getTranslation(systemSettingsKey); * }); * }); * @type Config */ export const config = preInitConfig // Alias preInitConfig to be able to `import {config} from 'd2';`