From 9d6585eb07c16a02511fea171b8d5a911b2e9399 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Thu, 23 Jul 2020 13:19:09 +0100 Subject: [PATCH 01/18] Apply friendly name setting for value-of attribute --- FetchXmlBuilder/AppCode/TreeNodeHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/FetchXmlBuilder/AppCode/TreeNodeHelper.cs b/FetchXmlBuilder/AppCode/TreeNodeHelper.cs index 07200b83..4d7ca143 100644 --- a/FetchXmlBuilder/AppCode/TreeNodeHelper.cs +++ b/FetchXmlBuilder/AppCode/TreeNodeHelper.cs @@ -181,6 +181,7 @@ public static void SetNodeText(TreeNode node, FetchXmlBuilder fxb) { var parent = GetAttributeFromNode(node.Parent.Parent, "name"); attr = fxb.GetAttributeDisplayName(parent, attr); + valueOf = fxb.GetAttributeDisplayName(parent, valueOf); } if (!string.IsNullOrEmpty(ent)) { From e9c0060486d5a427a8cc76706b5fdf78eaa6bd06 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Thu, 23 Jul 2020 13:19:32 +0100 Subject: [PATCH 02/18] Show friendly name of existing value-of attribute when selecting condition --- FetchXmlBuilder/Controls/conditionControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index f654d1f4..e6d35e5e 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -301,10 +301,10 @@ private void RefreshAttributes() // RefreshFill now that attributes are loaded ReFillControl(cmbAttribute); ReFillControl(cmbValue); - ReFillControl(cmbValueOf); EndInit(); RefreshOperators(); UpdateValueField(); + ReFillControl(cmbValueOf); } private void RefreshOperators() From 231db9f6c2abdb4d5c5864e828efb884a9c177ea Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 26 Jul 2020 21:04:43 +0100 Subject: [PATCH 03/18] Do not validate value if value-of is specified --- FetchXmlBuilder/Controls/conditionControl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index e6d35e5e..aa90aa2b 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -155,6 +155,11 @@ protected override ControlValidationResult ValidateControl(Control control) return new ControlValidationResult(ControlValidationLevel.Error, "Value and Value Of cannot both be set"); } + if (!string.IsNullOrWhiteSpace(cmbValueOf.Text)) + { + return null; + } + switch (valueType) { case null: From 4fd9b326706a2f62a88a45d2e237a03da377804a Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Tue, 28 Jul 2020 22:47:34 +0100 Subject: [PATCH 04/18] like-contains-startswith-endswith translations --- .../AppCode/OData4CodeGenerator.cs | 46 ++++++++- .../Controls/FetchXmlElementControlBase.cs | 7 +- FetchXmlBuilder/Controls/conditionControl.cs | 96 +++++++++++++++++++ 3 files changed, 143 insertions(+), 6 deletions(-) diff --git a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs index 8221591e..b13f7b43 100644 --- a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs +++ b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs @@ -328,16 +328,47 @@ private static string GetCondition(string entityName, condition condition, Fetch result += " ne null"; break; case @operator.like: - result = $"contains({attrMeta.LogicalName}, '{condition.value}')"; - break; case @operator.notlike: - result = $"not contains({attrMeta.LogicalName}, '{condition.value}')"; + string func; + var value = condition.value; + + if (value.StartsWith("%") && value.EndsWith("%") && value.Length >= 2) + { + func = "contains"; + value = value.Substring(1, value.Length - 2); + } + else if (value.StartsWith("%")) + { + func = "endswith"; + value = value.Substring(1); + } + else if (value.EndsWith("%")) + { + func = "startswith"; + value = value.Substring(0, value.Length - 1); + } + else + { + if (ContainsLikeWildcards(value)) + throw new Exception("Unsupported LIKE wildcard " + condition.value); + + result += " eq "; + break; + } + + if (ContainsLikeWildcards(value)) + throw new Exception("Unsupported LIKE wildcard " + condition.value); + + result = $"{func}({attrMeta.LogicalName}, '{FormatValue(typeof(string), value)}')"; + + if (condition.@operator == @operator.notlike) + result = "not " + result; break; case @operator.beginswith: - result = $"startswith({attrMeta.LogicalName}, '{condition.value}')"; + result = $"startswith({attrMeta.LogicalName}, '{FormatValue(typeof(string), condition.value)}')"; break; case @operator.endswith: - result = $"endswith({attrMeta.LogicalName}, '{condition.value}')"; + result = $"endswith({attrMeta.LogicalName}, '{FormatValue(typeof(string), condition.value)}')"; break; case @operator.above: function = "Above"; @@ -674,6 +705,11 @@ private static string GetCondition(string entityName, condition condition, Fetch return result; } + private static bool ContainsLikeWildcards(string value) + { + return value.Contains("%") || value.Contains("_"); + } + private static string GetPropertyName(AttributeMetadata attr) { if (attr is LookupAttributeMetadata) diff --git a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs index 02cfc46b..2bc837f2 100644 --- a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs +++ b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs @@ -151,10 +151,15 @@ protected virtual void SaveInternal(bool keyPress) { if (IsInitialized) { - SendSaveMessage(ControlUtils.GetAttributesCollection(this.Controls, true), keyPress); + SendSaveMessage(GetAttributesCollection(), keyPress); } } + protected virtual Dictionary GetAttributesCollection() + { + return ControlUtils.GetAttributesCollection(this.Controls, true); + } + protected virtual ControlValidationResult ValidateControl(Control control) { return null; diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index aa90aa2b..8cac9539 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -72,6 +72,47 @@ protected override void SaveInternal(bool silent) base.SaveInternal(silent); } + protected override Dictionary GetAttributesCollection() + { + var attr = base.GetAttributesCollection(); + + if (attr.TryGetValue("operator", out var op) && attr.TryGetValue("value", out var value)) + { + if (op == "begins-with") + { + attr["operator"] = "like"; + attr["value"] = value + "%"; + } + else if (op == "not-begin-with") + { + attr["operator"] = "not-like"; + attr["value"] = value + "%"; + } + else if (op == "ends-with") + { + attr["operator"] = "like"; + attr["value"] = "%" + value; + } + else if (op == "not-end-with") + { + attr["operator"] = "not-like"; + attr["value"] = "%" + value; + } + else if (op == "contains") + { + attr["operator"] = "like"; + attr["value"] = "%" + value + "%"; + } + else if (op == "does-not-contain") + { + attr["operator"] = "not-like"; + attr["value"] = "%" + value + "%"; + } + } + + return attr; + } + protected override ControlValidationResult ValidateControl(Control control) { if (control == cmbAttribute) @@ -310,6 +351,61 @@ private void RefreshAttributes() RefreshOperators(); UpdateValueField(); ReFillControl(cmbValueOf); + NormalizeLike(); + } + + private void NormalizeLike() + { + var op = cmbOperator.SelectedItem as OperatorItem; + + if (op == null) + { + return; + } + + var value = cmbValue.Text; + + if (String.IsNullOrWhiteSpace(value)) + { + return; + } + + if (op.GetValue() == "like" || op.GetValue() == "not-like") + { + string newOp = null; + + if (value.StartsWith("%") && value.EndsWith("%") && value.Length >= 2) + { + newOp = "contains"; + value = value.Substring(1, value.Length - 2); + } + else if (value.StartsWith("%")) + { + newOp = "ends-with"; + value = value.Substring(1); + } + else if (value.EndsWith("%")) + { + newOp = "begins-with"; + value = value.Substring(0, value.Length - 1); + } + + if (newOp != null) + { + if (op.GetValue() == "not-like") + { + newOp = "not-" + newOp.Replace("s-", "-"); + } + + var newOpItem = cmbOperator.Items.OfType().FirstOrDefault(o => o.GetValue() == newOp); + + if (newOpItem != null) + { + cmbOperator.SelectedItem = newOpItem; + cmbValue.Text = value; + } + } + } } private void RefreshOperators() From e99d9782ac2a119af70291e29948c38c94d8a894 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Mon, 10 Aug 2020 20:55:52 +0100 Subject: [PATCH 05/18] Handle conditions on EntityName attributes --- FetchXmlBuilder/AppCode/EntityNameItem.cs | 25 ++++++++++++++++++++ FetchXmlBuilder/AppCode/OperatorItem.cs | 3 ++- FetchXmlBuilder/Controls/conditionControl.cs | 14 ++++++++++- FetchXmlBuilder/FetchXmlBuilder.csproj | 1 + 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 FetchXmlBuilder/AppCode/EntityNameItem.cs diff --git a/FetchXmlBuilder/AppCode/EntityNameItem.cs b/FetchXmlBuilder/AppCode/EntityNameItem.cs new file mode 100644 index 00000000..53c53ecf --- /dev/null +++ b/FetchXmlBuilder/AppCode/EntityNameItem.cs @@ -0,0 +1,25 @@ +using Cinteros.Xrm.XmlEditorUtils; +using Microsoft.Xrm.Sdk.Metadata; + +namespace Cinteros.Xrm.FetchXmlBuilder.AppCode +{ + public class EntityNameItem : IComboBoxItem + { + private EntityMetadata meta = null; + + public EntityNameItem(EntityMetadata Entity) + { + meta = Entity; + } + + public override string ToString() + { + return FetchXmlBuilder.GetEntityDisplayName(meta); + } + + public string GetValue() + { + return meta.ObjectTypeCode.Value.ToString(); + } + } +} \ No newline at end of file diff --git a/FetchXmlBuilder/AppCode/OperatorItem.cs b/FetchXmlBuilder/AppCode/OperatorItem.cs index 3d83a47c..e013b7ea 100644 --- a/FetchXmlBuilder/AppCode/OperatorItem.cs +++ b/FetchXmlBuilder/AppCode/OperatorItem.cs @@ -315,7 +315,8 @@ public static OperatorItem[] GetConditionsByAttributeType(AttributeTypeCode valu valueType != AttributeTypeCode.Lookup && valueType != AttributeTypeCode.Customer && valueType != AttributeTypeCode.Owner && - valueType != AttributeTypeCode.Uniqueidentifier) + valueType != AttributeTypeCode.Uniqueidentifier && + valueType != AttributeTypeCode.EntityName) { validConditionsList.Add(new OperatorItem(ConditionOperator.BeginsWith)); validConditionsList.Add(new OperatorItem(ConditionOperator.DoesNotBeginWith)); diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index f654d1f4..981bb384 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -181,6 +181,7 @@ protected override ControlValidationResult ValidateControl(Control control) case AttributeTypeCode.Status: case AttributeTypeCode.Picklist: case AttributeTypeCode.BigInt: + case AttributeTypeCode.EntityName: int intvalue; if (!int.TryParse(value, out intvalue)) { @@ -208,7 +209,6 @@ protected override ControlValidationResult ValidateControl(Control control) break; case AttributeTypeCode.String: case AttributeTypeCode.Memo: - case AttributeTypeCode.EntityName: case AttributeTypeCode.Virtual: break; case AttributeTypeCode.PartyList: @@ -363,6 +363,18 @@ private void UpdateValueField() cmbValue.Items.AddRange(options.Options.Select(o => new OptionsetItem(o)).ToArray()); cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; } + else if (attribute.Metadata is EntityNameAttributeMetadata) + { + var entities = fxb.GetDisplayEntities(); + if (entities != null) + { + foreach (var entity in entities) + { + cmbValue.Items.Add(new EntityNameItem(entity.Value)); + } + cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; + } + } else if (attribute.Metadata is LookupAttributeMetadata lookupmeta) { if (fxb.settings.UseLookup) diff --git a/FetchXmlBuilder/FetchXmlBuilder.csproj b/FetchXmlBuilder/FetchXmlBuilder.csproj index 91ee292e..6d5f25ed 100644 --- a/FetchXmlBuilder/FetchXmlBuilder.csproj +++ b/FetchXmlBuilder/FetchXmlBuilder.csproj @@ -182,6 +182,7 @@ + From 24dad9fd7884d98659bec9747d69ffd6d5b5b6f0 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Mon, 10 Aug 2020 21:51:18 +0100 Subject: [PATCH 06/18] Ensure picklist conditions are shown correctly on first attempt --- FetchXmlBuilder/Controls/conditionControl.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index 981bb384..8d92e3d4 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -361,18 +361,19 @@ private void UpdateValueField() !(attribute.Metadata is EntityNameAttributeMetadata)) { cmbValue.Items.AddRange(options.Options.Select(o => new OptionsetItem(o)).ToArray()); + var value = cmbValue.Text; cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; + cmbValue.SelectedItem = cmbValue.Items.OfType().FirstOrDefault(i => i.GetValue() == value); } else if (attribute.Metadata is EntityNameAttributeMetadata) { var entities = fxb.GetDisplayEntities(); if (entities != null) { - foreach (var entity in entities) - { - cmbValue.Items.Add(new EntityNameItem(entity.Value)); - } + cmbValue.Items.AddRange(entities.Select(e => new EntityNameItem(e.Value)).ToArray()); + var value = cmbValue.Text; cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; + cmbValue.SelectedItem = cmbValue.Items.OfType().FirstOrDefault(i => i.GetValue() == value); } } else if (attribute.Metadata is LookupAttributeMetadata lookupmeta) @@ -413,6 +414,7 @@ private void UpdateValueField() } } } + if (valueType == null) { cmbValue.Text = ""; @@ -421,13 +423,8 @@ private void UpdateValueField() else { cmbValue.Enabled = true; - - if (cmbValue.Items.Count > 0 && cmbValue.SelectedIndex == -1 && !string.IsNullOrWhiteSpace(cmbValue.Text)) - { - var item = cmbValue.Items.OfType().FirstOrDefault(i => i.ToString() == cmbValue.Text); - cmbValue.SelectedItem = item; - } } + if (valueType == AttributeTypeCode.Lookup || valueType == AttributeTypeCode.Customer || valueType == AttributeTypeCode.Owner || From fe1474b857d64d8b3c3927283b68a67595f6055e Mon Sep 17 00:00:00 2001 From: Jonas Rapp Date: Sun, 23 Aug 2020 11:25:42 +0200 Subject: [PATCH 07/18] Fixed logo url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ed86d2f..e7cad985 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![FXB](https://fetchxmlbuilder.com/FXB150) FetchXML Builder for XrmToolBox +# ![FXB](https://fetchxmlbuilder.com/origdocs/fxb150) FetchXML Builder for XrmToolBox [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=rappen/FetchXMLBuilder)](https://dependabot.com) From bcb701dd2df27fd96839635884802559b13d1b31 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2020 11:53:53 +0200 Subject: [PATCH 08/18] Bump Microsoft.CrmSdk.XrmTooling.CoreAssembly from 9.1.0.49 to 9.1.0.51 (#355) Bumps Microsoft.CrmSdk.XrmTooling.CoreAssembly from 9.1.0.49 to 9.1.0.51. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- XmlEditorUtils/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XmlEditorUtils/packages.config b/XmlEditorUtils/packages.config index a771037b..87d2ed54 100644 --- a/XmlEditorUtils/packages.config +++ b/XmlEditorUtils/packages.config @@ -3,7 +3,7 @@ - + From 993a58e877c4ea50af727eddcef01b14dbe1ae4a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2020 11:54:07 +0200 Subject: [PATCH 09/18] Bump Microsoft.CrmSdk.XrmTooling.WpfControls in /FetchXmlBuilder (#356) Bumps Microsoft.CrmSdk.XrmTooling.WpfControls from 9.1.0.49 to 9.1.0.51. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- FetchXmlBuilder/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FetchXmlBuilder/packages.config b/FetchXmlBuilder/packages.config index 1abb61c4..5497e0e0 100644 --- a/FetchXmlBuilder/packages.config +++ b/FetchXmlBuilder/packages.config @@ -8,7 +8,7 @@ - + From 414bc559c413a4b633e8c7f6a4a0ad5d8a251482 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2020 11:54:45 +0200 Subject: [PATCH 10/18] Bump Microsoft.CrmSdk.XrmTooling.CoreAssembly in /FetchXmlBuilder (#357) Bumps Microsoft.CrmSdk.XrmTooling.CoreAssembly from 9.1.0.49 to 9.1.0.51. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Jonas Rapp --- FetchXmlBuilder/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FetchXmlBuilder/packages.config b/FetchXmlBuilder/packages.config index 5497e0e0..1488e801 100644 --- a/FetchXmlBuilder/packages.config +++ b/FetchXmlBuilder/packages.config @@ -7,7 +7,7 @@ - + From f8dc7c614ed65d8d086cf76e53b5ea94ea148d19 Mon Sep 17 00:00:00 2001 From: Jonas Rapp Date: Sun, 23 Aug 2020 12:21:40 +0200 Subject: [PATCH 11/18] Updated csproj file with package versions not updated by dependabot --- FetchXmlBuilder/FetchXmlBuilder.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FetchXmlBuilder/FetchXmlBuilder.csproj b/FetchXmlBuilder/FetchXmlBuilder.csproj index 6d5f25ed..3216be08 100644 --- a/FetchXmlBuilder/FetchXmlBuilder.csproj +++ b/FetchXmlBuilder/FetchXmlBuilder.csproj @@ -62,7 +62,7 @@ ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.5.2.8\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.49\lib\net462\Microsoft.Rest.ClientRuntime.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.51\lib\net462\Microsoft.Rest.ClientRuntime.dll ..\packages\Microsoft.Web.Xdt.3.1.0\lib\net40\Microsoft.Web.XmlTransform.dll @@ -77,16 +77,16 @@ ..\packages\Microsoft.CrmSdk.Workflow.9.0.2.26\lib\net462\Microsoft.Xrm.Sdk.Workflow.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.Connector.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.Connector.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.CrmConnectControl.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.CrmConnectControl.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.Ui.Styles.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.Ui.Styles.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.WebResourceUtility.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.WebResourceUtility.dll ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll From e019ec96abbf42cf9d33b7a259af192a3060bc95 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 23 Aug 2020 16:21:00 +0100 Subject: [PATCH 12/18] Reverted changes to LIKE handling --- .../AppCode/OData4CodeGenerator.cs | 46 ++----------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs index b13f7b43..8221591e 100644 --- a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs +++ b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs @@ -328,47 +328,16 @@ private static string GetCondition(string entityName, condition condition, Fetch result += " ne null"; break; case @operator.like: + result = $"contains({attrMeta.LogicalName}, '{condition.value}')"; + break; case @operator.notlike: - string func; - var value = condition.value; - - if (value.StartsWith("%") && value.EndsWith("%") && value.Length >= 2) - { - func = "contains"; - value = value.Substring(1, value.Length - 2); - } - else if (value.StartsWith("%")) - { - func = "endswith"; - value = value.Substring(1); - } - else if (value.EndsWith("%")) - { - func = "startswith"; - value = value.Substring(0, value.Length - 1); - } - else - { - if (ContainsLikeWildcards(value)) - throw new Exception("Unsupported LIKE wildcard " + condition.value); - - result += " eq "; - break; - } - - if (ContainsLikeWildcards(value)) - throw new Exception("Unsupported LIKE wildcard " + condition.value); - - result = $"{func}({attrMeta.LogicalName}, '{FormatValue(typeof(string), value)}')"; - - if (condition.@operator == @operator.notlike) - result = "not " + result; + result = $"not contains({attrMeta.LogicalName}, '{condition.value}')"; break; case @operator.beginswith: - result = $"startswith({attrMeta.LogicalName}, '{FormatValue(typeof(string), condition.value)}')"; + result = $"startswith({attrMeta.LogicalName}, '{condition.value}')"; break; case @operator.endswith: - result = $"endswith({attrMeta.LogicalName}, '{FormatValue(typeof(string), condition.value)}')"; + result = $"endswith({attrMeta.LogicalName}, '{condition.value}')"; break; case @operator.above: function = "Above"; @@ -705,11 +674,6 @@ private static string GetCondition(string entityName, condition condition, Fetch return result; } - private static bool ContainsLikeWildcards(string value) - { - return value.Contains("%") || value.Contains("_"); - } - private static string GetPropertyName(AttributeMetadata attr) { if (attr is LookupAttributeMetadata) From 5408f6b51be0fdae4d79b461e43ecd7cc8701845 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 23 Aug 2020 16:35:13 +0100 Subject: [PATCH 13/18] Translate between like, contains, ends-with and begins-with conditions --- .../Controls/FetchXmlElementControlBase.cs | 7 +- FetchXmlBuilder/Controls/conditionControl.cs | 101 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs index 02cfc46b..650ffead 100644 --- a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs +++ b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs @@ -151,10 +151,15 @@ protected virtual void SaveInternal(bool keyPress) { if (IsInitialized) { - SendSaveMessage(ControlUtils.GetAttributesCollection(this.Controls, true), keyPress); + SendSaveMessage(GetAttributesCollection(), keyPress); } } + protected virtual Dictionary GetAttributesCollection() + { + return ControlUtils.GetAttributesCollection(this.Controls, true); + } + protected virtual ControlValidationResult ValidateControl(Control control) { return null; diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index 8d92e3d4..f7b1ca1c 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -72,6 +72,47 @@ protected override void SaveInternal(bool silent) base.SaveInternal(silent); } + protected override Dictionary GetAttributesCollection() + { + var attr = base.GetAttributesCollection(); + + if (attr.TryGetValue("operator", out var op) && attr.TryGetValue("value", out var value)) + { + if (op == "begins-with") + { + attr["operator"] = "like"; + attr["value"] = value + "%"; + } + else if (op == "not-begin-with") + { + attr["operator"] = "not-like"; + attr["value"] = value + "%"; + } + else if (op == "ends-with") + { + attr["operator"] = "like"; + attr["value"] = "%" + value; + } + else if (op == "not-end-with") + { + attr["operator"] = "not-like"; + attr["value"] = "%" + value; + } + else if (op == "contains") + { + attr["operator"] = "like"; + attr["value"] = "%" + value + "%"; + } + else if (op == "does-not-contain") + { + attr["operator"] = "not-like"; + attr["value"] = "%" + value + "%"; + } + } + + return attr; + } + protected override ControlValidationResult ValidateControl(Control control) { if (control == cmbAttribute) @@ -305,6 +346,66 @@ private void RefreshAttributes() EndInit(); RefreshOperators(); UpdateValueField(); + NormalizeLike(); + } + + private void NormalizeLike() + { + var op = cmbOperator.SelectedItem as OperatorItem; + + if (op == null) + { + return; + } + + var value = cmbValue.Text; + + if (String.IsNullOrWhiteSpace(value)) + { + return; + } + + if (op.GetValue() == "like" || op.GetValue() == "not-like") + { + string newOp = null; + + if (value.StartsWith("%") && value.EndsWith("%") && value.Length >= 2) + { + newOp = "contains"; + value = value.Substring(1, value.Length - 2); + } + else if (value.StartsWith("%")) + { + newOp = "ends-with"; + value = value.Substring(1); + } + else if (value.EndsWith("%")) + { + newOp = "begins-with"; + value = value.Substring(0, value.Length - 1); + } + + if (newOp != null) + { + if (op.GetValue() == "not-like") + { + newOp = "not-" + newOp.Replace("s-", "-"); + + if (newOp == "not-contains") + { + newOp = "does-not-contain"; + } + } + + var newOpItem = cmbOperator.Items.OfType().FirstOrDefault(o => o.GetValue() == newOp); + + if (newOpItem != null) + { + cmbOperator.SelectedItem = newOpItem; + cmbValue.Text = value; + } + } + } } private void RefreshOperators() From 08669fcfbb9f6cab70488bae6cae0c56c39b8ba7 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 23 Aug 2020 16:36:54 +0100 Subject: [PATCH 14/18] Reverted more LIKE changes --- .../Controls/FetchXmlElementControlBase.cs | 7 +- FetchXmlBuilder/Controls/conditionControl.cs | 96 ------------------- 2 files changed, 1 insertion(+), 102 deletions(-) diff --git a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs index 2bc837f2..02cfc46b 100644 --- a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs +++ b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs @@ -151,15 +151,10 @@ protected virtual void SaveInternal(bool keyPress) { if (IsInitialized) { - SendSaveMessage(GetAttributesCollection(), keyPress); + SendSaveMessage(ControlUtils.GetAttributesCollection(this.Controls, true), keyPress); } } - protected virtual Dictionary GetAttributesCollection() - { - return ControlUtils.GetAttributesCollection(this.Controls, true); - } - protected virtual ControlValidationResult ValidateControl(Control control) { return null; diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index 8cac9539..aa90aa2b 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -72,47 +72,6 @@ protected override void SaveInternal(bool silent) base.SaveInternal(silent); } - protected override Dictionary GetAttributesCollection() - { - var attr = base.GetAttributesCollection(); - - if (attr.TryGetValue("operator", out var op) && attr.TryGetValue("value", out var value)) - { - if (op == "begins-with") - { - attr["operator"] = "like"; - attr["value"] = value + "%"; - } - else if (op == "not-begin-with") - { - attr["operator"] = "not-like"; - attr["value"] = value + "%"; - } - else if (op == "ends-with") - { - attr["operator"] = "like"; - attr["value"] = "%" + value; - } - else if (op == "not-end-with") - { - attr["operator"] = "not-like"; - attr["value"] = "%" + value; - } - else if (op == "contains") - { - attr["operator"] = "like"; - attr["value"] = "%" + value + "%"; - } - else if (op == "does-not-contain") - { - attr["operator"] = "not-like"; - attr["value"] = "%" + value + "%"; - } - } - - return attr; - } - protected override ControlValidationResult ValidateControl(Control control) { if (control == cmbAttribute) @@ -351,61 +310,6 @@ private void RefreshAttributes() RefreshOperators(); UpdateValueField(); ReFillControl(cmbValueOf); - NormalizeLike(); - } - - private void NormalizeLike() - { - var op = cmbOperator.SelectedItem as OperatorItem; - - if (op == null) - { - return; - } - - var value = cmbValue.Text; - - if (String.IsNullOrWhiteSpace(value)) - { - return; - } - - if (op.GetValue() == "like" || op.GetValue() == "not-like") - { - string newOp = null; - - if (value.StartsWith("%") && value.EndsWith("%") && value.Length >= 2) - { - newOp = "contains"; - value = value.Substring(1, value.Length - 2); - } - else if (value.StartsWith("%")) - { - newOp = "ends-with"; - value = value.Substring(1); - } - else if (value.EndsWith("%")) - { - newOp = "begins-with"; - value = value.Substring(0, value.Length - 1); - } - - if (newOp != null) - { - if (op.GetValue() == "not-like") - { - newOp = "not-" + newOp.Replace("s-", "-"); - } - - var newOpItem = cmbOperator.Items.OfType().FirstOrDefault(o => o.GetValue() == newOp); - - if (newOpItem != null) - { - cmbOperator.SelectedItem = newOpItem; - cmbValue.Text = value; - } - } - } } private void RefreshOperators() From 068fa7139b94cb7226371116641f9cb267966bc7 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 23 Aug 2020 16:04:42 +0100 Subject: [PATCH 15/18] Fix OData URL generation for queries containing multiple nodes or URL special characters --- .../AppCode/OData4CodeGenerator.cs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs index 8221591e..f8412091 100644 --- a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs +++ b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Web; namespace Cinteros.Xrm.FetchXmlBuilder.AppCode { @@ -233,15 +234,31 @@ private static string GetFilter(FetchEntityType entity, FetchXmlBuilder sender, var and = true; foreach (filter filteritem in filteritems) { - resultList.Append(GetFilter(entity.name, filteritem, sender)); + var filterText = GetFilter(entity.name, filteritem, sender); + + if (String.IsNullOrWhiteSpace(filterText)) + { + continue; + } + + if (resultList.Length > 0) + { + resultList.Append(" and "); + } + + resultList.Append(filterText); + if (filteritem.type == filterType.or) and = false; } + var result = resultList.ToString(); - if (result.StartsWith("(") && result.EndsWith(")")) + + if (filteritems.Count == 1 && result.StartsWith("(") && result.EndsWith(")")) { result = result.Substring(1, result.Length - 2); } + if (!String.IsNullOrEmpty(expandFilter)) { if (!and) @@ -328,16 +345,16 @@ private static string GetCondition(string entityName, condition condition, Fetch result += " ne null"; break; case @operator.like: - result = $"contains({attrMeta.LogicalName}, '{condition.value}')"; + result = $"contains({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; break; case @operator.notlike: - result = $"not contains({attrMeta.LogicalName}, '{condition.value}')"; + result = $"not contains({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; break; case @operator.beginswith: - result = $"startswith({attrMeta.LogicalName}, '{condition.value}')"; + result = $"startswith({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; break; case @operator.endswith: - result = $"endswith({attrMeta.LogicalName}, '{condition.value}')"; + result = $"endswith({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; break; case @operator.above: function = "Above"; @@ -613,13 +630,13 @@ private static string GetCondition(string entityName, condition condition, Fetch if (!String.IsNullOrEmpty(function)) { if (functionParameters == Int32.MaxValue) - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{attrMeta.LogicalName}',PropertyValues=[{String.Join(",", condition.Items.Select(i => FormatValue(functionParameterType, i.Value)))}])"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(attrMeta.LogicalName)}',PropertyValues=[{String.Join(",", condition.Items.Select(i => FormatValue(functionParameterType, i.Value)))}])"; else if (functionParameters == 0) - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{attrMeta.LogicalName}')"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(attrMeta.LogicalName)}')"; else if (functionParameters == 1) - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{attrMeta.LogicalName}',PropertyValue={FormatValue(functionParameterType, condition.value)})"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(attrMeta.LogicalName)}',PropertyValue={FormatValue(functionParameterType, condition.value)})"; else - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{attrMeta.LogicalName}',{String.Join(",", condition.Items.Select((i, idx) => $"Property{idx + 1}={FormatValue(functionParameterType, i.Value)}"))})"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(attrMeta.LogicalName)}',{String.Join(",", condition.Items.Select((i, idx) => $"Property{idx + 1}={FormatValue(functionParameterType, i.Value)}"))})"; } if (!string.IsNullOrEmpty(condition.value) && !result.Contains("(")) @@ -685,7 +702,7 @@ private static string GetPropertyName(AttributeMetadata attr) private static string FormatValue(Type type, string s) { if (type == typeof(string)) - return "'" + s.Replace("'", "''") + "'"; + return "'" + HttpUtility.UrlEncode(s.Replace("'", "''")) + "'"; if (type == typeof(DateTime)) { @@ -703,7 +720,7 @@ private static string FormatValue(Type type, string s) if (type == typeof(Guid)) return Guid.Parse(s).ToString(); - return Convert.ChangeType(s, type).ToString(); + return HttpUtility.UrlEncode(Convert.ChangeType(s, type).ToString()); } private static string GetOrder(FetchEntityType entity, FetchXmlBuilder sender) From c034e651df74c62d3213c1a6cff57c8e5ffb9c3d Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 23 Aug 2020 16:18:34 +0100 Subject: [PATCH 16/18] Added support for notbeingwith and notendwith --- .../AppCode/OData4CodeGenerator.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs index f8412091..08420082 100644 --- a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs +++ b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs @@ -345,16 +345,31 @@ private static string GetCondition(string entityName, condition condition, Fetch result += " ne null"; break; case @operator.like: - result = $"contains({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; - break; case @operator.notlike: - result = $"not contains({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; + result = $"contains({HttpUtility.UrlEncode(attrMeta.LogicalName)}, {FormatValue(typeof(string), condition.value)})"; + + if (condition.@operator == @operator.notlike) + { + result = "not " + result; + } break; case @operator.beginswith: - result = $"startswith({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; + case @operator.notbeginwith: + result = $"startswith({HttpUtility.UrlEncode(attrMeta.LogicalName)}, {FormatValue(typeof(string), condition.value)})"; + + if (condition.@operator == @operator.notbeginwith) + { + result = "not " + result; + } break; case @operator.endswith: - result = $"endswith({HttpUtility.UrlEncode(attrMeta.LogicalName)}, '{HttpUtility.UrlEncode(condition.value)}')"; + case @operator.notendwith: + result = $"endswith({HttpUtility.UrlEncode(attrMeta.LogicalName)}, {FormatValue(typeof(string), condition.value)})"; + + if (condition.@operator == @operator.notendwith) + { + result = "not " + result; + } break; case @operator.above: function = "Above"; From 25ba65b543d0fcd6221b00a5ddef241acb94ec9d Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Sun, 23 Aug 2020 16:49:16 +0100 Subject: [PATCH 17/18] Disable mnemonics in Power Automate parameter links so & in values is shown correctly --- FetchXmlBuilder/DockControls/FlowListControl.Designer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs b/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs index a58d193e..a167889a 100644 --- a/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs +++ b/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs @@ -105,6 +105,7 @@ private void InitializeComponent() this.linkSelect.TabStop = true; this.linkSelect.Tag = "Select formula"; this.linkSelect.Text = "N/A"; + this.linkSelect.UseMnemonic = false; this.linkSelect.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // lblCopied @@ -141,6 +142,7 @@ private void InitializeComponent() this.linkExpand.TabStop = true; this.linkExpand.Tag = "Expand Query"; this.linkExpand.Text = "N/A"; + this.linkExpand.UseMnemonic = false; this.linkExpand.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // linkTop @@ -156,6 +158,7 @@ private void InitializeComponent() this.linkTop.TabStop = true; this.linkTop.Tag = "Top Count"; this.linkTop.Text = "N/A"; + this.linkTop.UseMnemonic = false; this.linkTop.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // linkOrder @@ -171,6 +174,7 @@ private void InitializeComponent() this.linkOrder.TabStop = true; this.linkOrder.Tag = "Order By"; this.linkOrder.Text = "N/A"; + this.linkOrder.UseMnemonic = false; this.linkOrder.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // linkFilter @@ -186,6 +190,7 @@ private void InitializeComponent() this.linkFilter.TabStop = true; this.linkFilter.Tag = "Filter Query"; this.linkFilter.Text = "N/A"; + this.linkFilter.UseMnemonic = false; this.linkFilter.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // label5 From 10653b5ffd226cc7a2e0d830dc0dd5586e64b8b5 Mon Sep 17 00:00:00 2001 From: Mark Carrington Date: Thu, 27 Aug 2020 21:37:59 +0100 Subject: [PATCH 18/18] Fixed typo in ContainValues OData filter method --- FetchXmlBuilder/AppCode/OData4CodeGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs index 08420082..bcbfeec8 100644 --- a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs +++ b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs @@ -382,7 +382,7 @@ private static string GetCondition(string entityName, condition condition, Fetch functionParameters = Int32.MaxValue; break; case @operator.containvalues: - function = "ContainsValues"; + function = "ContainValues"; functionParameters = Int32.MaxValue; break; case @operator.notcontainvalues: