@shko.online/componentframework-mock
Version:
Mocking library to help with testing PowerApps Component Framework Components
808 lines (791 loc) • 37.1 kB
JavaScript
/*
Copyright (c) 2022 Betim Beja and Shko Online LLC
Licensed under the MIT license.
*/
import alasql from 'alasql';
import { stub } from 'sinon';
import { AttributeMetadataSQL, EntityMetadataSQL, OptionSetMetadataSQL } from './SQLQueries';
import { getAttributeTypeFromString } from './getAttributeTypeFromString';
import { getSqlTypeForAttribute } from './getSQLTypeForAttribute';
import { AttributeType, OptionSetType } from '../../ComponentFramework-Mock';
import { SavedQueryMetadata } from './PlatformMetadata/SavedQuery.Metadata';
import { UserQueryMetadata } from './PlatformMetadata/UserQuery.Metadata';
export class MetadataDB {
constructor() {
/**
* Setting this to `true` will warn for missing metadata when doing operations.
*/
this._warnMissingInit = void 0;
this.db = void 0;
this._newId = void 0;
this.EntityMetadataSQL = void 0;
this.OptionSetMetadataSQL = void 0;
this.AttributeMetadataSQL = void 0;
this._warnMissingInit = false;
this.db = new alasql.Database();
this.AttributeMetadataSQL = new AttributeMetadataSQL(this.db.exec.bind(this.db));
this.EntityMetadataSQL = new EntityMetadataSQL(this.db.exec.bind(this.db));
this.OptionSetMetadataSQL = new OptionSetMetadataSQL(this.db.exec.bind(this.db));
this._newId = stub();
this._newId.callsFake(() => this.db.exec('SELECT NEWID() as ID')[0]['ID']);
}
createAttribute(entityId, attribute) {
var _attribute$AttributeT;
if (!attribute.MetadataId) attribute.MetadataId = this._newId();
let OptionSetId = undefined;
if (attribute.AttributeType === AttributeType.Picklist) {
const optionsetAttribute = attribute;
if (!optionsetAttribute.OptionSet) {
optionsetAttribute.OptionSet = {
MetadataId: '',
IsCustomOptionSet: false,
Name: optionsetAttribute.LogicalName,
Options: {},
OptionSetType: OptionSetType.Picklist
};
}
if (!optionsetAttribute.OptionSet.MetadataId) {
optionsetAttribute.OptionSet.MetadataId = this._newId();
}
OptionSetId = optionsetAttribute.OptionSet.MetadataId;
this.OptionSetMetadataSQL.AddOptionSetMetadata({
IsCustomOptionSet: optionsetAttribute.OptionSet.IsCustomOptionSet,
LogicalName: optionsetAttribute.OptionSet.Name,
OptionSetId,
OptionSetType: optionsetAttribute.OptionSet.OptionSetType
});
for (let optionValue in optionsetAttribute.OptionSet.Options) {
const option = optionsetAttribute.OptionSet.Options[optionValue];
const optionId = this._newId();
this.OptionSetMetadataSQL.AddOptionMetadata({
OptionId: optionId,
OptionSetId,
Color: option.Color,
Label: option.Label,
Value: option.Value
});
}
} else if (attribute.AttributeType === AttributeType.Boolean) {
const booleanAttribute = attribute;
if (!booleanAttribute.OptionSet) {
booleanAttribute.OptionSet = {
MetadataId: '',
IsCustomOptionSet: false,
Name: booleanAttribute.LogicalName,
OptionSetType: OptionSetType.Boolean,
DisplayName: '',
FalseOption: {
Color: '',
Label: 'No',
Value: 0
},
TrueOption: {
Color: '',
Label: 'Yes',
Value: 1
}
};
}
if (!booleanAttribute.OptionSet.FalseOption) {
booleanAttribute.OptionSet.FalseOption = {
Color: '',
Label: 'No',
Value: 0
};
}
if (!booleanAttribute.OptionSet.TrueOption) {
booleanAttribute.OptionSet.TrueOption = {
Color: '',
Label: 'Yes',
Value: 1
};
}
if (!booleanAttribute.OptionSet.MetadataId) {
booleanAttribute.OptionSet.MetadataId = this._newId();
}
OptionSetId = booleanAttribute.OptionSet.MetadataId;
this.OptionSetMetadataSQL.AddOptionSetMetadata({
IsCustomOptionSet: booleanAttribute.OptionSet.IsCustomOptionSet,
LogicalName: booleanAttribute.OptionSet.Name,
OptionSetId,
OptionSetType: booleanAttribute.OptionSet.OptionSetType
});
let OptionId = this._newId();
let option = booleanAttribute.OptionSet.FalseOption;
this.OptionSetMetadataSQL.AddOptionMetadata({
OptionId,
OptionSetId,
Color: option.Color,
Label: option.Label,
Value: option.Value
});
OptionId = this._newId();
option = booleanAttribute.OptionSet.TrueOption;
this.OptionSetMetadataSQL.AddOptionMetadata({
OptionId,
OptionSetId,
Color: option.Color,
Label: option.Label,
Value: option.Value
});
}
this.AttributeMetadataSQL.AddAttributeMetadata({
EntityId: entityId,
AttributeId: attribute.MetadataId,
LogicalName: attribute.LogicalName,
AttributeType: attribute.AttributeType,
DefaultFormValue: attribute.DefaultFormValue,
OptionSetId,
AttributeOf: attribute.AttributeOf,
AttributeTypeName: ((_attribute$AttributeT = attribute.AttributeTypeName) === null || _attribute$AttributeT === void 0 ? void 0 : _attribute$AttributeT.value) || ''
});
}
/**
* Use this method to initialize the metadata
* @param metadatas
*/
initMetadata(metadatas) {
metadatas.forEach(metadata => this.initSingleTable(metadata));
}
initSingleTable(metadata) {
const entityId = this._newId();
const safeTableName = metadata.LogicalName.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
if (!metadata.Attributes) {
metadata.Attributes = [];
}
let attributes = metadata.Attributes;
attributes.forEach(attribute => {
attribute.AttributeType = getAttributeTypeFromString(attribute.AttributeType);
});
if (!metadata.PrimaryIdAttribute) {
let primaryAttribute = attributes.find(attr => attr.AttributeType === AttributeType.Uniqueidentifier);
if (primaryAttribute) {
metadata.PrimaryIdAttribute = primaryAttribute.LogicalName;
} else {
metadata.PrimaryIdAttribute = safeTableName + 'id';
}
}
if (!metadata.PrimaryNameAttribute) {
let primaryAttribute = attributes.find(attr => attr.AttributeType === AttributeType.EntityName);
if (primaryAttribute) {
metadata.PrimaryNameAttribute = primaryAttribute.LogicalName;
} else {
metadata.PrimaryNameAttribute = 'name';
}
}
if (!attributes.find(attr => attr.LogicalName === metadata.PrimaryIdAttribute)) {
attributes.push({
AttributeType: AttributeType.Uniqueidentifier,
LogicalName: metadata.PrimaryIdAttribute,
IsPrimaryId: true
});
}
const virtualAttributes = [];
attributes.forEach(attr => {
const virtualAttribute = attr.LogicalName + 'name';
if (!(attr.AttributeType === AttributeType.Boolean || attr.AttributeType === AttributeType.Lookup || attr.AttributeType === AttributeType.Picklist) || attributes.some(attrv => attrv.LogicalName === virtualAttribute)) {
return;
}
virtualAttributes.push({
AttributeOf: attr.LogicalName,
AttributeType: AttributeType.Virtual,
LogicalName: virtualAttribute,
SchemaName: (attr.SchemaName || attr.LogicalName) + 'Name'
});
});
metadata.Attributes = attributes.concat(virtualAttributes);
attributes = metadata.Attributes;
if (this.EntityMetadataSQL.SelectTableMetadata(metadata.LogicalName).length == 0) {
this.EntityMetadataSQL.AddEntityMetadata({
EntityId: entityId,
EntitySetName: metadata.EntitySetName,
LogicalName: metadata.LogicalName,
PrimaryIdAttribute: metadata.PrimaryIdAttribute,
PrimaryNameAttribute: metadata.PrimaryNameAttribute,
PrimaryImageAttribute: metadata.PrimaryImageAttribute,
DisplayName: metadata.DisplayName,
DisplayCollectionName: metadata.DisplayCollectionName,
LogicalCollectionName: metadata.LogicalCollectionName,
Description: metadata.Description
});
const columns = [];
attributes.forEach(attribute => {
this.createAttribute(entityId, attribute);
columns.push(' [' + attribute.LogicalName + '] ' + getSqlTypeForAttribute(attribute.AttributeType));
if (attribute.AttributeType === AttributeType.Lookup) {
columns.push(' [' + attribute.LogicalName + 'type] string');
columns.push(' [' + attribute.LogicalName + 'navigation] string');
}
});
// Create Table
const createTableQuery = `CREATE TABLE ${safeTableName} (${columns.join(',')})`;
this.db.exec(createTableQuery);
} else {
attributes.forEach(attribute => {
this.upsertAttributeMetadata(metadata.LogicalName, attribute);
});
}
}
/**
* Get the metadata for a specific attribute
* @param entity The target table
* @param attribute The target attribute
*/
getAttributeMetadata(entity, attribute) {
const tableMetadataDB = this.EntityMetadataSQL.SelectTableMetadata(entity);
if (!tableMetadataDB || tableMetadataDB.length === 0) {
if (this._warnMissingInit) {
console.warn(`Missing init for ${entity}`);
}
return;
}
const tableMetadata = {
LogicalName: tableMetadataDB[0].LogicalName,
EntitySetName: tableMetadataDB[0].EntitySetName,
Attributes: [],
PrimaryIdAttribute: tableMetadataDB[0].PrimaryIdAttribute,
PrimaryNameAttribute: tableMetadataDB[0].PrimaryNameAttribute,
PrimaryImageAttribute: tableMetadataDB[0].PrimaryImageAttribute,
DisplayName: tableMetadataDB[0].DisplayName,
DisplayCollectionName: tableMetadataDB[0].DisplayCollectionName,
LogicalCollectionName: tableMetadataDB[0].LogicalCollectionName,
Description: tableMetadataDB[0].Description
};
var resultDB = this.AttributeMetadataSQL.SelectAttributeMetadata(attribute, entity);
if (!resultDB || resultDB.length === 0) {
if (this._warnMissingInit) {
console.warn(`Missing init for ${entity} ${attribute}`);
}
return null;
}
return this.mapAttributeFromAttributeDB(tableMetadata, resultDB[0]);
}
/**
* Get the metadata for a table
* @param entity The target table
*/
getTableMetadataByEntitySet(entitySetName) {
const tableMetadataDB = this.EntityMetadataSQL.SelectTableMetadataByEntitySet(entitySetName);
if (!tableMetadataDB || tableMetadataDB.length === 0) {
if (this._warnMissingInit) {
console.warn(`Missing init for entitySet ${entitySetName}`);
}
return;
}
return this.getTableMetadata(tableMetadataDB[0].LogicalName);
}
/**
* Get the metadata for a table
* @param entity The target table
*/
getTableMetadata(entity) {
const tableMetadataDB = this.EntityMetadataSQL.SelectTableMetadata(entity);
if (!tableMetadataDB || tableMetadataDB.length === 0) {
if (this._warnMissingInit) {
console.warn(`Missing init for ${entity}`);
}
return;
}
const tableMetadata = {
LogicalName: tableMetadataDB[0].LogicalName,
EntitySetName: tableMetadataDB[0].EntitySetName,
Attributes: [],
PrimaryIdAttribute: tableMetadataDB[0].PrimaryIdAttribute,
PrimaryNameAttribute: tableMetadataDB[0].PrimaryNameAttribute,
PrimaryImageAttribute: tableMetadataDB[0].PrimaryImageAttribute,
DisplayName: tableMetadataDB[0].DisplayName,
DisplayCollectionName: tableMetadataDB[0].DisplayCollectionName,
LogicalCollectionName: tableMetadataDB[0].LogicalCollectionName,
Description: tableMetadataDB[0].Description
};
const attributesDB = this.AttributeMetadataSQL.SelectAttributeMetadataForTable(entity);
attributesDB.forEach(attributeDB => {
var _tableMetadata$Attrib;
const attribute = this.mapAttributeFromAttributeDB(tableMetadata, attributeDB);
(_tableMetadata$Attrib = tableMetadata.Attributes) === null || _tableMetadata$Attrib === void 0 || _tableMetadata$Attrib.push(attribute);
});
return tableMetadata;
}
mapAttributeFromAttributeDB(tableMetadata, attributeDB) {
const attribute = {
AttributeType: attributeDB.AttributeType,
EntityLogicalName: tableMetadata.LogicalName,
MetadataId: attributeDB.AttributeId,
LogicalName: attributeDB.LogicalName,
AttributeOf: attributeDB.AttributeOf,
AttributeTypeName: {
value: attributeDB.AttributeTypeName
},
IsPrimaryId: attributeDB.LogicalName === tableMetadata.PrimaryIdAttribute,
IsPrimaryName: attributeDB.LogicalName === tableMetadata.PrimaryNameAttribute
};
if (attribute.AttributeType === AttributeType.Picklist) {
const optionsetDB = this.OptionSetMetadataSQL.SelectOptionSetMetadata(attributeDB.OptionSetId || '');
attribute.DefaultFormValue = attributeDB.DefaultFormValue;
attribute.OptionSet = {
IsCustomOptionSet: optionsetDB[0].IsCustomOptionSet,
MetadataId: optionsetDB[0].OptionSetId,
Name: optionsetDB[0].LogicalName,
OptionSetType: optionsetDB[0].OptionSetType,
Options: {}
};
const optionsDB = this.OptionSetMetadataSQL.SelectOptionSetOptionMetadata(attributeDB.OptionSetId || '');
optionsDB.forEach(option => {
attribute.OptionSet.Options[option.Value] = {
Label: option.Label,
Value: option.Value,
Color: option.Color
};
});
} else if (attribute.AttributeType === AttributeType.Boolean) {
const optionsetDB = this.OptionSetMetadataSQL.SelectOptionSetMetadata(attributeDB.OptionSetId || '');
attribute.OptionSet = {
IsCustomOptionSet: optionsetDB[0].IsCustomOptionSet,
MetadataId: optionsetDB[0].OptionSetId,
Name: optionsetDB[0].LogicalName,
OptionSetType: optionsetDB[0].OptionSetType,
DisplayName: '',
FalseOption: {
Color: '',
Label: 'No',
Value: 0
},
TrueOption: {
Color: '',
Label: 'Yes',
Value: 1
}
};
const optionsDB = this.OptionSetMetadataSQL.SelectOptionSetOptionMetadata(attributeDB.OptionSetId || '');
optionsDB.forEach(option => {
attribute.OptionSet[option.Value ? 'TrueOption' : 'FalseOption'] = {
Label: option.Label,
Value: option.Value,
Color: option.Color
};
});
}
return attribute;
}
/**
* Update or Insert the metadata for a specific column of a table
* @param entity The target table
* @param attributeMetadata The target column
* @returns
*/
upsertAttributeMetadata(entity, attributeMetadata) {
const tableDB = this.EntityMetadataSQL.SelectTableMetadata(entity);
if (!tableDB) {
console.warn(`Could not find metadata for ${entity}`);
return;
}
const safeTableName = tableDB[0].LogicalName.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
let attributeDB = this.AttributeMetadataSQL.SelectAttributeMetadata(attributeMetadata.LogicalName, entity);
if (attributeMetadata.MetadataId) {
attributeDB = attributeDB.concat(this.AttributeMetadataSQL.SelectAttributeMetadataById(attributeMetadata.MetadataId));
}
if (attributeDB && attributeDB.length > 0) {
attributeDB.forEach(attribute => {
if ((attributeMetadata.AttributeType === AttributeType.Boolean || attributeMetadata.AttributeType === AttributeType.Lookup || attributeMetadata.AttributeType === AttributeType.Picklist || attributeMetadata.AttributeType === AttributeType.State || attributeMetadata.AttributeType == AttributeType.Status) && attribute.OptionSetId) {
this.db.exec('DELETE FROM Metadata__Optionset WHERE OptionSetId = ?', [attribute.OptionSetId]);
this.db.exec('DELETE FROM Metadata__Optionset_Option WHERE OptionSetId = ?', [attribute.OptionSetId]);
}
if (attribute.AttributeId) this.db.exec('DELETE FROM Metadata__Attribute WHERE AttributeId = ?', [attribute.AttributeId]);
});
}
const virtualAttribute = attributeMetadata.LogicalName + 'name';
this.createAttribute(tableDB[0].EntityId, attributeMetadata);
if (attributeMetadata.AttributeType === AttributeType.Boolean || attributeMetadata.AttributeType === AttributeType.Lookup || attributeMetadata.AttributeType === AttributeType.Picklist || attributeMetadata.AttributeType === AttributeType.State || attributeMetadata.AttributeType == AttributeType.Status) {
let optionsetMetadata = attributeMetadata;
if (optionsetMetadata.OptionSet && optionsetMetadata.OptionSet.MetadataId) {
this.db.exec('DELETE FROM Metadata__Optionset WHERE OptionSetId = ?', [optionsetMetadata.OptionSet.MetadataId]);
this.db.exec('DELETE FROM Metadata__Optionset_Option WHERE OptionSetId = ?', [optionsetMetadata.OptionSet.MetadataId]);
}
this.createAttribute(tableDB[0].EntityId, {
AttributeOf: attributeMetadata.LogicalName,
AttributeType: AttributeType.Virtual,
LogicalName: virtualAttribute,
SchemaName: (attributeMetadata.SchemaName || attributeMetadata.LogicalName) + 'Name'
});
if (!(attributeDB && attributeDB.length > 0)) this.db.exec('ALTER TABLE ' + safeTableName + ' ADD COLUMN [' + virtualAttribute + '] string');
if (optionsetMetadata.OptionSet) {
this.OptionSetMetadataSQL.AddOptionSetMetadata({
IsCustomOptionSet: optionsetMetadata.OptionSet.IsCustomOptionSet,
LogicalName: optionsetMetadata.OptionSet.Name,
OptionSetId: optionsetMetadata.OptionSet.MetadataId,
OptionSetType: optionsetMetadata.OptionSet.OptionSetType
});
for (let optionValue in optionsetMetadata.OptionSet.Options) {
const option = optionsetMetadata.OptionSet.Options[optionValue];
const optionId = this._newId();
this.OptionSetMetadataSQL.AddOptionMetadata({
OptionId: optionId,
OptionSetId: optionsetMetadata.OptionSet.MetadataId,
Color: option.Color,
Label: option.Label,
Value: option.Value
});
}
}
}
if (!(attributeDB && attributeDB.length > 0)) {
this.db.exec('ALTER TABLE ' + safeTableName + ' ADD COLUMN [' + attributeMetadata.LogicalName + '] ' + getSqlTypeForAttribute(attributeMetadata.AttributeType));
if (attributeMetadata.AttributeType === AttributeType.Lookup) {
this.db.exec('ALTER TABLE ' + safeTableName + ' ADD COLUMN [' + attributeMetadata.LogicalName + 'type] string');
this.db.exec('ALTER TABLE ' + safeTableName + ' ADD COLUMN [' + attributeMetadata.LogicalName + 'navigation] string');
}
}
}
/**
* Initialize the in-memory database with the items passed as param
* @param items Object as returned from the Dataverse OData API
*/
initItems(items) {
var _items$OdataContext;
const entitySetName = (_items$OdataContext = items['@odata.context']) === null || _items$OdataContext === void 0 ? void 0 : _items$OdataContext.substring(items['@odata.context'].indexOf('#') + 1);
const tableMetadataDB = this.EntityMetadataSQL.SelectTableMetadataByEntitySet(entitySetName);
if (!tableMetadataDB || tableMetadataDB.length === 0) {
if (this._warnMissingInit) {
console.warn(`Missing init for entitySet ${entitySetName}`);
}
return;
}
const tableMetadata = this.getTableMetadata(tableMetadataDB[0].LogicalName);
items.value.forEach(item => {
if (tableMetadataDB[0].LogicalName === MetadataDB.CanvasLogicalName && this.db.exec('SELECT Count(1) as Records FROM _canvasapp')[0].Records > 0) {
var _tableMetadata$Attrib2;
tableMetadata === null || tableMetadata === void 0 || (_tableMetadata$Attrib2 = tableMetadata.Attributes) === null || _tableMetadata$Attrib2 === void 0 || _tableMetadata$Attrib2.forEach(attribute => {
if (attribute.LogicalName in item) {
this.UpdateValue(item[attribute.LogicalName], MetadataDB.CanvasLogicalName, attribute.LogicalName);
}
});
} else {
this.AddRow(tableMetadataDB[0].LogicalName, item, tableMetadata);
}
});
}
/**
* Add a new row to the table
* @param entity The target table
* @param item The data
* @param tableMetadata The table metadata
*/
AddRow(entity, item, tableMetadata) {
var _tableMetadata$Attrib3;
if (!tableMetadata) {
tableMetadata = this.getTableMetadata(entity);
}
if (!tableMetadata) {
if (entity === 'savedquery') {
tableMetadata = SavedQueryMetadata;
this.initMetadata([tableMetadata]);
} else if (entity === 'userquery') {
tableMetadata = UserQueryMetadata;
this.initMetadata([tableMetadata]);
} else {
if (this._warnMissingInit) {
console.warn(`Missing init for entitySet ${entity}`);
}
return;
}
}
const safeTableName = tableMetadata.LogicalName.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
const params = [];
const columns = [];
let newID = null;
(_tableMetadata$Attrib3 = tableMetadata.Attributes) === null || _tableMetadata$Attrib3 === void 0 || _tableMetadata$Attrib3.forEach(attribute => {
const key = attribute.LogicalName;
if (attribute.AttributeType === AttributeType.Uniqueidentifier) {
var _tableMetadata, _tableMetadata2;
let value = item[attribute.LogicalName];
if (!value && ((_tableMetadata = tableMetadata) === null || _tableMetadata === void 0 ? void 0 : _tableMetadata.PrimaryIdAttribute) === (attribute === null || attribute === void 0 ? void 0 : attribute.LogicalName)) {
value = this._newId();
}
if (((_tableMetadata2 = tableMetadata) === null || _tableMetadata2 === void 0 ? void 0 : _tableMetadata2.PrimaryIdAttribute) === (attribute === null || attribute === void 0 ? void 0 : attribute.LogicalName)) {
newID = value;
}
params.push(value);
} else if (attribute.AttributeType === AttributeType.Lookup) {
var _tableMetadata3;
const lookupNameAttribute = (_tableMetadata3 = tableMetadata) === null || _tableMetadata3 === void 0 || (_tableMetadata3 = _tableMetadata3.Attributes) === null || _tableMetadata3 === void 0 ? void 0 : _tableMetadata3.find(attr => attr.AttributeOf === attribute.LogicalName && attr.LogicalName !== attribute.LogicalName + 'yominame');
if (typeof item[key] === 'object') {
const lookup = item[key];
params.push(lookup === null || lookup === void 0 ? void 0 : lookup.name);
columns.push('[' + (lookupNameAttribute === null || lookupNameAttribute === void 0 ? void 0 : lookupNameAttribute.LogicalName) + ']');
params.push(lookup === null || lookup === void 0 ? void 0 : lookup.entityType);
columns.push('[' + key + 'type]');
params.push(lookup === null || lookup === void 0 ? void 0 : lookup.id);
} else {
params.push(item[`_${key}_value@OData.Community.Display.V1.FormattedValue`]);
columns.push('[' + (lookupNameAttribute === null || lookupNameAttribute === void 0 ? void 0 : lookupNameAttribute.LogicalName) + ']');
params.push(item[`_${key}_value@Microsoft.Dynamics.CRM.lookuplogicalname`]);
columns.push('[' + key + 'type]');
params.push(item[`_${key}_value@Microsoft.Dynamics.CRM.associatednavigationproperty`]);
columns.push('[' + key + 'navigation]');
params.push(item[`_${key}_value`]);
}
} else if (attribute.AttributeOf && attribute.LogicalName === attribute.AttributeOf + 'name') {
var _tableMetadata4;
const parentAttribute = (_tableMetadata4 = tableMetadata) === null || _tableMetadata4 === void 0 || (_tableMetadata4 = _tableMetadata4.Attributes) === null || _tableMetadata4 === void 0 ? void 0 : _tableMetadata4.find(attr => attr.LogicalName === attribute.AttributeOf);
if ((parentAttribute === null || parentAttribute === void 0 ? void 0 : parentAttribute.AttributeType) === AttributeType.Lookup) {
return;
}
params.push(item[`${attribute.AttributeOf}.Community.Display.V1.FormattedValue`]);
}
// `_${key}_value` in item) {
// row[key] = item[`_${key}_value`]
// ? ({
// entityType: item[`_${key}_value@Microsoft.Dynamics.CRM.lookuplogicalname`],
// id: item[`_${key}_value`],
// name: item[`_${key}_value@OData.Community.Display.V1.FormattedValue`],
// } as ComponentFramework.LookupValue)
// : null;
// if (`_${key}_value@Microsoft.Dynamics.CRM.associatednavigationproperty` in item)
// row[`_${key}_value@Microsoft.Dynamics.CRM.associatednavigationproperty`] =
// item[`_${key}_value@Microsoft.Dynamics.CRM.associatednavigationproperty`]; // Temporary hack
else if (key in item) {
let value = attribute.AttributeType === AttributeType.DateTime && typeof item[key] === 'string' ? new Date(item[key]) : item[key];
if (value === null || value === undefined) {
return;
}
params.push(value);
} else {
return;
}
columns.push('[' + key + ']');
});
const insertSql = `INSERT INTO ${safeTableName} (${columns.join(',')}) VALUES (${params.map(p => '?').join(',')})`;
this.db.exec(insertSql, params);
return newID;
}
/**
* Same as init items but for Pseudo-Table #!CanvasApp
* @param items
*/
initCanvasItems(items) {
this.initItems({
'@odata.context': '#!CanvasApp',
value: items
});
}
/**
* Get a row from the table and the relative metadata
* @param entity The target table
* @param id The target row
* @returns
*/
GetRow(entity, id) {
var _entityMetadata$Attri, _entityMetadata$Attri2;
const entityMetadata = this.getTableMetadata(entity);
if (!entityMetadata) return {
row: null,
entityMetadata
};
const safeTableName = entity.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
let selectQuery = 'SELECT ' + (entityMetadata && entityMetadata.Attributes ? entityMetadata === null || entityMetadata === void 0 || (_entityMetadata$Attri = entityMetadata.Attributes) === null || _entityMetadata$Attri === void 0 ? void 0 : _entityMetadata$Attri.map(attr => attr.AttributeType === AttributeType.Lookup ? '[' + attr.LogicalName + '],[' + attr.LogicalName + 'type],[' + attr.LogicalName + 'navigation]' : '[' + attr.LogicalName + ']').join(',') : '*') + ' FROM ' + safeTableName;
if (id) {
selectQuery += ' WHERE [' + ((entityMetadata === null || entityMetadata === void 0 ? void 0 : entityMetadata.PrimaryIdAttribute) || 'id') + '] = ?';
}
const rowDB = this.db.exec(selectQuery, [id]);
if (!rowDB || rowDB.length == 0) {
console.warn('could not find row');
return {
row: null,
entityMetadata
};
}
const row = rowDB[0];
entityMetadata === null || entityMetadata === void 0 || (_entityMetadata$Attri2 = entityMetadata.Attributes) === null || _entityMetadata$Attri2 === void 0 || _entityMetadata$Attri2.forEach(attr => {
if (attr.AttributeType === AttributeType.Lookup) {
if (row[attr.LogicalName]) {
var _entityMetadata$Attri3;
const nameAttribute = (entityMetadata === null || entityMetadata === void 0 || (_entityMetadata$Attri3 = entityMetadata.Attributes) === null || _entityMetadata$Attri3 === void 0 || (_entityMetadata$Attri3 = _entityMetadata$Attri3.find(attrName => attrName.AttributeOf === attr.LogicalName)) === null || _entityMetadata$Attri3 === void 0 ? void 0 : _entityMetadata$Attri3.LogicalName) || attr.LogicalName + 'name';
row[attr.LogicalName] = {
id: row[attr.LogicalName],
entityType: row[attr.LogicalName + 'type'],
name: row[nameAttribute]
};
delete row[attr.LogicalName + 'type'];
delete row[nameAttribute];
}
} else if (attr.AttributeType === AttributeType.Picklist) {
if (typeof row[attr.LogicalName] === 'string') {
row[attr.LogicalName] = JSON.parse(row[attr.LogicalName]);
}
}
});
return {
row,
entityMetadata
};
}
/**
* Delete a row from the in-memory database
* @param entity The target table
* @param id The target row
*/
RemoveRow(entity, id) {
const entityMetadata = this.getTableMetadata(entity);
const safeTableName = entity.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
const selectQuery = 'DELETE FROM ' + safeTableName + ' WHERE [' + ((entityMetadata === null || entityMetadata === void 0 ? void 0 : entityMetadata.PrimaryIdAttribute) || 'id') + '] = ?';
this.db.exec(selectQuery, [id]);
}
/**
* Gets a row from a table and the associated metadata
* @param entity The target table
* @param id The target row
* @returns
*/
GetRowForAPI(entity, id) {
const result = this.GetRow(entity, id);
if (!result.entityMetadata) {
return result;
}
if (result.entityMetadata && result.entityMetadata.Attributes && result.row) {
result.entityMetadata.Attributes.filter(attr => attr.AttributeType === AttributeType.Lookup).forEach(lookupAttribute => {
const key = lookupAttribute.LogicalName;
const lookupValue = result.row[key];
delete result.row[key];
result.row[`_${key}_value`] = lookupValue;
});
}
return result;
}
/**
* Get a coulmn value and metadata
* @param entity The target table
* @param attributeName The target column
* @param rowid The target row
* @returns
*/
GetValueAndMetadata(entity, attributeName, rowid) {
var _result$entityMetadat, _result$row;
const result = this.GetRow(entity, rowid);
const attributeMetadata = (_result$entityMetadat = result.entityMetadata) === null || _result$entityMetadat === void 0 || (_result$entityMetadat = _result$entityMetadat.Attributes) === null || _result$entityMetadat === void 0 ? void 0 : _result$entityMetadat.find(attribute => attribute.LogicalName === attributeName);
const value = (_result$row = result.row) === null || _result$row === void 0 ? void 0 : _result$row[attributeName];
return {
value,
attributeMetadata
};
}
/**
* Get all the rows for a table
* @param entity The target table
* @returns
*/
GetAllRows(entity) {
const entityMetadata = this.getTableMetadata(entity);
const safeTableName = entity.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
const rows = this.db.exec('SELECT * FROM ' + safeTableName);
return {
rows,
entityMetadata
};
}
/**
* Update the value stored in a specific column in the in-memory database
* @param value The new value
* @param entity The target table
* @param attribute The target column
* @param row The target row
*/
UpdateValue(value, entity, attribute, rowId) {
var _tableMetadata$Attrib4;
const tableMetadata = this.getTableMetadata(entity);
if (!tableMetadata) {
return;
}
const safeTableName = tableMetadata.LogicalName.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
const attributeMetadata = (_tableMetadata$Attrib4 = tableMetadata.Attributes) === null || _tableMetadata$Attrib4 === void 0 ? void 0 : _tableMetadata$Attrib4.find(attr => attr.LogicalName === attribute);
if (!attributeMetadata) {
if (this._warnMissingInit) {
console.warn(`Missing init for attribute '${attribute}' of table '${entity}'`);
}
return;
}
const statements = [];
const params = [];
if (attributeMetadata.AttributeType === AttributeType.Lookup) {
var _tableMetadata$Attrib5;
const lookupNameAttribute = ((_tableMetadata$Attrib5 = tableMetadata.Attributes) === null || _tableMetadata$Attrib5 === void 0 || (_tableMetadata$Attrib5 = _tableMetadata$Attrib5.find(attr => attr.AttributeOf === attributeMetadata.LogicalName)) === null || _tableMetadata$Attrib5 === void 0 ? void 0 : _tableMetadata$Attrib5.LogicalName) || attributeMetadata.LogicalName + 'name';
const lookupValue = value;
statements.push(`
SET [${attributeMetadata.LogicalName}] = ?,
[${attributeMetadata.LogicalName}type] = ?,
[${lookupNameAttribute}] = ? `);
params.push(lookupValue && lookupValue.id ? lookupValue.id : null);
params.push(lookupValue && lookupValue.id ? lookupValue.entityType : null);
params.push(lookupValue && lookupValue.id ? lookupValue.name : null);
} else if (attributeMetadata.AttributeType === AttributeType.Picklist) {
var _tableMetadata$Attrib6;
const picklistNameAttribute = (tableMetadata === null || tableMetadata === void 0 || (_tableMetadata$Attrib6 = tableMetadata.Attributes) === null || _tableMetadata$Attrib6 === void 0 || (_tableMetadata$Attrib6 = _tableMetadata$Attrib6.find(attr => attr.AttributeOf === attributeMetadata.LogicalName)) === null || _tableMetadata$Attrib6 === void 0 ? void 0 : _tableMetadata$Attrib6.LogicalName) || attributeMetadata.LogicalName + 'name';
statements.push(`
SET [${attributeMetadata.LogicalName}] = ?,
[${picklistNameAttribute}] = ? `);
if (typeof value === 'object' && value instanceof Array) {
// multiselect
params.push(JSON.stringify(value));
} else {
var _OptionSet$Options;
params.push(value);
const label = attributeMetadata === null || attributeMetadata === void 0 || (_OptionSet$Options = attributeMetadata.OptionSet.Options[value]) === null || _OptionSet$Options === void 0 ? void 0 : _OptionSet$Options.Label;
if (!label) {
console.warn(`Picklist column ${attributeMetadata === null || attributeMetadata === void 0 ? void 0 : attributeMetadata.LogicalName} is missing option ${value}}`);
}
params.push(label || '');
}
} else {
statements.push(` SET [${attributeMetadata.LogicalName}] = ? `);
params.push(value);
}
if (rowId) {
statements.push(` WHERE ${tableMetadata.PrimaryIdAttribute} = ? `);
params.push(rowId);
}
this.db.exec(`UPDATE ${safeTableName} ${statements.join(' ')}`, params);
}
SelectUsingFetchXml(fetchXml) {
var fetchNode = fetchXml.documentElement;
var entityNode = fetchNode.firstElementChild;
if (!entityNode) {
throw new Error('Fetch does not contain the entity node');
}
var attributesX = entityNode.getElementsByTagName('attribute');
const attributes = [];
if (fetchNode.getAttribute('aggregate') === 'true') {
for (let i = 0; i < attributesX.length; i++) {
const attribute = attributesX[i].getAttribute('name');
const aggregate = attributesX[i].getAttribute('aggregate');
const alias = attributesX[i].getAttribute('alias');
attributes.push(`${aggregate}(${attribute}) as [${alias}]`);
}
} else {
for (let i = 0; i < attributesX.length; i++) {
attributes.push(attributesX[i].getAttribute('name'));
}
}
return this.db.exec(`SELECT ${attributes.join(',')} FROM ${entityNode.getAttribute('name')}`);
}
SelectUsingOData(tableMetadata, query) {
const safeTableName = tableMetadata.LogicalName.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
const attributes = [];
if (query.$select && tableMetadata.Attributes) {
tableMetadata.Attributes.forEach(attribute => {
var _query$$select, _query$$select2;
if (attribute.AttributeType === AttributeType.Lookup && (_query$$select = query.$select) !== null && _query$$select !== void 0 && _query$$select.find(a => a === `_${attribute.LogicalName}_value`)) {
attributes.push(attribute.LogicalName);
attributes.push(attribute.LogicalName + 'name');
attributes.push(attribute.LogicalName + 'type');
} else if ((_query$$select2 = query.$select) !== null && _query$$select2 !== void 0 && _query$$select2.find(a => a === attribute.LogicalName) || attribute.LogicalName === tableMetadata.PrimaryIdAttribute) {
attributes.push(attribute.LogicalName);
if (attribute.AttributeType === AttributeType.Boolean || attribute.AttributeType === AttributeType.Picklist || attribute.AttributeType === AttributeType.State || attribute.AttributeType === AttributeType.Status) {
attributes.push(attribute.LogicalName + 'name');
}
}
});
}
return this.db.exec(`SELECT ${attributes.join(',')} FROM ${safeTableName}`);
}
}
MetadataDB.CanvasLogicalName = '!CanvasApp';
MetadataDB.Collisions = 0;