Skip to content

Commit 3d5026a

Browse files
author
Greg DeCarlo
committed
Added module for Metadata with Updated tests; Published to npm.
1 parent 5c8defd commit 3d5026a

22 files changed

+628
-61
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*.userosscache
1010
*.sln.docstates
1111

12+
node_modules/
13+
1214
# Build results
1315
[Dd]ebug/
1416
[Dd]ebugPublic/

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*
2+
!dist/Dynamics/
3+
!dist/Query/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Scalable Dynamics, LLC
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ Create more applications using the Microsoft Dynamics Xrm platform
44

55
> more-xrm enables querying the dynamics data model from any application
66
7+
License: [MIT](http://www.opensource.org/licenses/mit-license.php)
8+
9+
## NPM
10+
The package name is more-xrm, you can find it here:
11+
12+
[![NPM version](https://img.shields.io/npm/v/more-xrm.svg?style=flat)](https://www.npmjs.com/package/more-xrm)
13+
714
## TypeScript Example
815

916
```typescript
@@ -30,6 +37,8 @@ async function DynamicsClientSample() {
3037
## Interfaces
3138

3239
```typescript
40+
//example: const dynamicsClient = dynamics();
41+
3342
interface Dynamics {
3443
batch(): DynamicsBatch;
3544
fetch<T>(query: Query, maxRowCount?: number): Promise<T[]>;
@@ -38,6 +47,8 @@ interface Dynamics {
3847
```
3948

4049
```typescript
50+
//example: const dynamicsBatch = dynamics().batch();
51+
4152
interface DynamicsBatch {
4253
execute(): Promise<any[]>;
4354
request(query: Query, maxRowCount?: number): DynamicsBatch;
@@ -50,6 +61,8 @@ interface DynamicsBatch {
5061
```
5162

5263
```typescript
64+
//example: const dynamicsQuery = dynamics().query('entityLogicalName','entitySetName');
65+
5366
interface Query {
5467
alias(attributeName: string, alias: string): Query;
5568
path(entityPath: string): Query;
@@ -61,12 +74,26 @@ interface Query {
6174
}
6275
```
6376

77+
```typescript
78+
//example: const dynamicsMetadata = dynamicsMetadata();
79+
80+
interface DynamicsMetadata {
81+
attributes(entityName: string): Promise<AttributeMetadata[]>;
82+
entities(): Promise<EntityMetadata[]>;
83+
entity(entityName: string): Promise<EntityAttributeMetadata>;
84+
}
85+
```
86+
6487
## Functions
6588

6689
```typescript
6790
function dynamics(accessToken?: string): Dynamics
6891
```
6992

93+
```typescript
94+
function dynamicsMetadata(accessToken?: string): DynamicsMetadata;
95+
```
96+
7097
```typescript
7198
function dynamicsQuery<T>(query: Query, maxRowCount?: number, headers?: any): Promise<T[]>;
7299
function dynamicsQueryUrl<T>(dynamicsEntitySetUrl: string, query: Query, maxRowCount?: number, headers?: any): Promise<T[]>;

build/dist/Dynamics/Dynamics.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ export declare const DynamicsHeaders: {
1010
export interface Dynamics {
1111
batch(): DynamicsBatch;
1212
fetch<T>(query: Query, maxRowCount?: number): Promise<T[]>;
13+
optionset(entityName: any, attributeName: any): Promise<{
14+
label: string;
15+
value: number;
16+
}[]>;
17+
query(entityLogicalName: string, entitySetName: string): Query;
1318
save(entitySetName: string, data: any, id?: string): Promise<string>;
1419
}
1520
export default function dynamics(accessToken?: string): Dynamics;

build/dist/Dynamics/Dynamics.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import query from "../Query/Query";
12
import { dynamicsQuery, dynamicsSave, dynamicsRequest } from "./DynamicsRequest";
23
import { dynamicsBatch } from "./DynamicsBatch";
34
export const WebApiVersion = 'v9.1';
@@ -12,25 +13,27 @@ export default function dynamics(accessToken) {
1213
}
1314
class DynamicsClient {
1415
constructor(accessToken) {
15-
if (accessToken) {
16-
this.dynamicsHeaders = {
17-
'Authorization': 'Bearer ' + accessToken
18-
};
19-
}
16+
this.dynamicsHeaders = accessToken && {
17+
'Authorization': 'Bearer ' + accessToken
18+
};
2019
}
2120
batch() {
2221
return dynamicsBatch(this.dynamicsHeaders);
2322
}
2423
fetch(query, maxRowCount = DefaultMaxRecords) {
2524
return dynamicsQuery(query, maxRowCount, this.dynamicsHeaders);
2625
}
27-
save(entitySetName, data, id) {
28-
return dynamicsSave(entitySetName, data, id, this.dynamicsHeaders);
29-
}
3026
optionset(entityName, attributeName) {
31-
return dynamicsRequest(`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options),GlobalOptionSet($select=Options)`).then(attribute => (attribute.OptionSet || attribute.GlobalOptionSet).Options.map((option) => ({
32-
label: option.Label.UserLocalizedLabel.Label,
27+
return dynamicsRequest(`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=LogicalName&$expand=OptionSet($select=Options),GlobalOptionSet($select=Options)`, this.dynamicsHeaders)
28+
.then(attribute => (attribute.OptionSet || attribute.GlobalOptionSet).Options.map((option) => ({
29+
label: (option.Label && option.Label.UserLocalizedLabel && option.Label.UserLocalizedLabel.Label),
3330
value: option.Value
3431
})));
3532
}
33+
query(entityLogicalName, entitySetName) {
34+
return query(entityLogicalName).path(entitySetName);
35+
}
36+
save(entitySetName, data, id) {
37+
return dynamicsSave(entitySetName, data, id, this.dynamicsHeaders);
38+
}
3639
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export default function dynamicsMetadata(accessToken?: string): DynamicsMetadata;
2+
export interface DynamicsMetadata {
3+
attributes(entityName: string): Promise<AttributeMetadata[]>;
4+
entities(): Promise<EntityMetadata[]>;
5+
entity(entityName: string): Promise<EntityAttributeMetadata>;
6+
}
7+
export interface EntityMetadata {
8+
LogicalName: string;
9+
EntitySetName: string;
10+
DisplayName: string;
11+
Description: string;
12+
}
13+
export interface EntityAttributeMetadata extends EntityMetadata {
14+
Attributes: AttributeMetadata[];
15+
}
16+
export interface AttributeMetadata {
17+
LogicalName: string;
18+
DisplayName: string;
19+
Type: AttributeTypeCode;
20+
LookupEntityName?: string;
21+
PicklistOptions?: OptionSetMetadata[];
22+
}
23+
export interface OptionSetMetadata {
24+
Label: string;
25+
Value: number;
26+
}
27+
export declare type AttributeTypeCode = 'BigInt' | 'Boolean' | 'Customer' | 'DateTime' | 'Decimal' | 'Double' | 'Integer' | 'Lookup' | 'Memo' | 'Money' | 'PartyList' | 'Picklist' | 'State' | 'Status' | 'String';
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { dynamicsRequest } from "./DynamicsRequest";
2+
import { dynamicsBatch } from "./DynamicsBatch";
3+
import { WebApiVersion } from "./Dynamics";
4+
export default function dynamicsMetadata(accessToken) {
5+
return new DynamicsMetadataClient(accessToken);
6+
}
7+
const ExcludedAttributeFilters = {
8+
'Uniqueidentifier': 'AttributeType',
9+
'CalendarRules': 'AttributeType',
10+
'EntityName': 'AttributeType',
11+
'ManagedProperty': 'AttributeType',
12+
'Owner': 'AttributeType',
13+
'Virtual': 'AttributeType',
14+
'Lookup': 'AttributeType',
15+
'Picklist': 'AttributeType',
16+
'Status': 'AttributeType',
17+
'State': 'AttributeType',
18+
'yomi': "contains(LogicalName,'yomi')",
19+
'base': "endswith(LogicalName,'base')"
20+
};
21+
class DynamicsMetadataClient {
22+
constructor(accessToken) {
23+
this.dynamicsHeaders = accessToken && {
24+
'Authorization': 'Bearer ' + accessToken
25+
};
26+
}
27+
attributes(entityName) {
28+
const properties = ["AttributeType", "DisplayName", "LogicalName", "SchemaName", "IsCustomAttribute"];
29+
const filter = Object.keys(ExcludedAttributeFilters).map(v => (ExcludedAttributeFilters[v] == 'AttributeType' && `AttributeType ne Microsoft.Dynamics.CRM.AttributeTypeCode'${v}'`) || v).join(' and ');
30+
return dynamicsBatch(this.dynamicsHeaders)
31+
.requestAllUrls([
32+
`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes?$select=${properties}&$filter=${filter}`,
33+
`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes/Microsoft.Dynamics.CRM.LookupAttributeMetadata?$select=${properties},Targets`,
34+
`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$select=${properties}&$expand=OptionSet($select=Options)`,
35+
`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes/Microsoft.Dynamics.CRM.StatusAttributeMetadata?$select=${properties}&$expand=OptionSet($select=Options)`,
36+
`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')/Attributes/Microsoft.Dynamics.CRM.StateAttributeMetadata?$select=${properties}&$expand=OptionSet($select=Options)`
37+
])
38+
.execute()
39+
.then(data => this.flatten(data)
40+
.map((attribute) => ({
41+
LogicalName: attribute.LogicalName,
42+
DisplayName: (attribute.DisplayName && attribute.DisplayName.UserLocalizedLabel && attribute.DisplayName.UserLocalizedLabel.Label) || attribute.LogicalName,
43+
Type: attribute.AttributeType,
44+
LookupEntityName: attribute.Targets && attribute.Targets[0],
45+
PicklistOptions: attribute.OptionSet && attribute.OptionSet.Options.map((opt) => ({
46+
Label: (opt.Label && opt.Label.UserLocalizedLabel && opt.Label.UserLocalizedLabel.Label),
47+
Value: opt.Value
48+
}))
49+
})));
50+
}
51+
entities() {
52+
return dynamicsRequest(`/api/data/${WebApiVersion}/EntityDefinitions?$select=EntitySetName,Description,DisplayName,LogicalName,PrimaryIdAttribute,PrimaryNameAttribute,IconSmallName,IsActivity,IsCustomEntity`, this.dynamicsHeaders)
53+
.then(data => data
54+
.map(entity => ({
55+
LogicalName: entity.LogicalName,
56+
EntitySetName: entity.EntitySetName,
57+
DisplayName: (entity.DisplayName && entity.DisplayName.UserLocalizedLabel && entity.DisplayName.UserLocalizedLabel.Label) || entity.LogicalName,
58+
Description: (entity.Description && entity.Description.UserLocalizedLabel && entity.Description.UserLocalizedLabel.Label) || ''
59+
})));
60+
}
61+
entity(entityName) {
62+
return dynamicsRequest(`/api/data/${WebApiVersion}/EntityDefinitions(LogicalName='${entityName}')?$select=EntitySetName,Description,DisplayName,LogicalName,PrimaryIdAttribute,PrimaryNameAttribute,IconSmallName,IsActivity,IsCustomEntity`, this.dynamicsHeaders)
63+
.then(entity => this.attributes(entityName)
64+
.then(attributes => ({
65+
LogicalName: entity.LogicalName,
66+
EntitySetName: entity.EntitySetName,
67+
DisplayName: (entity.DisplayName && entity.DisplayName.UserLocalizedLabel && entity.DisplayName.UserLocalizedLabel.Label) || entity.LogicalName,
68+
Description: (entity.Description && entity.Description.UserLocalizedLabel && entity.Description.UserLocalizedLabel.Label) || '',
69+
Attributes: attributes
70+
})));
71+
}
72+
flatten(values) {
73+
return [].concat(...values);
74+
}
75+
}

build/dist/Query/Query.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ export interface Query {
2323
alias(attributeName: string, alias: string): Query;
2424
path(entityPath: string): Query;
2525
select(...attributeNames: string[]): Query;
26-
where(attributeName: string, operator: QueryOperator, ...values: any[]): Query;
27-
whereAny(any: (or: (attributeName: string, operator: QueryOperator, ...values: any[]) => void) => void): Query;
26+
where(attributeName: string, operator: QueryOperatorParam, ...values: any[]): Query;
27+
whereAny(any: (or: (attributeName: string, operator: QueryOperatorParam, ...values: any[]) => void) => void): Query;
2828
orderBy(attributeName: string, isDescendingOrder?: boolean): Query;
2929
join(entityName: string, fromAttribute: string, toAttribute?: string, alias?: string, isOuterJoin?: boolean): Query;
3030
Query: DataQuery;
3131
}
32+
export declare type QueryOperatorParam = QueryOperator | QueryOperatorExpression;
3233
export declare enum QueryOperator {
3334
Contains = "like",
3435
StartsWith = "begins-with",
@@ -46,5 +47,6 @@ export declare enum QueryOperator {
4647
IsNotCurrentUser = "ne-userid",
4748
IsCurrentUserTeam = "eq-userteams"
4849
}
50+
export declare type QueryOperatorExpression = 'like' | 'begins-with' | 'eq' | 'neq' | 'gt' | 'lt' | 'in' | 'not-in' | 'on-or-before' | 'on-or-after' | 'null' | 'not-null' | 'eq-userid' | 'ne-userid' | 'eq-userteams';
4951
export default function query(entityName: string, ...attributeNames: string[]): Query;
5052
export declare function GetRootQuery(query: Query): DataQuery;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export declare function dynamicsMetadataRetrieveAll(): Promise<void>;

0 commit comments

Comments
 (0)