diff --git a/.github/workflows/buildjs.yml b/.github/workflows/buildjs.yml
index a6daad3..3535edf 100644
--- a/.github/workflows/buildjs.yml
+++ b/.github/workflows/buildjs.yml
@@ -3,7 +3,10 @@
name: buildjs
permissions:
- contents: write # Grants write access to repository content
+ contents: write
+ issues: read
+ checks: write
+ pull-requests: write
on:
workflow_call:
inputs:
diff --git a/src/broadcast-solution/Controls/fdn_Broadcast.AppModulePicker/ControlManifest.xml b/src/broadcast-solution/Controls/fdn_Broadcast.AppModulePicker/ControlManifest.xml
index 564126c..fcaf640 100644
--- a/src/broadcast-solution/Controls/fdn_Broadcast.AppModulePicker/ControlManifest.xml
+++ b/src/broadcast-solution/Controls/fdn_Broadcast.AppModulePicker/ControlManifest.xml
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/Entity.xml b/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/Entity.xml
index e9870b8..fcdfa9b 100644
--- a/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/Entity.xml
+++ b/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/Entity.xml
@@ -140,6 +140,69 @@
+
+ picklist
+ fdn_actiontype
+ fdn_actiontype
+ none
+ ValidForAdvancedFind|ValidForForm|ValidForGrid
+ auto
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1.0.0.0
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 1
+ 1
+ 0
+
+ 0
+ 0
+ 0
+ 0
+ -1
+
+ picklist
+ 1.0.0.0
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
nvarchar
fdn_appmoduleid
@@ -220,6 +283,90 @@
+
+ nvarchar
+ fdn_buttonactionurl
+ fdn_buttonactionurl
+ none
+ ValidForAdvancedFind|ValidForForm|ValidForGrid
+ auto
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1.0
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 1
+ 1
+ 0
+
+ 0
+ 0
+ 0
+ 0
+ url
+ 500
+ 1000
+
+
+
+
+
+
+
+
+
+
+ nvarchar
+ fdn_buttondefaulttext
+ fdn_buttondefaulttext
+ none
+ ValidForAdvancedFind|ValidForForm|ValidForGrid
+ auto
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1.0
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 1
+ 1
+ 0
+
+ 0
+ 0
+ 0
+ 0
+ text
+ 100
+ 200
+
+
+
+
+
+
+
+
+
nvarchar
fdn_description
diff --git a/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}.xml b/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}.xml
index 83d637c..4a30ae6 100644
--- a/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}.xml
+++ b/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}.xml
@@ -149,6 +149,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
@@ -240,6 +297,16 @@
+
+
+
+
+
+
+
+
+
+
1
1
diff --git a/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}_managed.xml b/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}_managed.xml
index e8848e7..d2b7820 100644
--- a/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}_managed.xml
+++ b/src/broadcast-solution/Entities/fdn_BroadcastAppNotification/FormXml/main/{e457ccdb-b279-49d1-ae85-1ffae404cc55}_managed.xml
@@ -41,6 +41,7 @@
+
|
@@ -141,6 +142,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
@@ -229,6 +284,16 @@
+
+
+
+
+
+
+
+
+
+
1
1
diff --git a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/Entity.xml b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/Entity.xml
index 6c46422..c6b9d9d 100644
--- a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/Entity.xml
+++ b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/Entity.xml
@@ -140,12 +140,55 @@
+
+ nvarchar
+ fdn_actionbuttondisplaytext
+ fdn_actionbuttondisplaytext
+ none
+ ValidForAdvancedFind|ValidForForm|ValidForGrid
+ auto
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1.0
+ 1
+ 1
+ 1
+ 1
+ 1
+ 0
+ 0
+ 0
+ 1
+ 1
+ 0
+
+ 0
+ 0
+ 0
+ 0
+ text
+ 100
+ 200
+
+
+
+
+
+
+
+
+
lookup
fdn_appnotificationconfigid
fdn_appnotificationconfigid
required
ValidForAdvancedFind|ValidForForm|ValidForGrid
+ auto
1
1
1
diff --git a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}.xml b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}.xml
index 742c24b..b626160 100644
--- a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}.xml
+++ b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}.xml
@@ -63,6 +63,23 @@
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
diff --git a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}_managed.xml b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}_managed.xml
index 5b7c417..8f57140 100644
--- a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}_managed.xml
+++ b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/main/{16ac9028-5ff7-48d4-99cc-a7560b2d828f}_managed.xml
@@ -59,6 +59,22 @@
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
diff --git a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}.xml b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}.xml
index 57a28b4..8e949e0 100644
--- a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}.xml
+++ b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}.xml
@@ -59,6 +59,15 @@
+
+ |
+
+
+
+
+
+ |
+
diff --git a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}_managed.xml b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}_managed.xml
index b6a0ed2..cd2efae 100644
--- a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}_managed.xml
+++ b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/FormXml/quickCreate/{93dbb73e-9d86-f011-b4cc-6045bdcd660b}_managed.xml
@@ -23,6 +23,7 @@
+
|
@@ -31,6 +32,7 @@
+
|
@@ -39,6 +41,7 @@
+
|
@@ -47,6 +50,7 @@
+
|
@@ -55,6 +59,14 @@
+
+ |
+
+
+
+
+ |
+
diff --git a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/SavedQueries/{b0a3afae-2afe-463a-a657-cf9725b3dccf}.xml b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/SavedQueries/{b0a3afae-2afe-463a-a657-cf9725b3dccf}.xml
index e09e79c..0ce2f9c 100644
--- a/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/SavedQueries/{b0a3afae-2afe-463a-a657-cf9725b3dccf}.xml
+++ b/src/broadcast-solution/Entities/fdn_LocalizedNotificationContent/SavedQueries/{b0a3afae-2afe-463a-a657-cf9725b3dccf}.xml
@@ -12,6 +12,7 @@
|
|
+ |
@@ -25,6 +26,7 @@
+
diff --git a/src/broadcast-solution/Other/Customizations.xml b/src/broadcast-solution/Other/Customizations.xml
index 5a97164..690b286 100644
--- a/src/broadcast-solution/Other/Customizations.xml
+++ b/src/broadcast-solution/Other/Customizations.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/src/broadcast-solution/Other/Solution.xml b/src/broadcast-solution/Other/Solution.xml
index 113e1fc..7cadda8 100644
--- a/src/broadcast-solution/Other/Solution.xml
+++ b/src/broadcast-solution/Other/Solution.xml
@@ -1,5 +1,5 @@
-
+
BroadcastAppNotificationsSolution
diff --git a/src/broadcast-solution/WebResources/fdn_/scripts/broadcast.js b/src/broadcast-solution/WebResources/fdn_/scripts/broadcast.js
index daa3d2f..7d9ea2f 100644
--- a/src/broadcast-solution/WebResources/fdn_/scripts/broadcast.js
+++ b/src/broadcast-solution/WebResources/fdn_/scripts/broadcast.js
@@ -1,4 +1,4 @@
-(function(){"use strict";function g(n,e,t){I(n,e,t,"get"),I(n,e,t,"value")}function I(n,e,t,o){if(typeof t[o]=="function"){const i=Symbol(e),a=t[o];t[o]=function(...r){let d=this,l=d[i];return typeof l>"u"&&(l=d[i]=a.apply(this,r)),l}}}class c{constructor(e,t){this._cultures={1033:e,1036:t}}toString(){const e=Xrm.Utility.getGlobalContext().userSettings.languageId;return this._cultures[e]||this._cultures[1033]}}const s={general:{MESSAGE_RECORD_DIRTY:new c("You have pending changes, please save the record.","Vous avez des modifications en attente de sauvegarde. Veuillez sauvegarder."),WAIT_POPUPTITLE:new c("Wait!","Attendez!"),CANCEL_BTN:new c("Cancel","Annuler"),PROCESSING:new c("Processing...","En traitement...")},broadcast:{PUBLISH_BTN:new c("Publish","Publier"),UNPUBLISH_BTN:new c("Unpublish","Dépublier"),NOTIFICATION_CONFIRMPUBLISH_TITLE:new c("Confirm Publish","Confirmer la publication"),NOTIFICATION_CONFIRMPUBLISH_CONTENT:new c(`Are you sure you want to publish this notification?\r
+(function(){"use strict";function f(e,t,n){y(e,t,n,"get"),y(e,t,n,"value")}function y(e,t,n,i){if(typeof n[i]=="function"){const o=Symbol(t),a=n[i];n[i]=function(...s){let l=this,d=l[o];return typeof d>"u"&&(d=l[o]=a.apply(this,s)),d}}}class c{constructor(t,n){this._cultures={1033:t,1036:n}}toString(){const t=Xrm.Utility.getGlobalContext().userSettings.languageId;return this._cultures[t]||this._cultures[1033]}}const r={general:{MESSAGE_RECORD_DIRTY:new c("You have pending changes, please save the record.","Vous avez des modifications en attente de sauvegarde. Veuillez sauvegarder."),WAIT_POPUPTITLE:new c("Wait!","Attendez!"),CANCEL_BTN:new c("Cancel","Annuler"),PROCESSING:new c("Processing...","En traitement...")},broadcast:{PUBLISH_BTN:new c("Publish","Publier"),UNPUBLISH_BTN:new c("Unpublish","Dépublier"),NOTIFICATION_CONFIRMPUBLISH_TITLE:new c("Confirm Publish","Confirmer la publication"),NOTIFICATION_CONFIRMPUBLISH_CONTENT:new c(`Are you sure you want to publish this notification?\r
\r
This notification will be shown to users.`,`Êtes-vous sûr de vouloir publier cette notification?\r
\r
@@ -6,5 +6,5 @@ Cette notification sera affichée aux utilisateurs.`),NOTIFICATION_CONFIRMUNPUBL
\r
This notification will no longer be shown to users.`,`Êtes-vous sûr de vouloir dépublier cette notification?\r
\r
-Cette notification ne sera plus affichée aux utilisateurs.`)}};function N(n){return n.replace("{","").replace("}","")}async function A(n){return n.data.entity.getIsDirty()?(await Xrm.Navigation.openAlertDialog({title:s.general.WAIT_POPUPTITLE.toString(),text:s.general.MESSAGE_RECORD_DIRTY.toString()}),!0):!1}function C(n,e,t,o){n.getAttribute(e)?.addOnChange(t.bind(o))}function w(){switch(Xrm.Utility.getGlobalContext().userSettings.languageId){case 1036:return 794560002;default:return 794560001}}class U{constructor(e,t){this.webApi=e,this._storage=t,this._cacheKey="broadcast_published_{appid}",this._expirationInSeconds=60*5,this._userLang=w()}_getExpiration(){return Date.now()+this._expirationInSeconds*1e3}async getPublishedNotifications(e){const t=this._cacheKey.replace("{appid}",e),o=this._storage.getItem(t),i=Date.now();if(o){const P=JSON.parse(o);if(P.timestamp>i)return P.value;this._storage.removeItem(t)}const a=`&$filter=statuscode eq 2 and fdn_appmoduleid eq '${e}'&$orderby=fdn_level asc`,l=`?$select=${["fdn_appmoduleid","fdn_message","fdn_broadcastappnotificationid","fdn_level"].join(",")}`+"&$expand=fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification($select=fdn_contentmessage,fdn_language;$filter=statecode eq 0)"+a,p=(await this.webApi.retrieveMultipleRecords("fdn_broadcastappnotification",l)).entities,m={value:p.map(v),timestamp:this._getExpiration()};return this._storage.setItem(t,JSON.stringify(m)),p}async Publish(e){await Xrm.WebApi.updateRecord("fdn_broadcastappnotification",e,{statecode:1,statuscode:2})}async Unpublish(e){await Xrm.WebApi.updateRecord("fdn_broadcastappnotification",e,{statecode:0,statuscode:1})}}function v(n){let t=T(n,["fdn_appmoduleid","fdn_message","fdn_broadcastappnotificationid","fdn_level","fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification"]);const o=t.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification;return!o||o.length===0||o.map(i=>T(i,["fdn_contentmessage","fdn_language"])),t}function T(n,e){for(const t in n)e.includes(t)||delete n[t];return n}class L{constructor(e){this._storage=e,this._storageCacheKey="broascast_{appid}"}getCachedNotifications(e){const t=this._storageCacheKey.replace("{appid}",e),o=this._storage.getItem(t);return o?JSON.parse(o):[]}setCachedNotifications(e,t){const o=this._storageCacheKey.replace("{appid}",e),i=t??[];this._storage.setItem(o,JSON.stringify(i))}}const y=new L(window.localStorage),_=new U(Xrm.WebApi,window.sessionStorage);var u=(n=>(n[n.Success=79456e4]="Success",n[n.Danger=794560001]="Danger",n[n.Warning=794560002]="Warning",n[n.Information=794560003]="Information",n))(u||{});class E{async renderNotifications(e){try{const t=Xrm.Utility.getPageContext()?.input?.pageType;if(!t||t!==e)return!1;const o=w();console.log("broadcast renderNotifications");const i=await Xrm.Utility.getGlobalContext().getCurrentAppProperties();if(!i||!i.appId)return!1;const r=y.getCachedNotifications(i.appId).map(f=>Xrm.App.clearGlobalNotification(f.uid)),d=await _.getPublishedNotifications(i.appId);await Promise.all(r);const l=[];for(const f of d){const p=R(f,o),m=await Xrm.App.addGlobalNotification(p);l.push({uid:m,data:f})}y.setCachedNotifications(i.appId,l)}catch(t){console.error(t)}finally{return!1}}}function R(n,e){const t=B(n.fdn_level);let o=n.fdn_message;const i=n.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification;return i&&i.length>0&&(o=i.find(a=>a.fdn_language===e)?.fdn_contentmessage??n.fdn_message),{level:t,message:o,showCloseButton:!1,type:2}}function B(n){switch(n){case u.Success:return 1;case u.Danger:return 2;case u.Warning:return 3;case u.Information:return 4;default:return 4}}class D{async PublishNotification(e){const t=N(e.data.entity.getId());if(await A(e))return;const i={text:s.broadcast.NOTIFICATION_CONFIRMPUBLISH_CONTENT.toString(),title:s.broadcast.NOTIFICATION_CONFIRMPUBLISH_TITLE.toString(),confirmButtonLabel:s.broadcast.PUBLISH_BTN.toString(),cancelButtonLabel:s.general.CANCEL_BTN.toString()};(await Xrm.Navigation.openConfirmDialog(i)).confirmed&&(Xrm.Utility.showProgressIndicator(s.general.PROCESSING.toString()),await _.Publish(t),e.data.refresh(!1),e.ui.refreshRibbon(!0),Xrm.Utility.closeProgressIndicator())}async UnpublishNotification(e){const t=N(e.data.entity.getId()),o={text:s.broadcast.NOTIFICATION_CONFIRMUNPUBLISH_CONTENT.toString(),title:s.broadcast.NOTIFICATION_CONFIRMUNPUBLISH_TITLE.toString(),confirmButtonLabel:s.broadcast.UNPUBLISH_BTN.toString(),cancelButtonLabel:s.general.CANCEL_BTN.toString()};(await Xrm.Navigation.openConfirmDialog(o)).confirmed&&(Xrm.Utility.showProgressIndicator(s.general.PROCESSING.toString()),await _.Unpublish(t),e.data.refresh(!1),e.ui.refreshRibbon(!0),Xrm.Utility.closeProgressIndicator())}}var F=Object.defineProperty,X=Object.getOwnPropertyDescriptor,S=(n,e,t,o)=>{for(var i=X(e,t),a=n.length-1,r;a>=0;a--)(r=n[a])&&(i=r(e,t,i)||i);return i&&F(e,t,i),i};class h{get application(){return new E}get fdn_broadcastappnotification(){return new D}}S([g],h.prototype,"application"),S([g],h.prototype,"fdn_broadcastappnotification");const x=new h;class M{async onLoad(e){const t=e.getFormContext();C(t,"fdn_language",this.onLanguageChange,this),C(t,"fdn_appnotificationconfigid",this.onConfigChange,this),t.ui.getFormType()==XrmEnum.FormType.Create&&b(t)}async onLanguageChange(e){const t=e.getFormContext();b(t)}async onConfigChange(e){const t=e.getFormContext();b(t)}}function b(n){const e=n.getAttribute("fdn_language"),t=n.getAttribute("fdn_appnotificationconfigid"),o=n.getAttribute("fdn_name"),a=(t.getValue()?t.getValue()[0]:null)?.name??"unknown",r=e.getSelectedOption();o.setValue(`${r.text} - ${a}`)}var z=Object.defineProperty,G=Object.getOwnPropertyDescriptor,H=(n,e,t,o)=>{for(var i=G(e,t),a=n.length-1,r;a>=0;a--)(r=n[a])&&(i=r(e,t,i)||i);return i&&z(e,t,i),i};class O{get fdn_localizednotificationcontent(){return new M}}H([g],O.prototype,"fdn_localizednotificationcontent");const $=new O,V={ribbon:x,forms:$};if(typeof window<"u"){const n=globalThis.XrmEnum??{FormType:{Undefined:0,Create:1,Update:2,ReadOnly:3,Disabled:4,QuickCreate:5,BulkEdit:6,ReadOptimized:11}};globalThis.XrmEnum=n,window.broadcast=window.broadcast??V}})();
+Cette notification ne sera plus affichée aux utilisateurs.`)}};function C(e){return e.replace("{","").replace("}","")}async function F(e){return e.data.entity.getIsDirty()?(await Xrm.Navigation.openAlertDialog({title:r.general.WAIT_POPUPTITLE.toString(),text:r.general.MESSAGE_RECORD_DIRTY.toString()}),!0):!1}function p(e,t,n,i){e.getAttribute(t)?.addOnChange(n.bind(i))}var u=(e=>(e[e.Success=79456e4]="Success",e[e.Danger=794560001]="Danger",e[e.Warning=794560002]="Warning",e[e.Information=794560003]="Information",e))(u||{}),g=(e=>(e[e.English=794560001]="English",e[e.French=794560002]="French",e))(g||{}),_=(e=>(e[e.WebLink=79456e4]="WebLink",e))(_||{});function S(){switch(Xrm.Utility.getGlobalContext().userSettings.languageId){case 1036:return g.French;default:return g.English}}class R{constructor(t,n){this.webApi=t,this._storage=n,this._cacheKey="broadcast_published_{appid}",this._expirationInSeconds=60*5,this._userLang=S()}_getExpiration(){return Date.now()+this._expirationInSeconds*1e3}async getPublishedNotifications(t){const n=this._cacheKey.replace("{appid}",t),i=this._storage.getItem(n),o=Date.now();if(i){const U=JSON.parse(i);if(U.timestamp>o)return U.value;this._storage.removeItem(n)}const a=`&$filter=statuscode eq 2 and fdn_appmoduleid eq '${t}'&$orderby=fdn_level asc`,s=["fdn_appmoduleid","fdn_message","fdn_broadcastappnotificationid","fdn_level","fdn_buttondefaulttext","fdn_buttonactionurl","fdn_actiontype"],d=`&$expand=fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification($select=${["fdn_language","fdn_contentmessage","fdn_actionbuttondisplaytext"].join(",")};$filter=statecode eq 0)`,N=`?$select=${s.join(",")}`+d+a,E=(await this.webApi.retrieveMultipleRecords("fdn_broadcastappnotification",N)).entities,Y={value:E.map(x),timestamp:this._getExpiration()};return this._storage.setItem(n,JSON.stringify(Y)),E}async Publish(t){await Xrm.WebApi.updateRecord("fdn_broadcastappnotification",t,{statecode:1,statuscode:2})}async Unpublish(t){await Xrm.WebApi.updateRecord("fdn_broadcastappnotification",t,{statecode:0,statuscode:1})}}function x(e){let n=T(e,["fdn_appmoduleid","fdn_message","fdn_broadcastappnotificationid","fdn_level","fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification","fdn_buttondefaulttext","fdn_buttonactionurl","fdn_actiontype"]);const i=n.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification;return!i||i.length===0||i.map(o=>T(o,["fdn_contentmessage","fdn_language","fdn_actionbuttondisplaytext"])),n}function T(e,t){for(const n in e)t.includes(n)||delete e[n];return e}class B{constructor(t){this._storage=t,this._storageCacheKey="broascast_{appid}"}getCachedNotifications(t){const n=this._storageCacheKey.replace("{appid}",t),i=this._storage.getItem(n);return i?JSON.parse(i):[]}setCachedNotifications(t,n){const i=this._storageCacheKey.replace("{appid}",t),o=n??[];this._storage.setItem(i,JSON.stringify(o))}}const D={[u.Success.toString()]:1,[u.Danger.toString()]:2,[u.Warning.toString()]:3,[u.Information.toString()]:4};class X{constructor(){this._buildAction=(t,n)=>{if(t.fdn_actiontype==_.WebLink)return{actionLabel:n?.fdn_actionbuttondisplaytext??t.fdn_buttondefaulttext,eventHandler:()=>{window.open(t.fdn_buttonactionurl,"_blank")}}}}create(t){const n=S(),i=D[t.fdn_level.toString()];let o=t.fdn_message;const a=t.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification,s=a&&a.length>0?a.find(l=>l.fdn_language===n):void 0;return s!=null&&(o=s.fdn_contentmessage??t.fdn_message),{level:i,message:o,showCloseButton:!1,type:2,action:this._buildAction(t,s)}}}const M=new X,w=new B(window.localStorage),b=new R(Xrm.WebApi,window.sessionStorage);class ${async renderNotifications(t){try{const n=Xrm.Utility.getPageContext()?.input?.pageType;if(!n||n!==t)return!1;console.log("broadcast renderNotifications");const i=await Xrm.Utility.getGlobalContext().getCurrentAppProperties();if(!i||!i.appId)return!1;const a=w.getCachedNotifications(i.appId).map(d=>Xrm.App.clearGlobalNotification(d.uid)),s=await b.getPublishedNotifications(i.appId);await Promise.all(a);const l=[];for(const d of s){const N=M.create(d),v=await Xrm.App.addGlobalNotification(N);l.push({uid:v,data:d})}w.setCachedNotifications(i.appId,l)}catch(n){console.error(n)}finally{return!1}}}class z{async PublishNotification(t){const n=C(t.data.entity.getId());if(await F(t))return;const o={text:r.broadcast.NOTIFICATION_CONFIRMPUBLISH_CONTENT.toString(),title:r.broadcast.NOTIFICATION_CONFIRMPUBLISH_TITLE.toString(),confirmButtonLabel:r.broadcast.PUBLISH_BTN.toString(),cancelButtonLabel:r.general.CANCEL_BTN.toString()};(await Xrm.Navigation.openConfirmDialog(o)).confirmed&&(Xrm.Utility.showProgressIndicator(r.general.PROCESSING.toString()),await b.Publish(n),t.data.refresh(!1),t.ui.refreshRibbon(!0),Xrm.Utility.closeProgressIndicator())}async UnpublishNotification(t){const n=C(t.data.entity.getId()),i={text:r.broadcast.NOTIFICATION_CONFIRMUNPUBLISH_CONTENT.toString(),title:r.broadcast.NOTIFICATION_CONFIRMUNPUBLISH_TITLE.toString(),confirmButtonLabel:r.broadcast.UNPUBLISH_BTN.toString(),cancelButtonLabel:r.general.CANCEL_BTN.toString()};(await Xrm.Navigation.openConfirmDialog(i)).confirmed&&(Xrm.Utility.showProgressIndicator(r.general.PROCESSING.toString()),await b.Unpublish(n),t.data.refresh(!1),t.ui.refreshRibbon(!0),Xrm.Utility.closeProgressIndicator())}}var H=Object.defineProperty,G=Object.getOwnPropertyDescriptor,O=(e,t,n,i)=>{for(var o=G(t,n),a=e.length-1,s;a>=0;a--)(s=e[a])&&(o=s(t,n,o)||o);return o&&H(t,n,o),o};class h{get application(){return new $}get fdn_broadcastappnotification(){return new z}}O([f],h.prototype,"application"),O([f],h.prototype,"fdn_broadcastappnotification");const V=new h;class W{async onLoad(t){const n=t.getFormContext();p(n,"fdn_language",this.onLanguageChange,this),p(n,"fdn_appnotificationconfigid",this.onConfigChange,this),n.ui.getFormType()==XrmEnum.FormType.Create&&m(n)}async onLanguageChange(t){const n=t.getFormContext();m(n)}async onConfigChange(t){const n=t.getFormContext();m(n)}}function m(e){const t=e.getAttribute("fdn_language"),n=e.getAttribute("fdn_appnotificationconfigid"),i=e.getAttribute("fdn_name"),a=(n.getValue()?n.getValue()[0]:null)?.name??"unknown",s=t.getSelectedOption();i.setValue(`${s.text} - ${a}`)}const A={[_.WebLink.toString()]:{name:"tab_action_section_url",mandatoryFields:["fdn_buttondefaulttext","fdn_buttonactionurl"]}};class k{async onLoad(t){const n=t.getFormContext();p(n,"fdn_actiontype",this.onActionTypeChange,this),P(n)}async onActionTypeChange(t){const n=t.getFormContext();P(n)}}function P(e){const t=e.getAttribute("fdn_actiontype"),n=e.ui.tabs.get("tab_action"),i=t.getValue()??-1;for(let o in A){const a=A[o],s=i.toString()===o;n.sections.get(a.name)?.setVisible(s),a.mandatoryFields.forEach(l=>{e.getAttribute(l)?.setRequiredLevel(s?"required":"none")})}}var j=Object.defineProperty,K=Object.getOwnPropertyDescriptor,L=(e,t,n,i)=>{for(var o=K(t,n),a=e.length-1,s;a>=0;a--)(s=e[a])&&(o=s(t,n,o)||o);return o&&j(t,n,o),o};class I{get fdn_localizednotificationcontent(){return new W}get fdn_broadcastappnotification(){return new k}}L([f],I.prototype,"fdn_localizednotificationcontent"),L([f],I.prototype,"fdn_broadcastappnotification");const q=new I,J={ribbon:V,forms:q};if(typeof window<"u"){const e=globalThis.XrmEnum??{FormType:{Undefined:0,Create:1,Update:2,ReadOnly:3,Disabled:4,QuickCreate:5,BulkEdit:6,ReadOptimized:11}};globalThis.XrmEnum=e,window.broadcast=window.broadcast??J}})();
//# sourceMappingURL=broadcast.js.map
diff --git a/src/broadcast-typescript/app/modules/common/utility/context.ts b/src/broadcast-typescript/app/modules/common/utility/context.ts
index d7c962f..5364016 100644
--- a/src/broadcast-typescript/app/modules/common/utility/context.ts
+++ b/src/broadcast-typescript/app/modules/common/utility/context.ts
@@ -1,9 +1,11 @@
+import { fdn_language } from "@app/modules/domain";
+
export function getUserLangFromGlobalContext(){
const userLang = Xrm.Utility.getGlobalContext().userSettings.languageId;
switch(userLang){
case 1036:
- return 794560002;
+ return fdn_language.French;
default:
- return 794560001;
+ return fdn_language.English;
}
}
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/domain/entities/fdn_broadcastappnotification.ts b/src/broadcast-typescript/app/modules/domain/entities/fdn_broadcastappnotification.ts
index 1fb3bdc..95e8419 100644
--- a/src/broadcast-typescript/app/modules/domain/entities/fdn_broadcastappnotification.ts
+++ b/src/broadcast-typescript/app/modules/domain/entities/fdn_broadcastappnotification.ts
@@ -1,4 +1,5 @@
import { entity ,fdn_localizednotificationcontent} from ".";
+import { fdn_actiontype } from "../optionsets";
export enum fdn_level {
Success = 794560000,
@@ -8,9 +9,13 @@ export enum fdn_level {
}
export class fdn_broadcastappnotification extends entity {
fdn_level?: fdn_level;
+ fdn_actiontype?: fdn_actiontype;
fdn_message?: string;
fdn_name?: string;
fdn_appmoduleid?: string
fdn_broadcastappnotificationid?: string;
+ fdn_buttondefaulttext?: string;
+ fdn_buttonactionurl?: string;
fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification?:fdn_localizednotificationcontent[];
+
}
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/domain/entities/fdn_localizednotificationcontent.ts b/src/broadcast-typescript/app/modules/domain/entities/fdn_localizednotificationcontent.ts
index 18542b6..81691af 100644
--- a/src/broadcast-typescript/app/modules/domain/entities/fdn_localizednotificationcontent.ts
+++ b/src/broadcast-typescript/app/modules/domain/entities/fdn_localizednotificationcontent.ts
@@ -1,11 +1,12 @@
import { entity } from ".";
export enum fdn_language {
- English = 794560000,
- French = 794560001,
+ English = 794560001,
+ French = 794560002,
}
export type fdn_localizednotificationcontent = entity &{
fdn_localizednotificationcontentid?:string;
fdn_language?:fdn_language;
fdn_name?:string;
fdn_contentmessage?:string;
+ fdn_actionbuttondisplaytext?:string;
}
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/domain/index.ts b/src/broadcast-typescript/app/modules/domain/index.ts
index 231fbe0..c626fbf 100644
--- a/src/broadcast-typescript/app/modules/domain/index.ts
+++ b/src/broadcast-typescript/app/modules/domain/index.ts
@@ -1,2 +1,3 @@
export * from './entities';
-export * from './model';
\ No newline at end of file
+export * from './model';
+export * from './optionsets';
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/domain/optionsets/fdn_actiontype.ts b/src/broadcast-typescript/app/modules/domain/optionsets/fdn_actiontype.ts
new file mode 100644
index 0000000..6e2fff5
--- /dev/null
+++ b/src/broadcast-typescript/app/modules/domain/optionsets/fdn_actiontype.ts
@@ -0,0 +1,3 @@
+export enum fdn_actiontype {
+ WebLink = 794560000
+}
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/domain/optionsets/index.ts b/src/broadcast-typescript/app/modules/domain/optionsets/index.ts
new file mode 100644
index 0000000..3f1f58c
--- /dev/null
+++ b/src/broadcast-typescript/app/modules/domain/optionsets/index.ts
@@ -0,0 +1 @@
+export * from './fdn_actiontype'
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/form/fdn_broadcastappnotification.ts b/src/broadcast-typescript/app/modules/form/fdn_broadcastappnotification.ts
new file mode 100644
index 0000000..97aa1e5
--- /dev/null
+++ b/src/broadcast-typescript/app/modules/form/fdn_broadcastappnotification.ts
@@ -0,0 +1,37 @@
+import * as utilities from "@app/modules/common/utility";
+import { fdn_actiontype } from "@app/modules/domain";
+const ActionTypeSectionMapping = {
+ [fdn_actiontype.WebLink.toString()]: {
+ name:"tab_action_section_url",
+ mandatoryFields:['fdn_buttondefaulttext','fdn_buttonactionurl']
+ }
+}
+export default class {
+ //broadcast.forms.fdn_broadcastappnotification.onLoad
+ async onLoad(executionContext: Xrm.Events.LoadEventContext) {
+ const formContext = executionContext.getFormContext();
+
+ utilities.registerOnChangeHandler(formContext, 'fdn_actiontype', this.onActionTypeChange, this);
+
+ displayActionSectionAccordingToType(formContext);
+ }
+ async onActionTypeChange(executionContext: Xrm.Events.EventContext) {
+ const formContext = executionContext.getFormContext();
+ displayActionSectionAccordingToType(formContext);
+ }
+}
+function displayActionSectionAccordingToType(formContext: Xrm.FormContext) {
+
+ const actionTypeAttribute = formContext.getAttribute("fdn_actiontype")!;
+ const tab = formContext.ui.tabs.get("tab_action")!;
+ const currentActionType = actionTypeAttribute.getValue() ?? -1;
+ for(let key in ActionTypeSectionMapping) {
+ const config = ActionTypeSectionMapping[key as keyof typeof ActionTypeSectionMapping];
+ const isSectionVisible = currentActionType.toString() === key;
+ tab.sections.get(config.name)?.setVisible(isSectionVisible);
+ config.mandatoryFields.forEach(fieldName => {
+ formContext.getAttribute(fieldName)?.setRequiredLevel(isSectionVisible ? "required" : "none");
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/form/index.ts b/src/broadcast-typescript/app/modules/form/index.ts
index a543784..729a039 100644
--- a/src/broadcast-typescript/app/modules/form/index.ts
+++ b/src/broadcast-typescript/app/modules/form/index.ts
@@ -1,10 +1,15 @@
import { lazy } from "@app/modules/common/system";
import fdn_localizednotificationcontent from "./fdn_localizednotificationcontent";
+import fdn_broadcastappnotification from "./fdn_broadcastappnotification";
class Forms{
@lazy
get fdn_localizednotificationcontent():fdn_localizednotificationcontent{
return new fdn_localizednotificationcontent();
}
+ @lazy
+ get fdn_broadcastappnotification():fdn_broadcastappnotification{
+ return new fdn_broadcastappnotification();
+ }
}
export const forms = new Forms();
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/ribbon/application.ts b/src/broadcast-typescript/app/modules/ribbon/application.ts
index d02a845..11bf503 100644
--- a/src/broadcast-typescript/app/modules/ribbon/application.ts
+++ b/src/broadcast-typescript/app/modules/ribbon/application.ts
@@ -1,6 +1,5 @@
-import { notificationStore,broadcastNotificationService } from "@app/modules/services";
-import { fdn_broadcastappnotification, fdn_level, NotificationUI } from "@app/modules/domain";
-import * as utilities from '@app/modules/common/utility';
+import { notificationStore,broadcastNotificationService,clientNotificationFactory } from "@app/modules/services";
+import { NotificationUI } from "@app/modules/domain";
export default class {
//broadcast.ribbon.application.renderNotifications
async renderNotifications(targetpage?:string){
@@ -12,7 +11,6 @@ export default class {
return false;
}
- const userLang = utilities.getUserLangFromGlobalContext();
console.log("broadcast renderNotifications");
const appProps = await Xrm.Utility.getGlobalContext().getCurrentAppProperties();
if(!appProps || !appProps.appId){
@@ -24,7 +22,7 @@ export default class {
await Promise.all(clearTasks);
const data:NotificationUI[] = [];
for(const n of livenotifications){
- const dataverseNotification = MapNotificationToDataverse(n,userLang);
+ const dataverseNotification =clientNotificationFactory.create(n);
const uid = await Xrm.App.addGlobalNotification(dataverseNotification);
data.push({
uid,
@@ -42,33 +40,3 @@ export default class {
}
}
-function MapNotificationToDataverse(n:fdn_broadcastappnotification,userLang:number):Xrm.App.Notification{
- const level = MapToLevel(n.fdn_level!);
- let msg = n.fdn_message;
- const localizedMsgObjs = n.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification;
- if(localizedMsgObjs && localizedMsgObjs.length > 0){
- msg = localizedMsgObjs.find((v)=> v.fdn_language === userLang)?.fdn_contentmessage ?? n.fdn_message;
- }
- return {
- level: level,
- message: msg!,
- showCloseButton: false,
- type: 2
- };
-}
-function MapToLevel(level:number):number{
- switch(level){
- case fdn_level.Success:
- return 1;
- case fdn_level.Danger:
- return 2;
- case fdn_level.Warning:
- return 3;
- case fdn_level.Information:
- return 4;
- default:
- return 4;
- }
-
-
-}
\ No newline at end of file
diff --git a/src/broadcast-typescript/app/modules/services/BroadcastNotificationService.ts b/src/broadcast-typescript/app/modules/services/BroadcastNotificationService.ts
index f42c4d6..08e23d6 100644
--- a/src/broadcast-typescript/app/modules/services/BroadcastNotificationService.ts
+++ b/src/broadcast-typescript/app/modules/services/BroadcastNotificationService.ts
@@ -36,9 +36,13 @@ export class BroadcastNotificationService implements IBroadcastNotificationServi
const columns = ["fdn_appmoduleid",
"fdn_message",
"fdn_broadcastappnotificationid",
- "fdn_level"
+ "fdn_level",
+ "fdn_buttondefaulttext",
+ "fdn_buttonactionurl",
+ "fdn_actiontype"
];
- const expandOptions = `&$expand=fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification($select=fdn_contentmessage,fdn_language;$filter=statecode eq 0)`;
+ const expandColumns = ['fdn_language','fdn_contentmessage','fdn_actionbuttondisplaytext'];
+ const expandOptions = `&$expand=fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification($select=${expandColumns.join(',')};$filter=statecode eq 0)`;
const queryOptions = `?$select=${columns.join(',')}`+expandOptions+filterOptions;
const result = await this.webApi.retrieveMultipleRecords("fdn_broadcastappnotification", queryOptions);
const notifications = result.entities as fdn_broadcastappnotification[];
@@ -63,13 +67,16 @@ function cleanNotification(e:fdn_broadcastappnotification){
"fdn_message",
"fdn_broadcastappnotificationid",
"fdn_level",
- "fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification"];
+ "fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification",
+ "fdn_buttondefaulttext",
+ "fdn_buttonactionurl",
+ "fdn_actiontype"];
let n = removeODataGarbages(e,keysToKeep);
const lmg = n.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification;
if(!lmg || lmg.length === 0){
return n;
}
- lmg.map(l => removeODataGarbages(l,["fdn_contentmessage","fdn_language"]));
+ lmg.map(l => removeODataGarbages(l,["fdn_contentmessage","fdn_language","fdn_actionbuttondisplaytext"]));
return n;
}
function removeODataGarbages(e:T,keys:string[]){
diff --git a/src/broadcast-typescript/app/modules/services/XrmAppNotificationFactory.ts b/src/broadcast-typescript/app/modules/services/XrmAppNotificationFactory.ts
new file mode 100644
index 0000000..09f8472
--- /dev/null
+++ b/src/broadcast-typescript/app/modules/services/XrmAppNotificationFactory.ts
@@ -0,0 +1,42 @@
+import { fdn_actiontype, fdn_broadcastappnotification, fdn_level, fdn_localizednotificationcontent } from "@app/modules/domain";
+import * as utilities from '@app/modules/common/utility';
+const LevelMappings = {
+ [fdn_level.Success.toString()]: 1,
+ [fdn_level.Danger.toString()]: 2,
+ [fdn_level.Warning.toString()]: 3,
+ [fdn_level.Information.toString()]: 4
+};
+
+export interface IClientAppNotificationFactory{
+ create(n:fdn_broadcastappnotification):Xrm.App.Notification;
+}
+export class XrmAppNotificationFactory implements IClientAppNotificationFactory{
+ private _buildAction = (n:fdn_broadcastappnotification,localizedContent?:fdn_localizednotificationcontent) => {
+ if(n.fdn_actiontype == fdn_actiontype.WebLink){
+ return {
+ actionLabel:localizedContent?.fdn_actionbuttondisplaytext ?? n.fdn_buttondefaulttext!,
+ eventHandler: () => {
+ window.open(n.fdn_buttonactionurl!, "_blank");
+ }
+ }
+ }
+ return undefined;
+ };
+ create(n:fdn_broadcastappnotification):Xrm.App.Notification{
+ const userLang = utilities.getUserLangFromGlobalContext();
+ const level = LevelMappings[n.fdn_level!.toString()];
+ let msg = n.fdn_message;
+ const localizedMsgObjs = n.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification;
+ const localizedObj = localizedMsgObjs && localizedMsgObjs.length > 0 ? localizedMsgObjs.find((v)=> v.fdn_language === userLang) : undefined;
+ if(localizedObj !== undefined && localizedObj !== null){
+ msg = localizedObj.fdn_contentmessage ?? n.fdn_message;
+ }
+ return {
+ level: level,
+ message: msg!,
+ showCloseButton: false,
+ type: 2,
+ action: this._buildAction(n,localizedObj)
+ };
+ }
+}
diff --git a/src/broadcast-typescript/app/modules/services/index.ts b/src/broadcast-typescript/app/modules/services/index.ts
index b2ff827..c9f19f7 100644
--- a/src/broadcast-typescript/app/modules/services/index.ts
+++ b/src/broadcast-typescript/app/modules/services/index.ts
@@ -2,8 +2,9 @@ import { BroadcastNotificationService, IBroadcastNotificationService } from "./B
import { INotificationStore,StorageNotificationStore} from "./notifications.store";
export * from "./BroadcastNotificationService";
export * from "./notifications.store";
+import { IClientAppNotificationFactory,XrmAppNotificationFactory} from "./XrmAppNotificationFactory";
-
+export const clientNotificationFactory:IClientAppNotificationFactory = new XrmAppNotificationFactory();
export const notificationStore:INotificationStore = new StorageNotificationStore(window.localStorage);
export const broadcastNotificationService:IBroadcastNotificationService = new BroadcastNotificationService(Xrm.WebApi,window.sessionStorage);
\ No newline at end of file
diff --git a/src/broadcast-typescript/tests/XrmTestDriver.ts b/src/broadcast-typescript/tests/XrmTestDriver.ts
new file mode 100644
index 0000000..4a73031
--- /dev/null
+++ b/src/broadcast-typescript/tests/XrmTestDriver.ts
@@ -0,0 +1,69 @@
+import { FormSelectorMock, UserSettingsMock, XrmMockGenerator, XrmStaticMock,ItemCollectionMock,FormItemMock, ContextMock, ClientContextMock, UiMock } from "xrm-mock";
+
+export class XrmTestDriver {
+
+ private userSettingsBuilder:XrmUserSettingsBuilder = new XrmUserSettingsBuilder();
+ private formBuilder:XrmFormBuilder = new XrmFormBuilder();
+ private clientType:Xrm.Client = "Web";
+ ConfigureUserSettings(fn:(u:XrmUserSettingsBuilder) => void):XrmTestDriver{
+ fn(this.userSettingsBuilder);
+ return this;
+ }
+ ConfigureForm(fn:(f:XrmFormBuilder) => void):XrmTestDriver{
+ fn(this.formBuilder);
+ return this;
+ }
+
+ public build():XrmStaticMock{
+ const xrmMock = XrmMockGenerator.initialise({
+ context: new ContextMock({
+ clientContext: new ClientContextMock(this.clientType,'Online'),
+ orgUniqueName: "",
+ userSettings: this.userSettingsBuilder.build()
+ }),
+ ui:new UiMock({
+ formSelector: this.formBuilder.build()
+ })
+ });
+ return xrmMock;
+ }
+}
+class XrmUserSettingsBuilder{
+ _userLcid:number = 1033
+ PreferFrench():XrmUserSettingsBuilder{
+ this._userLcid = 1036;
+ return this;
+ }
+ public build():UserSettingsMock{
+ return new UserSettingsMock(
+ {
+ isGuidedHelpEnabled: false,
+ isHighContrastEnabled: false,
+ isRTL: false,
+ securityRolePrivileges: [],
+ securityRoles: [],
+ userId: "{00000000-0000-0000-0000-000000000000}",
+ userName: "jdoe",
+ languageId: this._userLcid
+ });
+ }
+
+}
+class XrmFormBuilder{
+ _formType:XrmEnum.FormType = XrmEnum.FormType.Update;
+ public AsCreateForm():XrmFormBuilder{
+ this._formType = XrmEnum.FormType.Create;
+ return this;
+ }
+ public build():FormSelectorMock{
+ const itemMocks:ItemCollectionMock = new ItemCollectionMock([
+ new FormItemMock({
+ id: "form1",
+ label: "Main Form",
+ formType:this._formType,
+ currentItem: true
+ }),
+ ]);
+ return new FormSelectorMock(itemMocks);
+ }
+}
\ No newline at end of file
diff --git a/src/broadcast-typescript/tests/fakers/fdn_broadcastappnotification.faker.ts b/src/broadcast-typescript/tests/fakers/fdn_broadcastappnotification.faker.ts
index a5b4f81..c3f70a8 100644
--- a/src/broadcast-typescript/tests/fakers/fdn_broadcastappnotification.faker.ts
+++ b/src/broadcast-typescript/tests/fakers/fdn_broadcastappnotification.faker.ts
@@ -1,13 +1,26 @@
-import { fdn_broadcastappnotification, fdn_level } from '@app/modules/domain';
+import { fdn_actiontype, fdn_broadcastappnotification, fdn_level } from '@app/modules/domain';
import { faker } from '@faker-js/faker';
-import { createRandomLocalizedContent } from './fdn_localizednotificationcontent.faker';
-export function createRandomAppNotification(appid:string):fdn_broadcastappnotification{
+import { createBilingualLocalizedContents } from './fdn_localizednotificationcontent.faker';
+export function createRandomAppNotificationWithNoAction(appid:string,localized:boolean = true):fdn_broadcastappnotification{
return {
fdn_appmoduleid: appid,
fdn_broadcastappnotificationid: faker.string.uuid(),
fdn_level: faker.helpers.enumValue(fdn_level),
fdn_message: faker.string.alpha({length:10}),
fdn_name:faker.string.alpha({length:10}),
- fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification: faker.helpers.multiple(createRandomLocalizedContent,{count:2})
+ fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification: localized ?createBilingualLocalizedContents():[],
+ };
+}
+export function createRandomAppNotificationWithWebLinkAction(appid:string,localized:boolean = true):fdn_broadcastappnotification{
+ return {
+ fdn_appmoduleid: appid,
+ fdn_broadcastappnotificationid: faker.string.uuid(),
+ fdn_level: faker.helpers.enumValue(fdn_level),
+ fdn_message: faker.string.alpha({length:10}),
+ fdn_name:faker.string.alpha({length:10}),
+ fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification:localized? createBilingualLocalizedContents():[],
+ fdn_actiontype: fdn_actiontype.WebLink,
+ fdn_buttonactionurl: faker.internet.url(),
+ fdn_buttondefaulttext: faker.string.alpha({length:10})
};
}
\ No newline at end of file
diff --git a/src/broadcast-typescript/tests/fakers/fdn_localizednotificationcontent.faker.ts b/src/broadcast-typescript/tests/fakers/fdn_localizednotificationcontent.faker.ts
index ede174d..78f59e9 100644
--- a/src/broadcast-typescript/tests/fakers/fdn_localizednotificationcontent.faker.ts
+++ b/src/broadcast-typescript/tests/fakers/fdn_localizednotificationcontent.faker.ts
@@ -1,10 +1,17 @@
-import { fdn_localizednotificationcontent } from '@app/modules/domain';
+import { fdn_language, fdn_localizednotificationcontent } from '@app/modules/domain';
import { faker } from '@faker-js/faker';
-export function createRandomLocalizedContent():fdn_localizednotificationcontent{
+export function createRandomLocalizedContent(lang:fdn_language = fdn_language.English):fdn_localizednotificationcontent{
return {
fdn_contentmessage:faker.string.alpha({length:10}),
- fdn_language: faker.number.int({min:794560000,max:794560001}),
+ fdn_language: lang,
fdn_localizednotificationcontentid:faker.string.uuid(),
- fdn_name: faker.string.alpha({length:10})
+ fdn_name: faker.string.alpha({length:10}),
+ fdn_actionbuttondisplaytext: faker.string.alpha({length:10})
};
+}
+export function createBilingualLocalizedContents():fdn_localizednotificationcontent[]{
+ return [
+ createRandomLocalizedContent(fdn_language.English),
+ createRandomLocalizedContent(fdn_language.French)
+ ];
}
\ No newline at end of file
diff --git a/src/broadcast-typescript/tests/fakers/notificationui.faker.ts b/src/broadcast-typescript/tests/fakers/notificationui.faker.ts
index f415f68..1a7e248 100644
--- a/src/broadcast-typescript/tests/fakers/notificationui.faker.ts
+++ b/src/broadcast-typescript/tests/fakers/notificationui.faker.ts
@@ -1,9 +1,9 @@
import { NotificationUI } from '@app/modules/domain';
import { faker } from '@faker-js/faker';
-import { createRandomAppNotification } from './fdn_broadcastappnotification.faker';
+import { createRandomAppNotificationWithNoAction } from './fdn_broadcastappnotification.faker';
export function createRandomUiNotification(appid:string) : NotificationUI {
return {
uid:faker.string.uuid(),
- data:createRandomAppNotification(appid)
+ data:createRandomAppNotificationWithNoAction(appid)
};
}
\ No newline at end of file
diff --git a/src/broadcast-typescript/tests/form/fdn_broadcastappnotification.test.ts b/src/broadcast-typescript/tests/form/fdn_broadcastappnotification.test.ts
new file mode 100644
index 0000000..e1c4510
--- /dev/null
+++ b/src/broadcast-typescript/tests/form/fdn_broadcastappnotification.test.ts
@@ -0,0 +1,80 @@
+import { XrmTestDriver } from "@tests/XrmTestDriver";
+import { OptionSetAttributeMock, SectionMock, StringAttributeMock, TabMock, XrmMockGenerator, XrmStaticMock } from "xrm-mock";
+import { forms } from "@app/modules/form";
+import { fdn_actiontype } from "@app/modules/domain";
+
+describe("Given a broadcast app notification Form", () => {
+ let xrmMock:XrmStaticMock;
+ let actionTabMock:TabMock;
+ let urlSectionMock:SectionMock;
+ let ActionTypeAttributeMock: OptionSetAttributeMock;
+ let ButtonDisplayTextAttributeMock: StringAttributeMock;
+ let ButtonActionUrlAttributeMock: StringAttributeMock;
+ beforeEach(() => {
+ xrmMock = new XrmTestDriver().ConfigureForm(f => f.AsCreateForm()).build();
+ actionTabMock = XrmMockGenerator.Tab.createTab('tab_action');
+ urlSectionMock = XrmMockGenerator.Section.createSection("tab_action_section_url","Url",true,actionTabMock);
+ ActionTypeAttributeMock = XrmMockGenerator.Attribute.createOptionSet("fdn_actiontype",undefined,[{text:"Web Link",value:fdn_actiontype.WebLink}]);
+ ButtonDisplayTextAttributeMock = XrmMockGenerator.Attribute.createString("fdn_buttondefaulttext");
+ ButtonActionUrlAttributeMock = XrmMockGenerator.Attribute.createString("fdn_buttonactionurl");
+ });
+ describe("When it loads with no actiontype", () => {
+ beforeEach(() => {
+ forms.fdn_broadcastappnotification.onLoad(XrmMockGenerator.getEventContext() as any);
+ });
+ it("should add an onChange handler to the action type field", () => {
+ expect(ActionTypeAttributeMock.eventHandlers).toHaveLength(1);
+ });
+ it("should hide url section", () => {
+ expect(urlSectionMock.getVisible()).toBeFalsy();
+ });
+ });
+ describe("When it loads with WebLink actiontype", () => {
+ beforeEach(() => {
+ ActionTypeAttributeMock.setValue(fdn_actiontype.WebLink);
+ forms.fdn_broadcastappnotification.onLoad(XrmMockGenerator.getEventContext() as any);
+ });
+ it("should add an onChange handler to the action type field", () => {
+ expect(ActionTypeAttributeMock.eventHandlers).toHaveLength(1);
+ });
+ it("should display url section", () => {
+ expect(urlSectionMock.getVisible()).toBeTruthy();
+ });
+ it("should set button display text field required", () => {
+ expect(ButtonDisplayTextAttributeMock.getRequiredLevel()).toBe("required");
+ });
+ it("should set button action url field required", () => {
+ expect(ButtonActionUrlAttributeMock.getRequiredLevel()).toBe("required");
+ })
+ });
+ describe("When action type changes to WebLink", () => {
+ beforeEach(() => {
+ ActionTypeAttributeMock.setValue(fdn_actiontype.WebLink);
+ forms.fdn_broadcastappnotification.onActionTypeChange(XrmMockGenerator.getEventContext() as any);
+ });
+ it("should display url section", () => {
+ expect(urlSectionMock.getVisible()).toBeTruthy();
+ });
+ it("should set button display text field required", () => {
+ expect(ButtonDisplayTextAttributeMock.getRequiredLevel()).toBe("required");
+ });
+ it("should set button action url field required", () => {
+ expect(ButtonActionUrlAttributeMock.getRequiredLevel()).toBe("required");
+ })
+ });
+ describe("When action type changes to none", () => {
+ beforeEach(() => {
+ ActionTypeAttributeMock.setValue(null as any);
+ forms.fdn_broadcastappnotification.onActionTypeChange(XrmMockGenerator.getEventContext() as any);
+ });
+ it("should hide url section", () => {
+ expect(urlSectionMock.getVisible()).toBeFalsy();
+ });
+ it("should set button display text field required", () => {
+ expect(ButtonDisplayTextAttributeMock.getRequiredLevel()).toBe("none");
+ });
+ it("should set button action url field required", () => {
+ expect(ButtonActionUrlAttributeMock.getRequiredLevel()).toBe("none");
+ })
+ });
+});
diff --git a/src/broadcast-typescript/tests/ribbon/application.test.ts b/src/broadcast-typescript/tests/ribbon/application.test.ts
index 9db6063..5bcc8dc 100644
--- a/src/broadcast-typescript/tests/ribbon/application.test.ts
+++ b/src/broadcast-typescript/tests/ribbon/application.test.ts
@@ -1,9 +1,8 @@
import { v4 } from "uuid";
import { MockInstance } from "vitest";
import { XrmMockGenerator, XrmStaticMock } from "xrm-mock";
-import { notificationStore } from "@app/modules/services";
-import { broadcastNotificationService } from "@app/modules/services";
-import { createRandomAppNotification, createRandomUiNotification } from "@tests/fakers";
+import { notificationStore,broadcastNotificationService } from "@app/modules/services";
+import { createRandomAppNotificationWithNoAction, createRandomUiNotification } from "@tests/fakers";
import { faker } from "@faker-js/faker";
import { ribbon } from "@app/modules/ribbon";
@@ -18,7 +17,7 @@ describe("Given an application ribbon",() =>{
let clearGlobalNotifSpy:MockInstance;
let getPublishedNotificationSpy:MockInstance;
const cachedNotifications = faker.helpers.multiple(() => createRandomUiNotification(appId),{count:3});
- const livenotifications = faker.helpers.multiple(() => createRandomAppNotification(appId),{count:3});
+ const livenotifications = faker.helpers.multiple(() => createRandomAppNotificationWithNoAction(appId),{count:3});
beforeEach(() => {
xrmMock = XrmMockGenerator.initialise();
const ctx = xrmMock.Utility.getGlobalContext();
@@ -37,7 +36,6 @@ describe("Given an application ribbon",() =>{
Xrm.App.clearGlobalNotification = vitest.fn();
clearGlobalNotifSpy = vitest.spyOn(Xrm.App,"clearGlobalNotification");
getPublishedNotificationSpy = vitest.spyOn(broadcastNotificationService,"getPublishedNotifications").mockResolvedValue(livenotifications);
-
});
describe("When it renders notifications on the right page",() =>{
diff --git a/src/broadcast-typescript/tests/services/BroadcastNotificationService.test.ts b/src/broadcast-typescript/tests/services/BroadcastNotificationService.test.ts
index 1e6f80a..27cf226 100644
--- a/src/broadcast-typescript/tests/services/BroadcastNotificationService.test.ts
+++ b/src/broadcast-typescript/tests/services/BroadcastNotificationService.test.ts
@@ -1,7 +1,7 @@
import { fdn_broadcastappnotification } from "@app/modules/domain";
import { broadcastNotificationService, CachedValue } from "@app/modules/services";
import { faker } from "@faker-js/faker";
-import { createRandomAppNotification } from "@tests/fakers";
+import { createRandomAppNotificationWithNoAction } from "@tests/fakers";
import { simpleRetrieveMultipleMock } from "@tests/helpers/mockHelpers";
import { v4 } from "uuid";
import { MockInstance } from "vitest";
@@ -12,14 +12,18 @@ describe("Given a broadcast notification service",() => {
describe("When it retrieves published notifications",()=>{
let retrieveMultipleSpy:MockInstance;
const notifications:fdn_broadcastappnotification[] =
- faker.helpers.multiple(() =>createRandomAppNotification(appId),{count:2});
+ faker.helpers.multiple(() =>createRandomAppNotificationWithNoAction(appId),{count:2});
const filterOptions = `&$filter=statuscode eq 2 and fdn_appmoduleid eq '${appId}'&$orderby=fdn_level asc`;
const columns = ["fdn_appmoduleid",
"fdn_message",
"fdn_broadcastappnotificationid",
- "fdn_level"
+ "fdn_level",
+ "fdn_buttondefaulttext",
+ "fdn_buttonactionurl",
+ "fdn_actiontype"
];
- const expandOptions = `&$expand=fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification($select=fdn_contentmessage,fdn_language;$filter=statecode eq 0)`;
+ const expandColumns = ['fdn_language','fdn_contentmessage','fdn_actionbuttondisplaytext'];
+ const expandOptions = `&$expand=fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification($select=${expandColumns.join(',')};$filter=statecode eq 0)`;
const expectedQueryOptions = `?$select=${columns.join(',')}`+expandOptions+filterOptions;
beforeEach(() =>{
retrieveMultipleSpy = simpleRetrieveMultipleMock(notifications);
@@ -54,7 +58,7 @@ describe("Given a broadcast notification service",() => {
//arrange
const cachedItems:CachedValue ={
timestamp: Date.now() - ((60*5) * 1000),
- value:faker.helpers.multiple(() =>createRandomAppNotification(appId),{count:1})
+ value:faker.helpers.multiple(() =>createRandomAppNotificationWithNoAction(appId),{count:1})
};
sessionStorage.setItem(_cacheKey,JSON.stringify(cachedItems));
//act
diff --git a/src/broadcast-typescript/tests/services/XrmAppNotificationFactory.test.ts b/src/broadcast-typescript/tests/services/XrmAppNotificationFactory.test.ts
new file mode 100644
index 0000000..02ff7f7
--- /dev/null
+++ b/src/broadcast-typescript/tests/services/XrmAppNotificationFactory.test.ts
@@ -0,0 +1,83 @@
+import { XrmAppNotificationFactory } from '@app/modules/services/XrmAppNotificationFactory';
+import * as utilities from '@app/modules/common/utility';
+import { MockInstance } from 'vitest';
+import { fdn_broadcastappnotification, fdn_language } from '@app/modules/domain';
+import { createRandomAppNotificationWithNoAction,createRandomAppNotificationWithWebLinkAction } from '@tests/fakers';
+import { v4 } from "uuid";
+vitest.mock('@app/modules/common/utility', () => (
+vitest.importActual('@app/modules/common/utility')
+));
+describe('Given a XrmApp Client Notification Factory',() => {
+ const factory = new XrmAppNotificationFactory();
+ const appid = v4();
+ let notification:fdn_broadcastappnotification;
+ let getUserLangFromGlobalContextSpy: MockInstance;
+ beforeEach(() => {
+ getUserLangFromGlobalContextSpy = vitest.spyOn(utilities,'getUserLangFromGlobalContext');
+ getUserLangFromGlobalContextSpy.mockReturnValue(fdn_language.English);
+ });
+ describe('when creating a client app notification with no action',() => {
+ let xrmAppNotification:Xrm.App.Notification;
+ beforeEach(()=> {
+ notification = createRandomAppNotificationWithNoAction(appid);
+ xrmAppNotification = factory.create(notification);
+ });
+ it('should not have an action',() => {
+ expect(xrmAppNotification.action).toBeUndefined();
+ });
+ it('should have proper level',() => {
+ const expectedLevel = (notification.fdn_level! - 794560000) + 1;
+ expect(xrmAppNotification.level).toBe(expectedLevel);
+ });
+ it('should have proper message content',() => {
+ const expectedMessage = notification.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification!.find((v)=> v.fdn_language === fdn_language.English)!.fdn_contentmessage!;
+ expect(xrmAppNotification.message).toBe(expectedMessage);
+ });
+ it('should not have a dissmiss button',() => {
+ expect(xrmAppNotification.showCloseButton).toBe(false);
+ });
+ it('should be a type of message bar',() => {
+ expect(xrmAppNotification.type).toBe(2);
+ });
+ });
+ describe('when creating a client app notification with no localized content',() => {
+ let xrmAppNotification:Xrm.App.Notification;
+ beforeEach(()=> {
+ notification = createRandomAppNotificationWithNoAction(appid,false);
+ xrmAppNotification = factory.create(notification);
+ });
+ it('should have proper message content',() => {
+ expect(xrmAppNotification.message).toBe(notification.fdn_message!);
+ });
+ });
+ describe('when creating a client app notification with Action',() => {
+ let xrmAppNotification:Xrm.App.Notification;
+ beforeEach(()=> {
+ notification = createRandomAppNotificationWithWebLinkAction(appid);
+ xrmAppNotification = factory.create(notification);
+ });
+ it('should have proper action text',() => {
+ const expectedLocalizedObj = notification.fdn_localizednotificationcontent_AppNotificationConfigId_fdn_broadcastappnotification!.find((v)=> v.fdn_language === fdn_language.English);
+ expect(xrmAppNotification.action!.actionLabel).toBe(expectedLocalizedObj!.fdn_actionbuttondisplaytext!);
+ });
+ it('should have an action event handler',() => {
+ expect(xrmAppNotification.action!.eventHandler).toBeInstanceOf(Function);
+ });
+ it('should open a web page when action is clicked',() => {
+ const windowOpenSpy = vitest.spyOn(window,'open').mockReturnValue(null);
+ expect(xrmAppNotification.action!.eventHandler!()).toBeUndefined();
+ expect(windowOpenSpy).toHaveBeenCalledWith(notification.fdn_buttonactionurl!,"_blank");
+ });
+ });
+ describe('when creating a client app notification with Action and no localized content',() => {
+ let xrmAppNotification:Xrm.App.Notification;
+ beforeEach(()=> {
+ notification = createRandomAppNotificationWithWebLinkAction(appid,false);
+ xrmAppNotification = factory.create(notification);
+ });
+ it('should have proper action text',() => {
+ expect(xrmAppNotification.action!.actionLabel).toBe(notification.fdn_buttondefaulttext!);
+ });
+
+ });
+});
\ No newline at end of file