UNPKG

@shko.online/componentframework-mock

Version:

Mocking library to help with testing PowerApps Component Framework Components

800 lines (782 loc) 34.5 kB
"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}@OData.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;