@shko.online/componentframework-mock
Version:
Mocking library to help with testing PowerApps Component Framework Components
800 lines (782 loc) • 34.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MetadataDB = void 0;
var _alasql = _interopRequireDefault(require("alasql"));
var _sinon = require("sinon");
var _SQLQueries = require("./SQLQueries");
var _getAttributeTypeFromString = require("./getAttributeTypeFromString");
var _getSQLTypeForAttribute = require("./getSQLTypeForAttribute");
var _ComponentFrameworkMock = require("../../ComponentFramework-Mock");
var _SavedQuery = require("./PlatformMetadata/SavedQuery.Metadata");
var _UserQuery = require("./PlatformMetadata/UserQuery.Metadata");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
Copyright (c) 2022 Betim Beja and Shko Online LLC
Licensed under the MIT license.
*/
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.default.Database();
this.AttributeMetadataSQL = new _SQLQueries.AttributeMetadataSQL(this.db.exec.bind(this.db));
this.EntityMetadataSQL = new _SQLQueries.EntityMetadataSQL(this.db.exec.bind(this.db));
this.OptionSetMetadataSQL = new _SQLQueries.OptionSetMetadataSQL(this.db.exec.bind(this.db));
this._newId = (0, _sinon.stub)();
this._newId.callsFake(() => this.db.exec('SELECT NEWID() as ID')[0]['ID']);
}
createAttribute(entityId, attribute) {
if (!attribute.MetadataId) attribute.MetadataId = this._newId();
let OptionSetId = undefined;
if (attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Picklist) {
const optionsetAttribute = attribute;
if (!optionsetAttribute.OptionSet) {
optionsetAttribute.OptionSet = {
MetadataId: '',
IsCustomOptionSet: false,
Name: optionsetAttribute.LogicalName,
Options: {},
OptionSetType: _ComponentFrameworkMock.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 === _ComponentFrameworkMock.AttributeType.Boolean) {
const booleanAttribute = attribute;
if (!booleanAttribute.OptionSet) {
booleanAttribute.OptionSet = {
MetadataId: '',
IsCustomOptionSet: false,
Name: booleanAttribute.LogicalName,
OptionSetType: _ComponentFrameworkMock.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.AttributeTypeName?.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 = (0, _getAttributeTypeFromString.getAttributeTypeFromString)(attribute.AttributeType);
});
if (!metadata.PrimaryIdAttribute) {
let primaryAttribute = attributes.find(attr => attr.AttributeType === _ComponentFrameworkMock.AttributeType.Uniqueidentifier);
if (primaryAttribute) {
metadata.PrimaryIdAttribute = primaryAttribute.LogicalName;
} else {
metadata.PrimaryIdAttribute = safeTableName + 'id';
}
}
if (!metadata.PrimaryNameAttribute) {
let primaryAttribute = attributes.find(attr => attr.AttributeType === _ComponentFrameworkMock.AttributeType.EntityName);
if (primaryAttribute) {
metadata.PrimaryNameAttribute = primaryAttribute.LogicalName;
} else {
metadata.PrimaryNameAttribute = 'name';
}
}
if (!attributes.find(attr => attr.LogicalName === metadata.PrimaryIdAttribute)) {
attributes.push({
AttributeType: _ComponentFrameworkMock.AttributeType.Uniqueidentifier,
LogicalName: metadata.PrimaryIdAttribute,
IsPrimaryId: true
});
}
const virtualAttributes = [];
attributes.forEach(attr => {
const virtualAttribute = attr.LogicalName + 'name';
if (!(attr.AttributeType === _ComponentFrameworkMock.AttributeType.Boolean || attr.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup || attr.AttributeType === _ComponentFrameworkMock.AttributeType.Picklist) || attributes.some(attrv => attrv.LogicalName === virtualAttribute)) {
return;
}
virtualAttributes.push({
AttributeOf: attr.LogicalName,
AttributeType: _ComponentFrameworkMock.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 + '] ' + (0, _getSQLTypeForAttribute.getSqlTypeForAttribute)(attribute.AttributeType));
if (attribute.AttributeType === _ComponentFrameworkMock.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 => {
const attribute = this.mapAttributeFromAttributeDB(tableMetadata, attributeDB);
tableMetadata.Attributes?.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 === _ComponentFrameworkMock.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 === _ComponentFrameworkMock.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 === _ComponentFrameworkMock.AttributeType.Boolean || attributeMetadata.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup || attributeMetadata.AttributeType === _ComponentFrameworkMock.AttributeType.Picklist || attributeMetadata.AttributeType === _ComponentFrameworkMock.AttributeType.State || attributeMetadata.AttributeType == _ComponentFrameworkMock.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 === _ComponentFrameworkMock.AttributeType.Boolean || attributeMetadata.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup || attributeMetadata.AttributeType === _ComponentFrameworkMock.AttributeType.Picklist || attributeMetadata.AttributeType === _ComponentFrameworkMock.AttributeType.State || attributeMetadata.AttributeType == _ComponentFrameworkMock.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: _ComponentFrameworkMock.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 + '] ' + (0, _getSQLTypeForAttribute.getSqlTypeForAttribute)(attributeMetadata.AttributeType));
if (attributeMetadata.AttributeType === _ComponentFrameworkMock.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) {
const entitySetName = items['@odata.context']?.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) {
tableMetadata?.Attributes?.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) {
if (!tableMetadata) {
tableMetadata = this.getTableMetadata(entity);
}
if (!tableMetadata) {
if (entity === 'savedquery') {
tableMetadata = _SavedQuery.SavedQueryMetadata;
this.initMetadata([tableMetadata]);
} else if (entity === 'userquery') {
tableMetadata = _UserQuery.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.Attributes?.forEach(attribute => {
const key = attribute.LogicalName;
if (attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Uniqueidentifier) {
let value = item[attribute.LogicalName];
if (!value && tableMetadata?.PrimaryIdAttribute === attribute?.LogicalName) {
value = this._newId();
}
if (tableMetadata?.PrimaryIdAttribute === attribute?.LogicalName) {
newID = value;
}
params.push(value);
} else if (attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup) {
const lookupNameAttribute = tableMetadata?.Attributes?.find(attr => attr.AttributeOf === attribute.LogicalName && attr.LogicalName !== attribute.LogicalName + 'yominame');
if (typeof item[key] === 'object') {
const lookup = item[key];
params.push(lookup?.name);
columns.push('[' + lookupNameAttribute?.LogicalName + ']');
params.push(lookup?.entityType);
columns.push('[' + key + 'type]');
params.push(lookup?.id);
} else {
params.push(item[`_${key}_value@OData.Community.Display.V1.FormattedValue`]);
columns.push('[' + 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') {
const parentAttribute = tableMetadata?.Attributes?.find(attr => attr.LogicalName === attribute.AttributeOf);
if (parentAttribute?.AttributeType === _ComponentFrameworkMock.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 === _ComponentFrameworkMock.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) {
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?.Attributes?.map(attr => attr.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup ? '[' + attr.LogicalName + '],[' + attr.LogicalName + 'type],[' + attr.LogicalName + 'navigation]' : '[' + attr.LogicalName + ']').join(',') : '*') + ' FROM ' + safeTableName;
if (id) {
selectQuery += ' WHERE [' + (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?.Attributes?.forEach(attr => {
if (attr.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup) {
if (row[attr.LogicalName]) {
const nameAttribute = entityMetadata?.Attributes?.find(attrName => attrName.AttributeOf === attr.LogicalName)?.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 === _ComponentFrameworkMock.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?.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 === _ComponentFrameworkMock.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) {
const result = this.GetRow(entity, rowid);
const attributeMetadata = result.entityMetadata?.Attributes?.find(attribute => attribute.LogicalName === attributeName);
const value = 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) {
const tableMetadata = this.getTableMetadata(entity);
if (!tableMetadata) {
return;
}
const safeTableName = tableMetadata.LogicalName.toLowerCase().replace(/\!/g, '_').replace(/\@/g, '_');
const attributeMetadata = tableMetadata.Attributes?.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 === _ComponentFrameworkMock.AttributeType.Lookup) {
const lookupNameAttribute = tableMetadata.Attributes?.find(attr => attr.AttributeOf === attributeMetadata.LogicalName)?.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 === _ComponentFrameworkMock.AttributeType.Picklist) {
const picklistNameAttribute = tableMetadata?.Attributes?.find(attr => attr.AttributeOf === attributeMetadata.LogicalName)?.LogicalName || attributeMetadata.LogicalName + 'name';
statements.push(`
SET [${attributeMetadata.LogicalName}] = ?,
[${picklistNameAttribute}] = ? `);
if (typeof value === 'object' && value instanceof Array) {
// multiselect
params.push(JSON.stringify(value));
} else {
params.push(value);
const label = attributeMetadata?.OptionSet.Options[value]?.Label;
if (!label) {
console.warn(`Picklist column ${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 => {
if (attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Lookup && 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.$select?.find(a => a === attribute.LogicalName) || attribute.LogicalName === tableMetadata.PrimaryIdAttribute) {
attributes.push(attribute.LogicalName);
if (attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Boolean || attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Picklist || attribute.AttributeType === _ComponentFrameworkMock.AttributeType.State || attribute.AttributeType === _ComponentFrameworkMock.AttributeType.Status) {
attributes.push(attribute.LogicalName + 'name');
}
}
});
}
return this.db.exec(`SELECT ${attributes.join(',')} FROM ${safeTableName}`);
}
}
exports.MetadataDB = MetadataDB;
MetadataDB.CanvasLogicalName = '!CanvasApp';
MetadataDB.Collisions = 0;