UNPKG

@aappddeevv/dynamics-client-ui

Version:

## What is it? A library to help you create great dynamics applications.

401 lines 19.9 kB
"use strict"; /** * Metadata access with cacheing so its not too slow. Cache * is shared across all Metadata instances. */ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const BuildSettings_1 = require("BuildSettings"); //import * as R from "ramda" const values_1 = require("ramda/es/values"); const mergeDeepRight_1 = require("ramda/es/mergeDeepRight"); /** * Return the user localized label using lcid if its provided and found. */ function getLabel(labels, lcid) { if (!lcid && labels.UserLocalizedLabel) { // if no lcid, return the default if it exists return labels.UserLocalizedLabel; } const x = labels.LocalizedLabels.filter(l => l.LanguageCode === lcid); if (x.length > 0) return x[0]; return null; } exports.getLabel = getLabel; /** [entity name] => {[attribute name]: Attribute} */ let entityToAttribute = {}; /** singular entity name to entity object */ const entityNameToDefinition = new Map(); const entityDefinitions = []; /** entity logical name to array of relationships */ const entityNameToOneToMany = new Map(); const entityNameToManyToOne = new Map(); let objectTypeCodes = []; const objectTypeCodesByCode = new Map(); const objectTypeCodesByName = new Map(); let connectionRoleCategories = []; const connectionRoleCategoriesByName = new Map(); const connectionRoleCategoriesByValue = new Map(); let connectionRoles = []; const connectionRolesById = new Map(); const connectionRolesByName = new Map(); /** Reciprocal connection roles. Most just have 1 but you can have many. */ const connectionRoleAssociatedRoles = new Map(); let connectionRoleObjectTypeCodes = []; /** Cache that indexes by a connection role's id. */ const connectionRoleObjectTypeCodesByRoleId = new Map(); /** * Metadata API. Fetched metadata is shared among all instances of this class at the moment. */ class Metadata { constructor(client, lcid = 1033) { /** Get all attributes for a logical entity name or return [] */ this.getAttributes = (entityName) => tslib_1.__awaiter(this, void 0, void 0, function* () { // quick wins and cache check if (!entityName) { if (BuildSettings_1.DEBUG) console.log("Metadata.getAttributes: called with nil entityName"); return []; } else if (entityName in entityToAttribute) { const entry = entityToAttribute[entityName]; return values_1.default(entry); } try { const m = yield this.getMetadata(entityName); // Navigate to attributes by pulling an EntityDefinition with the Attributes. const attrs = yield this.client.Get("EntityDefinitions", m.MetadataId, { Select: ["LogicalName"], Expand: [{ Property: "Attributes" }] }). then(m => m.Attributes); if (attrs && attrs.length > 0) { // place in cache, by logical name! const mergeMe = attrs.reduce((accum, a) => { accum[a.LogicalName] = a; return accum; }, {}); entityToAttribute = mergeDeepRight_1.default(entityToAttribute, { [entityName]: mergeMe }); return attrs; } } catch (e) { console.log(`Error obtaining entity attributes for entity name '${entityName}'`, e); } // no attributes returned? probably a bad entity name?? should we error? return []; }); /** Find a specific entity-attribute metadata. Return null if not found. */ this.lookupAttribute = (entityName, attributeName) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (BuildSettings_1.DEBUG) { console.log(`Metadata.lookupAttribute: looking up '${entityName}.${attributeName}'`); } if (!entityName || !attributeName) return null; yield this.getAttributes(entityName); // attribtse for entityName should be in "cache" const entityAttributes = entityToAttribute[entityName]; if (entityAttributes) { const attribute = entityAttributes[attributeName]; if (attribute) return attribute; } return null; }); /** Returns all entity {LogicalName, ObjectTypeCode} pairs. */ this.getObjectTypeCodes = () => tslib_1.__awaiter(this, void 0, void 0, function* () { if (objectTypeCodes.length > 0) return objectTypeCodes; const qopts = { Select: ["LogicalName", "ObjectTypeCode"] }; const r = yield this.client.GetList("EntityDefinitions", qopts); objectTypeCodes = r.List; objectTypeCodes.forEach(c => objectTypeCodesByCode.set(c.ObjectTypeCode, c)); objectTypeCodes.forEach(c => objectTypeCodesByName.set(c.LogicalName, c)); return objectTypeCodes; }); /** Given a name, return the (LogicalName, ObjectTypeCode) pair. */ this.lookupObjectTypeCodeByName = (name) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getObjectTypeCodes(); return objectTypeCodesByName.get(name); }); /** Pass in the entity singular logical name. Returns null if not found. Pulls all attributes but no navs. */ this.getMetadata = (entityName) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (BuildSettings_1.DEBUG) console.log(`Metadata.getMetadata: entity name ${entityName}`); const cacheCheck = entityNameToDefinition.get(entityName); if (cacheCheck) return cacheCheck; const qopts = { Filter: `LogicalName eq '${entityName}'` }; // We can do this with a EntityDefinitions(LogicalName='..name...') but CRMWebAPI // does not have that. return this.client.GetList("EntityDefinitions", qopts). then(r => { if (!r.List) return null; // add to cache const edef = r.List[0]; entityDefinitions.push(edef); entityNameToDefinition.set(entityName, edef); return edef; }); }); /** Get the entity set name given the entity logical name e.g. contact => contacts. */ this.getEntitySetName = (logicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const md = yield this.getMetadata(logicalName); if (md) return md.LogicalCollectionName; return null; }); /** Get the schema name given the entity logical name. */ this.getSchemaName = (logicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const md = yield this.getMetadata(logicalName); if (md) return md.SchemaName; return null; }); /** Return all connection roles. */ this.getConnectionRoles = () => tslib_1.__awaiter(this, void 0, void 0, function* () { if (connectionRoles.length > 0) return connectionRoles; const qopts = { FormattedValues: true, Filter: "statecode eq 0", Expand: [ { Property: "connectionroleassociation_association" }, ], }; const r = yield this.client.GetList("connectionroles", qopts).then(r => r.List); //console.log("connectionroles", r) connectionRoles = r; connectionRoles.forEach(cr => connectionRolesById.set(cr.connectionroleid, cr)); connectionRoles.forEach(cr => connectionRolesByName.set(cr.name, cr)); return r; }); /** Get "reciprocal" ConnectionRoles. You'll need to lookup the id using `getConnectionRoleById`. */ this.getConnectionRoleAssociatedConnectionRoles = (connectionRoleId) => tslib_1.__awaiter(this, void 0, void 0, function* () { if (connectionRoleAssociatedRoles.has(connectionRoleId)) return connectionRoleAssociatedRoles.get(connectionRoleId); const cr = yield this.getConnectionRoleById(connectionRoleId); if (!cr) return []; const qopts = { Select: ["connectionroledid"], }; const associated = yield this.client.Fetch(cr["connectionroleassociation_association@odata.nextLink"], qopts) .then(r => r.value); const associatedIds = associated.map(cr => cr.connectionroleid); connectionRoleAssociatedRoles.set(connectionRoleId, associatedIds); return associatedIds; }); /** * Return an array of connection roles for a given connection category name. * * TODO: Rewrite this so it does not need a filter, just use the id lookup cache. */ this.getConnectionRolesForCategoryNamed = (categoryName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const roles = yield this.getConnectionRoles(); const cat = yield this.getConnectionRoleCategoryByName(categoryName); return roles.filter(cr => cr.category === cat.Value); }); this.getConnectionRoleByCategoryAndName = (categoryName, roleName) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getConnectionRoles(); const x = connectionRoles.filter(cr => cr.name === roleName && cr["category@OData.Community.Display.V1.FormattedValue"] === categoryName); if (x.length === 1) return x[0]; return null; }); /** Return a connection role by its id. */ this.getConnectionRoleById = (id) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getConnectionRoles(); const r = connectionRolesById.get(id); return r ? r : null; }); /** Return a connection role by its name. */ this.getConnectionRoleByName = (name) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getConnectionRoles(); const r = connectionRolesByName.get(name); return r ? r : null; }); /** Return an array of connection role categories. */ this.getConnectionRoleCategories = () => tslib_1.__awaiter(this, void 0, void 0, function* () { if (connectionRoleCategories.length > 0) return connectionRoleCategories; const r = yield this.client.GetOptionSetUserLabels("connectionrole_category"); connectionRoleCategories = connectionRoleCategories.concat(r); connectionRoleCategories.forEach(crc => connectionRoleCategoriesByName.set(crc.Label, crc)); connectionRoleCategories.forEach(crc => connectionRoleCategoriesByValue.set(crc.Value, crc)); return connectionRoleCategories; }); /** Return a connecton role category by value (Category = OptionSet). */ this.getConnectionRoleCategoryByValue = (value) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getConnectionRoleCategories(); return connectionRoleCategoriesByValue.get(value); }); /** Return a connection role category its name. */ this.getConnectionRoleCategoryByName = (name) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getConnectionRoleCategories(); return connectionRoleCategoriesByName.get(name); }); /** * Obtain the list of allowed object type. Empty means any entity type is allowed. */ this.getAllowedTypeCodesForConnectionRoleId = (roleId) => tslib_1.__awaiter(this, void 0, void 0, function* () { const cacheItem = connectionRoleObjectTypeCodesByRoleId.get(roleId); if (cacheItem !== undefined) return cacheItem; const qopts = { FormattedValues: true, Filter: `_connectionroleid_value eq ${roleId}` }; return this.client.GetList("connectionroleobjecttypecodes", qopts). then(r => { const list = r.List.map(i => (Object.assign({}, i, { entityName: i.associatedobjecttypecode, associatedobjecttypecode_formatted: i["associatedobjecttypecode@OData.Community.Display.V1.FormattedValue"], _connectionroleid_value_formatted: i["_connectionroleid_value@OData.Community.Display.V1.FormattedValue"], roleName: i["_connectionroleid_value@OData.Community.Display.V1.FormattedValue"] }))); if (list.length > 0) { connectionRoleObjectTypeCodes = connectionRoleObjectTypeCodes.concat(list); } connectionRoleObjectTypeCodesByRoleId.set(roleId, list); return list; }); }); /** * Get Option pairs back, Label and Value or an empty list.. * Hackey implementation. Only looks at Attribute.OptionSet not Attribute.GlobalOptionSet. * Not cached yet!!! */ this.getOptionSet = (entityLogicalName, attributeLogicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const emeta = yield this.getMetadata(entityLogicalName); const ameta = yield this.lookupAttribute(entityLogicalName, attributeLogicalName); if (!emeta || !ameta) return []; const qopts = { Select: ["Options"], Path: [ { Property: `Attributes(${ameta.MetadataId})`, Type: "Microsoft.Dynamics.CRM.PicklistAttributeMetadata" }, { Property: "OptionSet", } ] }; const attr = yield this.client.Get("EntityDefinitions", emeta.MetadataId, qopts); const pairs = attr.Options.map(opt => ({ Label: opt.Label.LocalizedLabels[0].Label, Value: opt.Value })); //console.log("attr", attr, pairs) return pairs; }); /** * Return all activity types. How do we filter on non-published kinds? * This may return a surprising number of activities that are used only * in a specialized context so you absolutely will need to filter this list * down for use in your application. */ this.getAllActivityTypes = () => tslib_1.__awaiter(this, void 0, void 0, function* () { const qopts = { Select: ["LogicalName", "ObjectTypeCode", "Description", "DisplayName", "IconSmallName", "IconLargeName", "IconMediumName"], Filter: "IsActivity eq true", }; const l = yield this.client.GetList("EntityDefinitions", qopts).then(r => { return r.List.map((entry) => (Object.assign({}, entry, { // @ts-ignore Description: entry.Description.LocalizedLabels[0].Label, // @ts-ignore DisplayName: entry.DisplayName.LocalizedLabels[0].Label }))); }); return l; }); /** Retur the primary PK logical attribute name for a given entity. */ this.getPk = (entityLogicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { return this.getMetadata(entityLogicalName). then(md => { if (md) return md.PrimaryIdAttribute; return null; }); }); /** Relationships. Returns empty array if not found. */ this.getOneToManyRelationships = (entityLogicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const rels = entityNameToOneToMany.get(entityLogicalName); if (rels) return rels; const m = yield this.getMetadata(entityLogicalName); if (!m) return []; return this.client.Get("EntityDefinitions", m.MetadataId, { Select: ["LogicalName"], Expand: [{ Property: "OneToManyRelationships" }] }). then((r) => { entityNameToOneToMany.set(entityLogicalName, r.OneToManyRelationships); return r.OneToManyRelationships; }); }); /** Get a 1:M relationship to a specific name. Could be multiple, so choose wisely. */ this.getOneToManyRelationshipsTo = (entityLogicalName, toEntityLogicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const rels = yield this.getOneToManyRelationships(entityLogicalName); return rels.filter(r => r.ReferencingEntityNavigationPropertyName === toEntityLogicalName); }); /** Should be only one. null if not found. */ this.getOneToManyRelationshipBySchemaName = (entityLogicalName, schemaName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const rels = yield this.getOneToManyRelationships(entityLogicalName); const x = rels.filter(r => r.SchemaName === schemaName); if (x.length === 1) return x[0]; return null; }); this.getManyToOneRelationships = (entityLogicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const rels = entityNameToManyToOne.get(entityLogicalName); if (rels) return rels; const m = yield this.getMetadata(entityLogicalName); if (!m) return []; return this.client.Get("EntityDefinitions", m.MetadataId, { Select: ["LogicalName"], Expand: [{ Property: "ManyToOneRelationships" }] }). then((r) => { entityNameToManyToOne.set(entityLogicalName, r.ManyToOneRelationships); return r.ManyToOneRelationships; }); }); this.getManyToOneRelationshipsFrom = (entityLogicalName, fromEntityLogicalName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const rels = yield this.getManyToOneRelationships(entityLogicalName); return rels.filter(r => r.ReferencedEntityNavigationPropertyName === fromEntityLogicalName); }); this.getManyToOneRelationshipBySchemaName = (entityLogicalName, schemaName) => tslib_1.__awaiter(this, void 0, void 0, function* () { const rels = yield this.getManyToOneRelationships(entityLogicalName); const x = rels.filter(r => r.SchemaName === schemaName); if (x.length === 1) return x[0]; return null; }); this.client = client; this.lcid = lcid; } getLabel(labels) { return getLabel(labels, this.lcid); } /** Given a numerical code, return the (LogicalName, ObjectTypeCode) pair. */ lookupObjectTypeCodeByCode(code) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.getObjectTypeCodes(); return objectTypeCodesByCode.get(code); }); } } exports.Metadata = Metadata; exports.default = Metadata; //# sourceMappingURL=Metadata.js.map