@aappddeevv/dynamics-client-ui
Version:
## What is it? A library to help you create great dynamics applications.
401 lines • 19.9 kB
JavaScript
"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