Skip to content

Commit 3ab5363

Browse files
committed
Refactor Comment type to use XrmAttributeType DU
1 parent 586f9d5 commit 3ab5363

8 files changed

Lines changed: 110 additions & 50 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## [Unreleased]
44
### Changed
5+
- Refactored `Comment` type to use `XrmAttributeType` DU (24 SDK attribute types) instead of strings for type-safe formatting
6+
- Extracted `XrmAttributeType` and `RelType` DUs to `Domain.fs` for better organization and constraint satisfaction (Set operations in form intersection)
7+
- Moved formatting logic into `Comment.ToCommentStrings()` and `XrmAttributeType.fromDisplayName()` for cleaner separation of concerns
58
- Lookup logical name variables are now derived from attributes rather than relationships, using a union type of all target entity names; duplicate variable names are merged into union types instead of being renamed
69
- Section control interfaces now include a JSDoc comment with the tab display name and a `{@link}` reference to the containing tab section interface
710
- Added `{@link}` comments to `ui.tabs.get()`, `tab.sections.get()`, and `ui.quickForms.get()` overloads

src/CreateTypeScript/CreateWebEntities.fs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ open TsStringUtil
55
open Constants
66
open InterpretCommon
77
open IntermediateRepresentation
8-
open Microsoft.Xrm.Sdk.Metadata
98

109

1110
let INTERNAL_NS = "_"
@@ -18,7 +17,7 @@ let currencyId = {
1817
schemaName = "TransactionCurrencyId"
1918
specialType = SpecialType.EntityReference
2019
varType = TsType.String
21-
typeName = AttributeTypeDisplayName.LookupType.Value
20+
colType = XrmAttributeType.Lookup
2221
targetEntitySets = Some [| "transactioncurrency", "transactioncurrencies", "Currency" |]
2322
readable = true
2423
createable = true
@@ -108,12 +107,12 @@ let defToFormattedVars (a, comment, _, _) =
108107
Variable.Create(formattedName a, TsType.String, comment, optional = true )
109108

110109
let getEntityRefDef nameFormat (a: XrmAttribute) =
111-
nameFormat a, [ a, Comment.Create (a.displayName, colType = a.typeName), a.varType, Some guidName ]
110+
nameFormat a, [ a, Comment.Create (a.displayName, colType = a.colType), a.varType, Some guidName ]
112111

113112
let getResultDef (ent: XrmEntity) (attr: XrmAttribute) =
114113
let vType = attr.varType
115114
let name = attr.logicalName
116-
let comment = Comment.Create(attr.displayName, colType = attr.typeName, link = getLink ent attr)
115+
let comment = Comment.Create(attr.displayName, colType = attr.colType, link = getLink ent attr)
117116

118117
match attr.specialType with
119118
| SpecialType.EntityReference -> getEntityRefDef guidName attr
@@ -166,7 +165,7 @@ let getLookupNameVariable (a: XrmAttribute) =
166165
Variable.Create(
167166
lookupName a,
168167
unionType,
169-
Comment.Create(a.displayName, colType = a.typeName),
168+
Comment.Create(a.displayName, colType = a.colType),
170169
optional = true
171170
)
172171
)

src/Domain.fs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,67 @@ open Microsoft.Xrm.Sdk.Metadata
66
open Microsoft.Xrm.Sdk.Client
77
open System.Runtime.Serialization
88

