Skip to content

Commit

Permalink
Merge branch 'release/5.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nsteenbeek committed Aug 30, 2021
2 parents cad19ea + 404c5c6 commit 17ef467
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 76 deletions.
2 changes: 2 additions & 0 deletions bin/OptionSets/OptionSets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* This is a generated file, please regenerate and do not modify */

2 changes: 1 addition & 1 deletion bin/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hso/d365-cli",
"version": "5.4.3",
"version": "5.5.0",
"author": "HSO Innovation <[email protected]> (http://www.hso.com)",
"description": "HSO D365 Command Line Interface",
"repository": {
Expand Down
24 changes: 13 additions & 11 deletions src/generator/Enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,24 @@ export class Enum {
for (const attribute of attributesMetadata) {
const {AttributeType: attributeType, LogicalName: logicalName, SchemaName: schemaName, AttributeTypeName: attributeTypeName} = attribute;
if (attributeType === 'Picklist' || attributeTypeName.Value === 'MultiSelectPicklistType') {
const pascalSchemaName = Enum.capitalize(schemaName);
enumStrings += `export enum ${pascalSchemaName} {\n`;
let options;
let optionSet;
if (attributeType === 'Picklist') {
options = await NodeApi.getPicklistOptionSet(this.entityLogicalName, logicalName, this.bearer);
optionSet = await NodeApi.getPicklistOptionSet(this.entityLogicalName, logicalName, this.bearer);
} else {
options = await NodeApi.getMultiSelectPicklistAttributeMetadata(this.entityLogicalName, logicalName, this.bearer);
optionSet = await NodeApi.getMultiSelectPicklistAttributeMetadata(this.entityLogicalName, logicalName, this.bearer);
}
for (const option of options) {
let label = option.label.replace(/\W/g, '');
if (!label.charAt(0).match(/^[a-zA-Z]/)) {
label = `'${label}'`;
if (!optionSet.IsGlobal) {
const pascalSchemaName = Enum.capitalize(schemaName);
enumStrings += `export enum ${pascalSchemaName} {\n`;
for (const option of optionSet.Options) {
let label = option.Label.UserLocalizedLabel.Label.replace(/\W/g, '');
if (!label.charAt(0).match(/^[a-zA-Z]/)) {
label = `'${label}'`;
}
enumStrings += ` ${label} = ${option.Value},\n`;
}
enumStrings += ` ${label} = ${option.value},\n`;
enumStrings += '}\n';
}
enumStrings += '}\n';
}
}
if (enumStrings) {
Expand Down
1 change: 0 additions & 1 deletion src/generator/EnvironmentVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export class EnvironmentVariable extends AdalRouter {
return null;
}

// eslint-disable-next-line max-lines-per-function
protected async onAuthenticated(): Promise<void> {
await this.log(`Generating EnvironmentVariable`);
await this.writeEnvironmentVariablesFiles();
Expand Down
5 changes: 4 additions & 1 deletion src/generator/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {Webresource} from './Webresource';
import {ModelRouter} from './ModelRouter';
import {EnvironmentVariable} from './EnvironmentVariable';
import {LicenseValidator} from './LicenseValidator';
import {GlobalOptionSet} from './GlobalOptionSet';

export class Generator {
public static generate(schematic: string, name: string, options: unknown): Promise<void> {
const supportedSchematics = ['entity', 'webresource', 'model', 'licensevalidator', 'environmentvariable'];
const supportedSchematics = ['entity', 'webresource', 'model', 'licensevalidator', 'environmentvariable', 'globaloptionset'];
if (!shell.test('-e', 'src')) {
console.log(colors.red(`You are not inside the project Webresources folder!`));
} else if (!schematic) {
Expand All @@ -25,6 +26,8 @@ export class Generator {
return LicenseValidator.generateLicenseValidator(name);
} else if(schematic.toLowerCase() === 'environmentvariable') {
return EnvironmentVariable.generateEnvironmentVariable();
} else if(schematic.toLowerCase() === 'globaloptionsets') {
return GlobalOptionSet.generateGlobalOptionSets();
}
}

Expand Down
63 changes: 63 additions & 0 deletions src/generator/GlobalOptionSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as shell from 'shelljs';
import {AdalRouter} from '../root/tools/AdalRouter';
import {NodeApi} from '../root/tools/NodeApi/NodeApi';
import fs from 'fs';

export class GlobalOptionSet extends AdalRouter {
public static generateGlobalOptionSets(): Promise<void> {
new GlobalOptionSet();
return null;
}

protected async onAuthenticated(): Promise<void> {
await this.log(`Generating Global OptionSet`);
await this.writeGlobalOptionSetsFile();
await this.log('Generated Global OptionSet');
}

private async writeGlobalOptionSetsFile(): Promise<void> {
await this.log(`Generating OptionSets/OptionSets.ts`);
const globalOptionSetsString = await this.getGlobalOptionSetsString();
shell.cp('-r', `${__dirname}/OptionSets`, `src`);
const optionSetFilepath = 'src/OptionSets/OptionSets.ts';
const fileData = String(fs.readFileSync(optionSetFilepath));
shell.ShellString(fileData + globalOptionSetsString).to(optionSetFilepath);
shell.exec(`git add src/OptionSets`);
await this.log(`Generated OptionSet/OptionSets.ts`);
}

private async getGlobalOptionSetsString(): Promise<string> {
let optionSetStrings = '';
const optionSets = await NodeApi.getGlobalOptionSetDefinitions(this.bearer);
for (const optionSet of optionSets) {
const pascalSchemaName = GlobalOptionSet.capitalize(optionSet.Name);
optionSetStrings += `export enum ${pascalSchemaName} {\n`;
if (optionSet.OptionSetType === 'Boolean') {
const {FalseOption, TrueOption} = optionSet;
optionSetStrings += ` ${TrueOption.Label.UserLocalizedLabel.Label.replace(/\W/g, '')} = ${TrueOption.Value},\n`;
optionSetStrings += ` ${FalseOption.Label.UserLocalizedLabel.Label.replace(/\W/g, '')} = ${FalseOption.Value},\n`;
} else {
const usedLabels: string[] = [];
for (const option of optionSet.Options) {
let label = option.Label.UserLocalizedLabel.Label.replace(/\W/g, '');
if (!usedLabels.includes(label)) {
usedLabels.push(label);
if (!label.charAt(0).match(/^[a-zA-Z]/)) {
label = `Nr_${label}`;
}
optionSetStrings += ` ${label} = ${option.Value},\n`;
}
}
}
optionSetStrings += '}\n';
}
if (optionSetStrings) {
optionSetStrings += '\n';
}
return optionSetStrings;
}

private static capitalize(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1);
}
}
21 changes: 13 additions & 8 deletions src/generator/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ export class Model {
} else if (['Integer', 'Double', 'BigInt', 'Decimal', 'Double', 'Money'].includes(attributeType)) {
return 'number';
} else if (['Status'].includes(attributeType)) {
const options = await NodeApi.getStatusOptionSet(this.entityLogicalName, this.bearer);
return options.map(option => option.value).join(' | ');
const optionSet = await NodeApi.getStatusOptionSet(this.entityLogicalName, this.bearer);
return optionSet.Options.map(option => option.Value).join(' | ');
} else if (['State'].includes(attributeType)) {
const options = await NodeApi.getStateOptionSet(this.entityLogicalName, this.bearer);
return options.map(option => option.value).join(' | ');
const optionSet = await NodeApi.getStateOptionSet(this.entityLogicalName, this.bearer);
return optionSet.Options.map(option => option.Value).join(' | ');
} else if (attributeTypeName.Value === 'MultiSelectPicklistType') {
return 'number[]';
}
Expand All @@ -202,10 +202,15 @@ export class Model {
let typeStrings = '';
const attributesMetadata = await NodeApi.getAttributesMetadata(this.entityLogicalName, this.bearer);
for (const attribute of attributesMetadata) {
const {AttributeType: attributeType, LogicalName: logicalName, SchemaName: schemaName} = attribute;
if (attributeType === 'Picklist') {
const options = await NodeApi.getPicklistOptionSet(this.entityLogicalName, logicalName, this.bearer),
types = options.map(option => option.value).join(' | ');
const {AttributeType: attributeType, LogicalName: logicalName, SchemaName: schemaName, AttributeTypeName: attributeTypeName} = attribute;
if (attributeType === 'Picklist' || attributeTypeName.Value === 'MultiSelectPicklistType') {
let optionSet;
if (attributeType === 'Picklist') {
optionSet = await NodeApi.getPicklistOptionSet(this.entityLogicalName, logicalName, this.bearer);
} else {
optionSet = await NodeApi.getMultiSelectPicklistAttributeMetadata(this.entityLogicalName, logicalName, this.bearer);
}
const types = optionSet.Options.map(option => option.Value).join(' | ');
typeStrings += `type ${schemaName}Values = ${types};\n`;
}
}
Expand Down
137 changes: 86 additions & 51 deletions src/root/tools/NodeApi/NodeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,32 @@ interface HttpHeaders {
[index: string]: string | number;
}

interface OptionSetOption {
value?: number;
externalValue?: number;
label?: string;
}

interface JsonHttpHeaders extends HttpHeaders {
'OData-MaxVersion': string;
'OData-Version': string;
'Accept': string;
'Content-Type': string;
}

interface Option {
Value: number;
ExternalValue: number;
Label: {
UserLocalizedLabel: {
Label: string;
}
}
}

interface OptionSet {
Name: string;
IsGlobal: boolean;
FalseOption?: Option;
TrueOption?: Option;
Options: Option[];
OptionSetType: 'Boolean' | 'Picklist'
}

interface NodeApiResponse {
body: any;
getResponseHeader(headerName: string): string | string[];
Expand Down Expand Up @@ -85,91 +98,113 @@ export class NodeApi {
return body;
}

public static async getStatusOptionSet(entityLogicalName: string, bearer: string): Promise<OptionSetOption[]> {
public static async getStatusOptionSet(entityLogicalName: string, bearer: string): Promise<OptionSet> {
const {crm} = NodeApi.getSettings(),
{url, version} = crm,
// eslint-disable-next-line max-len
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes/Microsoft.Dynamics.CRM.StatusAttributeMetadata?$expand=OptionSet`,
{body} = await NodeApi.request('GET', uri, null, {
'Authorization': `Bearer ${bearer}`
});
return body.value[0].OptionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
return {
value: option.Value,
externalValue: option.ExternalValue,
label: option.Label.UserLocalizedLabel.Label
};
});
return body.value[0].OptionSet;
// return body.value[0].OptionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
// return {
// value: option.Value,
// externalValue: option.ExternalValue,
// label: option.Label.UserLocalizedLabel.Label
// };
// });
}

public static async getStateOptionSet(entityLogicalName: string, bearer: string): Promise<OptionSetOption[]> {
public static async getStateOptionSet(entityLogicalName: string, bearer: string): Promise<OptionSet> {
const {crm} = NodeApi.getSettings(),
{url, version} = crm,
// eslint-disable-next-line max-len
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes/Microsoft.Dynamics.CRM.StateAttributeMetadata?$expand=OptionSet`,
{body} = await NodeApi.request('GET', uri, null, {
'Authorization': `Bearer ${bearer}`
});
return body.value[0].OptionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
return {
value: option.Value,
externalValue: option.ExternalValue,
label: option.Label.UserLocalizedLabel.Label
};
});
return body.value[0].OptionSet;
// return optionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
// return {
// value: option.Value,
// externalValue: option.ExternalValue,
// label: option.Label.UserLocalizedLabel.Label
// };
// });
}

public static async getMultiSelectPicklistAttributeMetadata(entityLogicalName: string, attribute: string, bearer: string): Promise<OptionSetOption[]> {
public static async getMultiSelectPicklistAttributeMetadata(entityLogicalName: string, attribute: string, bearer: string): Promise<OptionSet> {
const {crm} = NodeApi.getSettings(),
{url, version} = crm,
// eslint-disable-next-line max-len
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes(LogicalName='${attribute}')/Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options)`,
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes(LogicalName='${attribute}')/Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options,IsGlobal)`,
{body} = await NodeApi.request('GET', uri, null, {
'Authorization': `Bearer ${bearer}`
});
return body.OptionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
return {
value: option.Value,
externalValue: option.ExternalValue,
label: option.Label.UserLocalizedLabel.Label
};
});
return body.OptionSet;
// if (!optionSet.IsGlobal) {
// return optionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
// return {
// value: option.Value,
// externalValue: option.ExternalValue,
// label: option.Label.UserLocalizedLabel.Label
// };
// });
// }
}

public static async getPicklistOptionSet(entityLogicalName: string, attribute: string, bearer: string): Promise<OptionSetOption[]> {
public static async getPicklistOptionSet(entityLogicalName: string, attribute: string, bearer: string): Promise<OptionSet> {
const {crm} = NodeApi.getSettings(),
{url, version} = crm,
// eslint-disable-next-line max-len
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes(LogicalName='${attribute}')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options)`,
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes(LogicalName='${attribute}')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options,IsGlobal)`,
{body} = await NodeApi.request('GET', uri, null, {
'Authorization': `Bearer ${bearer}`
});
return body.OptionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
return {
value: option.Value,
externalValue: option.ExternalValue,
label: option.Label.UserLocalizedLabel.Label
};
});
return body.OptionSet;
// if (!optionSet.IsGlobal) {
// return optionSet.Options.map((option: {Value: number; ExternalValue: number; Label: {UserLocalizedLabel: {Label: string}}}) => {
// return {
// value: option.Value,
// externalValue: option.ExternalValue,
// label: option.Label.UserLocalizedLabel.Label
// };
// });
// }
}

public static async getGlobalOptionSetDefinitions(bearer: string): Promise<OptionSet[]> {
const {crm} = NodeApi.getSettings(),
{url, version} = crm,
// eslint-disable-next-line max-len
uri = `${url}/api/data/v${version}/GlobalOptionSetDefinitions`,
{body} = await NodeApi.request('GET', uri, null, {
'Authorization': `Bearer ${bearer}`
});
return body.value;
}

public static async getBooleanOptionSet(entityLogicalName: string, attribute: string, bearer: string): Promise<OptionSetOption[]> {
public static async getBooleanOptionSet(entityLogicalName: string, attribute: string, bearer: string): Promise<OptionSet> {
const {crm} = NodeApi.getSettings(),
{url, version} = crm,
// eslint-disable-next-line max-len
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes(LogicalName='${attribute}')/Microsoft.Dynamics.CRM.BooleanAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=TrueOption,FalseOption)`,
uri = `${url}/api/data/v${version}/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes(LogicalName='${attribute}')/Microsoft.Dynamics.CRM.BooleanAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=TrueOption,FalseOption,IsGlobal)`,
{body} = await NodeApi.request('GET', uri, null, {
'Authorization': `Bearer ${bearer}`
}),
})/*,
optionSet = body.OptionSet,
{FalseOption, TrueOption} = optionSet;
return [{
value: FalseOption.Value,
label: FalseOption.Label.UserLocalizedLabel.Label
}, {
value: TrueOption.Value,
label: TrueOption.Label.UserLocalizedLabel.Label
}];
{FalseOption, TrueOption, IsGlobal} = optionSet*/;
return body.OptionSet;
// if (!IsGlobal) {
// return [{
// value: FalseOption.Value,
// label: FalseOption.Label.UserLocalizedLabel.Label
// }, {
// value: TrueOption.Value,
// label: TrueOption.Label.UserLocalizedLabel.Label
// }];
// }
}

private static jsonHeaders: JsonHttpHeaders = {
Expand Down
6 changes: 4 additions & 2 deletions src/root/tools/Webresource/Webresource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ export class WebresourceService {
}

private static async getWebresourcetype(extension: string, bearer: string): Promise<number> {
const options = await NodeApi.getPicklistOptionSet(WebresourceService.logicalName, 'webresourcetype', bearer);
const optionSet = await NodeApi.getPicklistOptionSet(WebresourceService.logicalName, 'webresourcetype', bearer);
let webresourcetype: number,
scriptvalue: number;
for (const {value, label} of options) {
for (const option of optionSet.Options) {
const value = option.Value;
const label = option.Label.UserLocalizedLabel.Label;
if (label.toLocaleLowerCase().includes('script')) {
scriptvalue = parseInt(String(value), 10);
}
Expand Down

0 comments on commit 17ef467

Please sign in to comment.