UNPKG

d2

Version:

Javascript library for DHIS2

604 lines (494 loc) 26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _check = require("../lib/check"); var _utils = require("../lib/utils"); var _ModelDefinitions = _interopRequireDefault(require("./ModelDefinitions")); var _Model = _interopRequireDefault(require("./Model")); var _ModelCollection = _interopRequireDefault(require("./ModelCollection")); var _ModelCollectionProperty = _interopRequireDefault(require("./ModelCollectionProperty")); var _SchemaTypes = _interopRequireDefault(require("../lib/SchemaTypes")); var _Filters = _interopRequireDefault(require("./Filters")); var _ModelBase = require("./ModelBase"); var _config = require("./config"); var _json = require("./helpers/json"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function createModelPropertyDescriptor(propertiesObject, schemaProperty) { var propertyName = schemaProperty.collection ? schemaProperty.collectionName : schemaProperty.name; var propertyDetails = { // Actual property descriptor properties configurable: false, enumerable: true, get: function get() { return this.dataValues[propertyName]; } }; // Store available constants for ENUM type properties if (schemaProperty.constants) { propertyDetails.constants = schemaProperty.constants; } // Only add a setter for writable properties if (schemaProperty.writable) { propertyDetails.set = function dynamicPropertySetter(value) { // TODO: Objects and Arrays are considered unequal when their data is the same and therefore trigger a dirty if (!(0, _check.isObject)(value) && value !== this.dataValues[propertyName] || (0, _check.isObject)(value)) { this.dirty = true; this[_ModelBase.DIRTY_PROPERTY_LIST].add(propertyName); this.dataValues[propertyName] = value; } }; } if (propertyName) { propertiesObject[propertyName] = propertyDetails; } } function createPropertiesObject(schemaProperties) { var propertiesObject = {}; var createModelPropertyDescriptorOn = (0, _utils.curry)(createModelPropertyDescriptor, propertiesObject); (schemaProperties || []).forEach(createModelPropertyDescriptorOn); return propertiesObject; } // eslint-disable-next-line complexity function createValidationSetting(validationObject, schemaProperty) { var propertyName = schemaProperty.collection ? schemaProperty.collectionName : schemaProperty.name; var validationDetails = { persisted: schemaProperty.persisted, type: _SchemaTypes.default.typeLookup(schemaProperty.propertyType), required: schemaProperty.required, min: schemaProperty.min, max: schemaProperty.max, owner: schemaProperty.owner, unique: schemaProperty.unique, writable: schemaProperty.writable, ordered: Boolean(schemaProperty.ordered), embeddedObject: Boolean(schemaProperty.embeddedObject) }; function getReferenceTypeFrom(property) { if (property.href) { return property.href.split('/').pop(); } return undefined; } // Add a referenceType to be able to get a hold of the reference objects model. if (validationDetails.type === 'REFERENCE' || validationDetails.type === 'COLLECTION' && schemaProperty.itemPropertyType === 'REFERENCE') { validationDetails.referenceType = getReferenceTypeFrom(schemaProperty); } if (propertyName) { validationObject[propertyName] = validationDetails; } } function createValidations(schemaProperties) { var validationsObject = {}; var createModelPropertyOn = (0, _utils.curry)(createValidationSetting, validationsObject); (schemaProperties || []).forEach(createModelPropertyOn); return validationsObject; } function shouldBeModelCollectionProperty(model, models) { // eslint-disable-next-line complexity return function shouldBeModelCollectionPropertyIterator(modelProperty) { return model && models && model.modelDefinition && model.modelDefinition.modelValidations && model.modelDefinition.modelValidations[modelProperty] && model.modelDefinition.modelValidations[modelProperty].type === 'COLLECTION' && models.hasOwnProperty(model.modelDefinition.modelValidations[modelProperty].referenceType); }; } function isAnUpdate(modelToCheck) { return Boolean(modelToCheck.id); } var translatableProperties = new WeakMap(); /** * Definition of a Model. Basically this object contains the meta data related to the Model. Like `name`, `apiEndPoint`, `modelValidation`, etc. * It also has methods to create and load Models that are based on this definition. The Data element `ModelDefinition` would be used to create Data Element `Model`s * * Note: ModelDefinition has a property `api` that is used for the communication with the dhis2 api. The value of this * property is an instance of `Api`. * * @memberof module:model */ var ModelDefinition = /*#__PURE__*/ function () { // eslint-disable-next-line complexity function ModelDefinition() { var schema = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var properties = arguments.length > 1 ? arguments[1] : undefined; var validations = arguments.length > 2 ? arguments[2] : undefined; var attributes = arguments.length > 3 ? arguments[3] : undefined; var authorities = arguments.length > 4 ? arguments[4] : undefined; _classCallCheck(this, ModelDefinition); (0, _check.checkType)(schema.singular, 'string'); (0, _check.checkType)(schema.plural, 'string', 'Plural'); (0, _utils.addLockedProperty)(this, 'name', schema.singular); (0, _utils.addLockedProperty)(this, 'displayName', schema.displayName); (0, _utils.addLockedProperty)(this, 'plural', schema.plural); (0, _utils.addLockedProperty)(this, 'isShareable', schema.shareable || false); (0, _utils.addLockedProperty)(this, 'isMetaData', schema.metadata || false); (0, _utils.addLockedProperty)(this, 'apiEndpoint', schema.apiEndpoint); (0, _utils.addLockedProperty)(this, 'javaClass', schema.klass); (0, _utils.addLockedProperty)(this, 'identifiableObject', schema && schema.identifiableObject); (0, _utils.addLockedProperty)(this, 'modelProperties', properties); (0, _utils.addLockedProperty)(this, 'modelValidations', validations); (0, _utils.addLockedProperty)(this, 'attributeProperties', attributes); (0, _utils.addLockedProperty)(this, 'authorities', authorities); (0, _utils.addLockedProperty)(this, 'translatable', schema.translatable || false); this.filters = _Filters.default.getFilters(this); translatableProperties.set(this, (schema.properties || []).filter(function (prop) { return prop.translationKey; }).map(function (_ref) { var name = _ref.name, translationKey = _ref.translationKey; return { name: name, translationKey: translationKey }; })); // TODO: The function getOwnedPropertyJSON should probably not be exposed, perhaps we could have a getJSONForModel(ownedPropertiesOnly=true) method. this.getOwnedPropertyJSON = _json.getOwnedPropertyJSON.bind(this); } _createClass(ModelDefinition, [{ key: "filter", value: function filter() { return this.clone().filters; } /** * Creates a fresh Model instance based on the `ModelDefinition`. If data is passed into the method that * data will be loaded into the matching properties of the model. * * @param {Object} [data] Data values that should be loaded into the model. * * @returns {Model} Returns the newly created model instance. * * @example * dataElement.create({name: 'ANC', id: 'd2sf33s3ssf'}); */ }, { key: "create", value: function create(data) { var model = _Model.default.create(this); var models = _ModelDefinitions.default.getModelDefinitions(); var dataValues = data ? Object.assign({}, data) : (0, _config.getDefaultValuesForModelType)(model.modelDefinition.name); Object.keys(model).filter(shouldBeModelCollectionProperty(model, models)).forEach(function (modelProperty) { var referenceType = model.modelDefinition.modelValidations[modelProperty].referenceType; var values = []; if (Array.isArray(dataValues[modelProperty])) { values = dataValues[modelProperty].map(function (value) { return models[referenceType].create(value); }); } else if (dataValues[modelProperty] === true || dataValues[modelProperty] === undefined) { values = dataValues[modelProperty]; } dataValues[modelProperty] = _ModelCollectionProperty.default.create(model, models[referenceType], modelProperty, values); model.dataValues[modelProperty] = dataValues[modelProperty]; }); Object.keys(model).filter(function (modelProperty) { return !shouldBeModelCollectionProperty(model, models)(modelProperty); }).forEach(function (modelProperty) { model.dataValues[modelProperty] = dataValues[modelProperty]; }); return model; } }, { key: "clone", value: function clone() { var ModelDefinitionPrototype = Object.getPrototypeOf(this); var priorFilters = this.filters.getFilterList(); var clonedDefinition = (0, _utils.copyOwnProperties)(Object.create(ModelDefinitionPrototype), this); clonedDefinition.filters = _Filters.default.getFilters(clonedDefinition, priorFilters); return clonedDefinition; } /** * Get a `Model` instance from the api loaded with data that relates to `identifier`. * This will do an API call and return a Promise that resolves with a `Model` or rejects with the api error message. * * @param {String} identifier * @param {Object} [queryParams={fields: ':all'}] Query parameters that should be passed to the GET query. * @returns {Promise} Resolves with a `Model` instance or an error message. * * @example * //Do a get request for the dataElement with given id (d2sf33s3ssf) and print it's name * //when that request is complete and the model is loaded. * dataElement.get('d2sf33s3ssf') * .then(model => console.log(model.name)); */ }, { key: "get", value: function get(identifier) { var _this = this; var queryParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { fields: ':all,attributeValues[:all,attribute[id,name,displayName]]' }; (0, _check.checkDefined)(identifier, 'Identifier'); if (Array.isArray(identifier)) { return this.list({ filter: ["id:in:[".concat(identifier.join(','), "]")] }); } // TODO: should throw error if API has not been defined return this.api.get([this.apiEndpoint, identifier].join('/'), queryParams).then(function (data) { return _this.create(data); }).catch(function (response) { if (response.message) { return Promise.reject(response.message); } return Promise.reject(response); }); } /** * Loads a list of models. * * @param {Object} [listParams={fields: ':all'}] Query parameters that should be passed to the GET query. * @returns {Promise} ModelCollection collection of models objects of the `ModelDefinition` type. * * @example * // Loads a list of models and prints their name. * dataElement.list() * .then(modelCollection => { * modelCollection.forEach(model => console.log(model.name)); * }); */ }, { key: "list", value: function list() { var _this2 = this; var listParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var apiEndpoint = listParams.apiEndpoint, extraParams = _objectWithoutProperties(listParams, ["apiEndpoint"]); var definedRootJunction = this.filters.rootJunction ? { rootJunction: this.filters.rootJunction } : {}; var params = Object.assign({ fields: ':all' }, definedRootJunction, extraParams); var definedFilters = this.filters.getQueryFilterValues(); if (!(0, _check.isDefined)(params.filter)) { delete params.filter; if (definedFilters.length) { params.filter = definedFilters; } } // If listParams.apiEndpoint exists, send the request there in stead of this.apiEndpoint return this.api.get(apiEndpoint || this.apiEndpoint, params).then(function (responseData) { return _ModelCollection.default.create(_this2, responseData[_this2.plural].map(function (data) { return _this2.create(data); }), Object.assign(responseData.pager || {}, { query: params })); }); } /** * This method is used by the `Model` instances to save the model when calling `model.save()`. * * @param {Model} model The model that should be saved to the server. * @returns {Promise} A promise which resolves when the save was successful * or rejects when it failed. The promise will resolve with the data that is * returned from the server. * * @note {warning} This should generally not be called directly. */ // TODO: check the return status of the save to see if it was actually successful and not ignored }, { key: "save", value: function save(model) { if (isAnUpdate(model)) { var jsonPayload = _json.getOwnedPropertyJSON.bind(this)(model); // Fallback to modelDefinition if href is unavailable var updateUrl = model.dataValues.href ? (0, _utils.updateAPIUrlWithBaseUrlVersionNumber)(model.dataValues.href, this.api.baseUrl) : [model.modelDefinition.apiEndpoint, model.dataValues.id].join('/'); // Save the existing model return this.api.update(updateUrl, jsonPayload, true); } return this.saveNew(model); } }, { key: "saveNew", value: function saveNew(model) { var jsonPayload = _json.getOwnedPropertyJSON.bind(this)(model); // Its a new object return this.api.post(this.apiEndpoint, jsonPayload); } /** * This method returns a list of property names that that are defined * as "owner" properties on this schema. This means these properties are used * when saving the model to the server. * * @returns {String[]} Returns an array of property names. * * @example * dataElement.getOwnedPropertyNames() */ }, { key: "getOwnedPropertyNames", value: function getOwnedPropertyNames() { var _this3 = this; return Object.keys(this.modelValidations).filter(function (propertyName) { return _this3.modelValidations[propertyName].owner; }); } /** * This method is used by the `Model` instances to delete the model when calling `model.delete()`. * * @returns {Promise} Returns a promise to the deletion operation * * @note {warning} This should generally not be called directly. */ }, { key: "delete", value: function _delete(model) { if (model.dataValues.href) { return this.api.delete(model.dataValues.href); } return this.api.delete([model.modelDefinition.apiEndpoint, model.dataValues.id].join('/')); } /** * Check for if the Model supports translations * * @returns {Boolean} True when the schema can be translated, false otherwise */ }, { key: "isTranslatable", value: function isTranslatable() { return this.translatable; } /** * These properties can be translated using the DHIS2 _database_ translations. * * @returns {String[]} Returns a list of property names on the object that are translatable. */ }, { key: "getTranslatableProperties", value: function getTranslatableProperties() { return translatableProperties.get(this).map((0, _utils.pick)('name')); } /** * This method is similar to getTranslatableProperties() but in addition to the property names also returns the * `translationKey` that is used to save the translations for the property names. * * @returns {Object[]} Returns an array with objects that have `name` and `translationKey` properties. */ }, { key: "getTranslatablePropertiesWithKeys", value: function getTranslatablePropertiesWithKeys() { return translatableProperties.get(this); } /** * @static * * This method creates a new `ModelDefinition` based on a JSON structure called * a schema. A schema represents the structure of a domain model as it is * required by DHIS. Since these schemas can not be altered on the server from * the modelDefinition is frozen to prevent accidental changes to the definition. * * @param {Object} schema A schema definition received from the web api (/api/schemas) * @param {Object[]} attributes A list of attribute objects that describe custom attributes (/api/attributes) * * @returns {ModelDefinition} Frozen model definition object. * * @example * ModelDefinition.createFromSchema(schemaDefinition, attributes); * * @note {info} An example of a schema definition can be found on * https://apps.dhis2.org/demo/api/schemas/dataElement */ }], [{ key: "createFromSchema", value: function createFromSchema(schema) { var attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var ModelDefinitionClass; (0, _check.checkType)(schema, Object, 'Schema'); if (typeof ModelDefinition.specialClasses[schema.singular] === 'function') { ModelDefinitionClass = ModelDefinition.specialClasses[schema.singular]; } else { ModelDefinitionClass = ModelDefinition; } return Object.freeze(new ModelDefinitionClass(schema, Object.freeze(createPropertiesObject(schema.properties)), Object.freeze(createValidations(schema.properties)), attributes.reduce(function (current, attributeDefinition) { current[attributeDefinition.name] = attributeDefinition; return current; }, {}), schema.authorities)); } }]); return ModelDefinition; }(); var UserModelDefinition = /*#__PURE__*/ function (_ModelDefinition) { _inherits(UserModelDefinition, _ModelDefinition); function UserModelDefinition() { _classCallCheck(this, UserModelDefinition); return _possibleConstructorReturn(this, _getPrototypeOf(UserModelDefinition).apply(this, arguments)); } _createClass(UserModelDefinition, [{ key: "get", // TODO: userCredentials should always be included, no matter what the query params, that is currently not the case value: function get(identifier) { var queryParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { fields: ':all,userCredentials[:owner]' }; return _get(_getPrototypeOf(UserModelDefinition.prototype), "get", this).call(this, identifier, queryParams); } }]); return UserModelDefinition; }(ModelDefinition); var DataSetModelDefinition = /*#__PURE__*/ function (_ModelDefinition2) { _inherits(DataSetModelDefinition, _ModelDefinition2); function DataSetModelDefinition() { _classCallCheck(this, DataSetModelDefinition); return _possibleConstructorReturn(this, _getPrototypeOf(DataSetModelDefinition).apply(this, arguments)); } _createClass(DataSetModelDefinition, [{ key: "create", value: function create() { var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var hasData = Boolean(Object.keys(data).length); // Filter out the compulsoryDataElementOperands structure from the retrieved data // This structure does not follow the convention of a typical reference. We can not create a proper // ModelCollection for this collection. var dataClone = Object.keys(data).filter(function (key) { return key !== 'compulsoryDataElementOperands'; }).reduce(function (obj, key) { obj[key] = data[key]; return obj; }, {}); // Create the model using the usual way of creating a model // Only pass data when there is data in the object passed to the constructor. This will guarantee // that the empty ModelCollections are created properly. var model = _get(_getPrototypeOf(DataSetModelDefinition.prototype), "create", this).call(this, hasData ? dataClone : undefined); // Set the compulsoryDataElementOperands onto the dataValues so it will be included during the save operations model.dataValues.compulsoryDataElementOperands = data.compulsoryDataElementOperands; return model; } }]); return DataSetModelDefinition; }(ModelDefinition); var OrganisationUnitModelDefinition = /*#__PURE__*/ function (_ModelDefinition3) { _inherits(OrganisationUnitModelDefinition, _ModelDefinition3); function OrganisationUnitModelDefinition() { _classCallCheck(this, OrganisationUnitModelDefinition); return _possibleConstructorReturn(this, _getPrototypeOf(OrganisationUnitModelDefinition).apply(this, arguments)); } _createClass(OrganisationUnitModelDefinition, [{ key: "list", // If a 'root' is specified when listing organisation units the results will be limited to the root and its // descendants. This is special behavior for the organisation unit API endpoint, which is documented here: // https://dhis2.github.io/dhis2-docs/master/en/developer/html/webapi_organisation_units.html value: function list() { var extraParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var root = extraParams.root, params = _objectWithoutProperties(extraParams, ["root"]); if (extraParams.hasOwnProperty('root') && root) { params.apiEndpoint = "".concat(this.apiEndpoint, "/").concat(root); } return _get(_getPrototypeOf(OrganisationUnitModelDefinition.prototype), "list", this).call(this, params); } }]); return OrganisationUnitModelDefinition; }(ModelDefinition); ModelDefinition.specialClasses = { user: UserModelDefinition, dataSet: DataSetModelDefinition, organisationUnit: OrganisationUnitModelDefinition }; var _default = ModelDefinition; exports.default = _default; //# sourceMappingURL=ModelDefinition.js.map