9+
type RelType =
10+
| ManyToOne
11+
| OneToMany
12+
| ManyToMany
13+
14+
type XrmAttributeType =
15+
| Boolean
16+
| Customer
17+
| DateTime
18+
| Decimal
19+
| Double
20+
| Integer
21+
| Lookup
22+
| Memo
23+
| Money
24+
| Owner
25+
| PartyList
26+
| Picklist
27+
| State
28+
| Status
29+
| String
30+
| Uniqueidentifier
31+
| CalendarRules
32+
| Virtual
33+
| BigInt
34+
| ManagedProperty
35+
| EntityName
36+
| Image
37+
| MultiSelectPicklist
38+
| File
39+
40+
module XrmAttributeType =
41+
let private map = [
42+
AttributeTypeDisplayName.BooleanType, Boolean
43+
AttributeTypeDisplayName.CustomerType, Customer
44+
AttributeTypeDisplayName.DateTimeType, DateTime
45+
AttributeTypeDisplayName.DecimalType, Decimal
46+
AttributeTypeDisplayName.DoubleType, Double
47+
AttributeTypeDisplayName.IntegerType, Integer
48+
AttributeTypeDisplayName.LookupType, Lookup
49+
AttributeTypeDisplayName.MemoType, Memo
50+
AttributeTypeDisplayName.MoneyType, Money
51+
AttributeTypeDisplayName.OwnerType, Owner
52+
AttributeTypeDisplayName.PartyListType, PartyList
53+
AttributeTypeDisplayName.PicklistType, Picklist
54+
AttributeTypeDisplayName.StateType, State
55+
AttributeTypeDisplayName.StatusType, Status
56+
AttributeTypeDisplayName.StringType, String
57+
AttributeTypeDisplayName.UniqueidentifierType, Uniqueidentifier
58+
AttributeTypeDisplayName.CalendarRulesType, CalendarRules
59+
AttributeTypeDisplayName.VirtualType, Virtual
60+
AttributeTypeDisplayName.BigIntType, BigInt
61+
AttributeTypeDisplayName.ManagedPropertyType, ManagedProperty
62+
AttributeTypeDisplayName.EntityNameType, EntityName
63+
AttributeTypeDisplayName.ImageType, Image
64+
AttributeTypeDisplayName.MultiSelectPicklistType, MultiSelectPicklist
65+
AttributeTypeDisplayName.FileType, File ]
66+
67+
let fromDisplayName t =
68+
map |> List.tryFind (fst >> (=) t) |> Option.map snd |> Option.defaultValue Virtual
69+
970
type Version = int * int * int * int
1071
type Intersect = string * Guid[]
1172

src/IntermediateRepresentation.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type XrmAttribute = {
2626
varType: TsType
2727
specialType: SpecialType
2828
targetEntitySets: (string * string * string)[] option
29-
typeName: string
29+
colType: XrmAttributeType
3030
readable: bool
3131
createable: bool
3232
updateable: bool
@@ -37,7 +37,7 @@ type XrmRelationship = {
3737
schemaName: string
3838
attributeName: string
3939
displayName: string
40-
relType: string
40+
relType: RelType
4141
relatedSetName: string
4242
relatedSchemaName: string
4343
navProp: string

src/Interpretation/InterpretCommon.fs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@ module internal DG.XrmTypeScript.InterpretCommon
22

33
open Constants
44
open IntermediateRepresentation
5-
open Microsoft.Xrm.Sdk.Metadata
65

76

87
let getLink (e: XrmEntity) (a: XrmAttribute) =
9-
if
10-
[ AttributeTypeDisplayName.PicklistType.Value
11-
AttributeTypeDisplayName.MultiSelectPicklistType.Value
12-
AttributeTypeDisplayName.StateType.Value
13-
AttributeTypeDisplayName.StatusType.Value ]
14-
|> List.contains a.typeName
15-
then
8+
match a.colType with
9+
| XrmAttributeType.Picklist
10+
| XrmAttributeType.MultiSelectPicklist
11+
| XrmAttributeType.State
12+
| XrmAttributeType.Status ->
1613
let enumName = TsStringUtil.typeToString(a.varType).Split '.' |> Array.last
1714
let enum = e.optionSets |> List.tryFind (fun o -> o.name = enumName)
15+
1816
match enum with
1917
| Some e -> $"{ENUM_NS}.{e.name} {e.displayName}"
2018
| None -> ""
21-
else
22-
""
19+
| _ -> ""

src/Interpretation/InterpretEntityMetadata.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ let interpretAttribute (nameMap: Map<string, EntityInfo>) labelMapping (a: Attri
9494
logicalName = a.LogicalName
9595
varType = vType
9696
specialType = sType
97-
typeName = a.AttributeTypeName.Value
97+
colType = XrmAttributeType.fromDisplayName a.AttributeTypeName
9898
targetEntitySets = targetEntitySets
9999
readable = a.IsValidForRead.GetValueOrDefault(false)
100100
createable = a.IsValidForCreate.GetValueOrDefault(false)
@@ -134,7 +134,7 @@ let interpretRelationship (nameMap: Map<string, EntityInfo>) referencing (attrib
134134
if referencing then rel.ReferencingAttribute
135135
else rel.ReferencedAttribute
136136
displayName = eInfo.DisplayName
137-
relType = if referencing then "ManyToOne" else "OneToMany"
137+
relType = if referencing then RelType.ManyToOne else RelType.OneToMany
138138
navProp =
139139
if referencing then rel.ReferencingEntityNavigationPropertyName
140140
else rel.ReferencedEntityNavigationPropertyName
@@ -160,7 +160,7 @@ let interpretM2MRelationship (nameMap: Map<string, EntityInfo>) logicalName (rel
160160
{ XrmRelationship.schemaName = rel.SchemaName
161161
attributeName = rel.SchemaName
162162
displayName = eInfo.DisplayName
163-
relType = "ManyToMany"
163+
relType = RelType.ManyToMany
164164
navProp =
165165
if logicalName = rel.Entity2LogicalName then rel.Entity1NavigationPropertyName
166166
else rel.Entity2NavigationPropertyName

src/Interpretation/InterpretFormXml.fs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,11 @@ let getTargetEntities (tes: string option) (a: XrmAttribute option) =
5656
let el = tes' |> Array.map (fun (l, _, _) -> l) |> Array.toList
5757
match el.IsEmpty with
5858
| true -> "\"NoTargets\""
59-
| false -> List.fold(fun acc e -> sprintf "%s | \"%s\"" acc e) (sprintf "\"%s\"" el.Head) el.Tail
59+
| false -> List.fold(fun acc e -> $"{acc} | \"{e}\"") ($"\"{el.Head}\"") el.Tail
6060

6161
let getAttributeType = function
6262
| None -> TsType.Undefined
6363
| Some a -> a.varType
64-
let getTargetDisplayName (a: XrmAttribute) =
65-
match a.targetEntitySets with
66-
| None -> ""
67-
| Some tes ->
68-
tes |> Array.map (fun (_, _, dn) -> dn) |> String.concat " | "
6964

7065
let getAttribute (enums:Map<string,TsType>) (entity: XrmEntity) (cField: ControlField): XrmFormAttribute option =
7166
if String.IsNullOrEmpty cField.dataFieldName then None else
@@ -77,7 +72,7 @@ let getAttribute (enums:Map<string,TsType>) (entity: XrmEntity) (cField: Control
7772
let comment =
7873
match attribute with
7974
| None -> Comment.Create cField.displayName
80-
| Some attr -> Comment.Create(attr.displayName, colType = attr.typeName, table = getTargetDisplayName attr, link = getLink entity attr)
75+
| Some attr -> Comment.Create(attr.displayName, colType = attr.colType, ?targetEntitySets = attr.targetEntitySets, link = getLink entity attr)
8176

8277
let attrType = getAttributeType attribute
8378

@@ -141,7 +136,7 @@ let getControl (enums:Map<string,TsType>) (entity: XrmEntity) (cField:ControlFi
141136
| None -> Comment.Create cField.displayName
142137
| Some attr ->
143138
let label = if cField.displayName.Trim() <> attr.displayName.Trim() then cField.displayName else ""
144-
Comment.Create(attr.displayName, label = label, colType = attr.typeName, table = getTargetDisplayName attr, link = getLink entity attr)
139+
Comment.Create(attr.displayName, label = label, colType = attr.colType, ?targetEntitySets = attr.targetEntitySets, link = getLink entity attr)
145140

146141
let cType =
147142
match cField.controlClass with
@@ -297,20 +292,20 @@ let getQuickViewFormControls : ControlField list -> XrmFormQuickViewForm list =
297292

298293
let getControlFields (entity: XrmEntity) (form: XElement) : ControlField list =
299294
let controlDescriptions =
300-
form.Descendants(XName.Get("controlDescription"))
295+
form.Descendants(XName.Get "controlDescription")
301296
|> Seq.choose (fun cd ->
302-
cd.Elements(XName.Get("customControl"))
297+
cd.Elements(XName.Get "customControl")
303298
|> Seq.choose (fun cc ->
304299
match getValue cc "id" with
305300
| null -> None
306301
| x -> Some (getValue cd "forControl", x))
307302
|> Seq.tryHead)
308303
|> Map.ofSeq
309304

310-
form.Descendants(XName.Get("cell"))
311-
|> Seq.filter (fun cell -> cell.Descendants(XName.Get("control")) |> Seq.isEmpty |> not) // Filter out cells that don't have a control
305+
form.Descendants(XName.Get "cell")
306+
|> Seq.filter (fun cell -> cell.Descendants(XName.Get "control") |> Seq.isEmpty |> not) // Filter out cells that don't have a control
312307
|> Seq.map (fun cell ->
313-
let ctrl = cell.Descendants(XName.Get("control")) |> Seq.head
308+
let ctrl = cell.Descendants(XName.Get "control") |> Seq.head
314309

315310
let id = getValue ctrl "id"
316311
let classId = getValue ctrl "classid"
@@ -319,23 +314,23 @@ let getControlFields (entity: XrmEntity) (form: XElement) : ControlField list =
319314
let datafieldname = getValue ctrl "datafieldname"
320315

321316
let targetEntities =
322-
let parms = ctrl.Descendants(XName.Get("parameters"))
317+
let parms = ctrl.Descendants(XName.Get "parameters")
323318
if Seq.isEmpty parms then
324319
let rel = getValue ctrl "relationship"
325320
entity.allRelationships
326321
|> List.choose (fun r -> if r.schemaName = rel then Some r.relatedSetName else None)
327322
else
328323
parms
329-
|> Seq.collect (fun p -> p.Elements(XName.Get("TargetEntityType")))
324+
|> Seq.collect (fun p -> p.Elements(XName.Get "TargetEntityType"))
330325
|> Seq.map (fun e -> e.Value)
331326
|> Seq.toList
332327

333328
let quickViewForms =
334-
let parms = ctrl.Descendants(XName.Get("parameters"))
329+
let parms = ctrl.Descendants(XName.Get "parameters")
335330
if Seq.isEmpty parms then None
336331
else
337332
parms
338-
|> Seq.collect (fun p -> p.Elements(XName.Get("QuickForms")))
333+
|> Seq.collect (fun p -> p.Elements(XName.Get "QuickForms"))
339334
|> Seq.map (fun e -> e.Value)
340335
|> Seq.toList
341336
|> function
@@ -359,9 +354,9 @@ let getControlFields (entity: XrmEntity) (form: XElement) : ControlField list =
359354
targetEntitySets = None;
360355
quickViewForms = quickViewForms }
361356

362-
if(targetEntities.Length > 0) then
357+
if targetEntities.Length > 0 then
363358
let tes =
364-
Seq.fold(fun acc e -> sprintf "%s | \"%s\"" acc e) (sprintf "\"%s\"" targetEntities.Head) targetEntities.Tail
359+
Seq.fold(fun acc e -> $"{acc} | \"{e}\"") ($"\"{targetEntities.Head}\"") targetEntities.Tail
365360
{baseControl with targetEntitySets = Some tes}
366361
else baseControl
367362
)

src/TypeScript/TypeScript.fs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,31 @@ and Variable =
4848
and Comment =
4949
{ displayName: string
5050
label: string
51-
colType: string
52-
table: string
53-
relType: string
51+
colType: XrmAttributeType option
52+
targetEntitySets: (string * string * string)[] option
53+
relType: RelType option
5454
tab: string
5555
link: string }
56-
static member Create(?displayName, ?label, ?colType, ?table, ?relType, ?tab, ?link) =
56+
static member Create(?displayName, ?label, ?colType, ?targetEntitySets, ?relType, ?tab, ?link) =
5757
{ displayName = defaultArg displayName ""
5858
label = defaultArg label ""
59-
colType = defaultArg colType ""
60-
table = defaultArg table ""
61-
relType = defaultArg relType ""
59+
colType = colType
60+
targetEntitySets = targetEntitySets
61+
relType = relType
6262
tab = defaultArg tab ""
6363
link = defaultArg link "" }
6464
member c.ToCommentStrings() =
6565
let dsLine = if String.IsNullOrWhiteSpace c.displayName then None else Some $"**{c.displayName.Trim()}**"
66-
let tabLine = if String.IsNullOrWhiteSpace c.tab then None else Some $"Tab: *{c.tab.Trim()}*"
66+
let tabLine = if String.IsNullOrWhiteSpace c.tab then None else Some $"Tab: {c.tab.Trim()}"
6767
let labelLine = if String.IsNullOrWhiteSpace c.label then None else Some $"Label: {c.label.Trim()}"
68-
let colTypeLine = if String.IsNullOrWhiteSpace c.colType then None else Some $"Column Type: *{c.colType.Trim()}*"
69-
let tableLine = if String.IsNullOrWhiteSpace c.table then None else Some $"Table: *{c.table.Trim()}*"
70-
let relTypeLine = if String.IsNullOrWhiteSpace c.relType then None else Some $"Relationship Type: *{c.relType.Trim()}*"
68+
let colTypeLine = c.colType |> Option.map (fun t -> $"Column Type: {t}")
69+
let tableLine =
70+
match c.targetEntitySets with
71+
| None | Some [||] -> None
72+
| Some tes ->
73+
let formatted = tes |> Array.map (fun (ln, _, dn) -> $"{dn} (`{ln}`)") |> String.concat " | "
74+
Some $"Table: {formatted}"
75+
let relTypeLine = c.relType |> Option.map (fun t -> $"Relationship Type: {t}")
7176
let linkLine = if String.IsNullOrWhiteSpace c.link then None else Some (sprintf "{@link %s}" (c.link.Trim()))
7277

7378
let lines = [ dsLine; tabLine; labelLine; colTypeLine; tableLine; relTypeLine; linkLine ] |> List.choose id

0 commit comments

Comments
 (0)