From 16d7ca550c767a7204009cc64a64a0569d305c1e Mon Sep 17 00:00:00 2001 From: Filip Michalski Date: Tue, 14 Jan 2025 16:36:06 +0100 Subject: [PATCH] Sticky notes (#7277) * Create StickyNotes DB entity * Add PoC httpApiService for StickyNotes - add slick migration * Add put and delete endpoints, use value classes for ID in stickyNotes model * Add StickyNote PoC to FE * Resize and update StickyNotes * Handle note removal * Remove some unused fragments * Update openApi definition * Resize stickyNote without visual-lag * Disable stickyNotes when scenario is not saved * Show/hide tools on graph actions * Edit stickyNote on graph * Fix some suggestions made by rabbit * Update openApi definitions * Add white characters to textarea in stickyNote markdown editor * Allow focus to stay in markdown editor * Allow switch viewer to editor witgh left mouse click * Add stickyNotes length and count validation, add stickyNotes configuration, fix error method (or did i?) * Remove node specific code from StickyNotePreview, update openApi defs * Add some fixes, improve types, add max width and height for stickyNote * Add STICKY_NOTE_CONSTRAINTS with config values * Reuse common code, restore default color, remove duplicated update method * Restore CSS class, remove unused imports * Remove stickyNotePanel, add stickyNote to creatos panel * Update cypress test * Add Changelog and migrationGuide entries * Updated snapshots (#7275) Co-authored-by: philemone <13632953+philemone@users.noreply.github.com> * remove space * Remove DOMpurify and use XSS instead * Remove StickyNotePreview from ComponentPreview * Rename stickyNotes panel to sticky notes * Fix markdown preview * Change shape of stickyNote, fix 'changed' detection for notes * Add notifications to graph, prevent from making changes to old stickyNotes versions * Revert fix in error notification msg * Fix warn messages * Add strict type for color #xxxxxx, remove unnecessary generated noteId, add more optimistic tests * Update pointer, add cypress tests, remove todo from uncomplete method, remove unused dompurify from package * Add regex in sticky note tests for note id * Change remove tool color and margins, update cypress tests * update cypress tests * Add maxDiffThreshold to flaky cypress test --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: philemone <13632953+philemone@users.noreply.github.com> --- ...ow collapse (persist) and filtering #0.png | Bin 12310 -> 14293 bytes ... to note and display it as markdown #0.png | Bin 0 -> 72354 bytes ...es should allow to drag sticky note #0.png | Bin 0 -> 69916 bytes ...cky note when scenario is not saved #0.png | Bin 0 -> 71149 bytes designer/client/cypress/e2e/components.cy.ts | 2 +- .../client/cypress/e2e/creatorToolbar.cy.ts | 1 + designer/client/cypress/e2e/stickyNotes.cy.ts | 65 +++ .../client/cypress/fixtures/stickyNotes.json | 58 ++ designer/client/src/actions/actionTypes.ts | 2 + .../client/src/actions/nk/assignSettings.ts | 7 + designer/client/src/actions/nk/process.ts | 43 ++ .../src/actions/notificationActions.tsx | 8 + .../src/assets/json/nodeAttributes.json | 3 + designer/client/src/common/StickyNote.ts | 16 + .../src/components/ComponentDragPreview.tsx | 35 +- .../src/components/ComponentPreview.tsx | 1 + .../src/components/StickyNotePreview.tsx | 62 +++ .../components/graph/EspNode/stickyNote.ts | 128 +++++ .../graph/EspNode/stickyNoteElements.ts | 137 +++++ .../client/src/components/graph/Graph.tsx | 166 +++++- .../GraphPartialsInTS/applyCellChanges.ts | 21 +- .../graph/GraphPartialsInTS/cellUtils.ts | 13 + .../src/components/graph/GraphWrapped.tsx | 8 +- .../graph/NodeDescriptionPopover.tsx | 2 + .../src/components/graph/ProcessGraph.tsx | 29 +- .../src/components/graph/StickyNoteElement.ts | 52 ++ .../src/components/graph/fragmentGraph.tsx | 34 +- .../components/graph/graphStyledWrapper.ts | 37 +- .../graph/node-modal/node/FragmentContent.tsx | 4 +- designer/client/src/components/graph/types.ts | 12 + .../src/components/graph/utils/graphUtils.ts | 3 +- .../toolbars/creator/ComponentIcon.tsx | 6 + .../toolbars/creator/StickyNoteComponent.tsx | 19 + .../components/toolbars/creator/ToolBox.tsx | 26 +- .../creator/ToolboxComponentGroup.tsx | 8 +- .../client/src/containers/theme/helpers.ts | 10 + .../client/src/containers/theme/nuTheme.tsx | 1 + designer/client/src/http/HttpService.ts | 60 ++- designer/client/src/reducers/graph/reducer.ts | 15 + designer/client/src/reducers/graph/types.ts | 2 + designer/client/src/reducers/graph/utils.ts | 40 ++ .../client/src/reducers/selectors/graph.ts | 5 + .../client/src/reducers/selectors/settings.ts | 1 + designer/client/src/types/component.ts | 1 + designer/client/src/types/node.ts | 3 +- designer/client/src/types/stickyNote.ts | 4 + .../main/resources/defaultDesignerConfig.conf | 6 + .../static/assets/components/StickyNote.svg | 1 + .../V1_060__CreateStickyNotesDefinition.scala | 89 ++++ .../hsql/V1_060__CreateStickyNotes.scala | 8 + .../postgres/V1_060__CreateStickyNotes.scala | 8 + .../ui/api/SettingsResources.scala | 5 +- .../ui/api/StickyNotesApiHttpService.scala | 230 ++++++++ .../description/StickyNotesApiEndpoints.scala | 219 ++++++++ .../ui/api/description/stickynotes/Dtos.scala | 119 +++++ .../stickynotes/StickyNoteEvent.scala | 14 + .../ui/config/FeatureTogglesConfig.scala | 11 +- .../pl/touk/nussknacker/ui/db/NuTables.scala | 3 +- .../db/entity/StickyNotesEntityFactory.scala | 112 ++++ .../stickynotes/DbStickyNotesRepository.scala | 174 ++++++ .../stickynotes/StickyNotesRepository.scala | 55 ++ .../server/AkkaHttpBasedRouteProvider.scala | 12 + .../resources/config/common-designer.conf | 6 + .../WithSimplifiedConfigScenarioHelper.scala | 10 + .../test/utils/domain/ScenarioHelper.scala | 61 ++- ...tickyNotesApiHttpServiceBusinessSpec.scala | 165 ++++++ docs-internal/api/nu-designer-openapi.yaml | 499 ++++++++++++++++++ docs/Changelog.md | 4 + docs/MigrationGuide.md | 9 + 69 files changed, 2898 insertions(+), 72 deletions(-) create mode 100644 designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should add text to note and display it as markdown #0.png create mode 100644 designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should allow to drag sticky note #0.png create mode 100644 designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should disable sticky note when scenario is not saved #0.png create mode 100644 designer/client/cypress/e2e/stickyNotes.cy.ts create mode 100644 designer/client/cypress/fixtures/stickyNotes.json create mode 100644 designer/client/src/common/StickyNote.ts create mode 100644 designer/client/src/components/StickyNotePreview.tsx create mode 100644 designer/client/src/components/graph/EspNode/stickyNote.ts create mode 100644 designer/client/src/components/graph/EspNode/stickyNoteElements.ts create mode 100644 designer/client/src/components/graph/StickyNoteElement.ts create mode 100644 designer/client/src/components/toolbars/creator/StickyNoteComponent.tsx create mode 100644 designer/client/src/types/stickyNote.ts create mode 100644 designer/server/src/main/resources/web/static/assets/components/StickyNote.svg create mode 100644 designer/server/src/main/scala/db/migration/V1_060__CreateStickyNotesDefinition.scala create mode 100644 designer/server/src/main/scala/db/migration/hsql/V1_060__CreateStickyNotes.scala create mode 100644 designer/server/src/main/scala/db/migration/postgres/V1_060__CreateStickyNotes.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpService.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/StickyNotesApiEndpoints.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/Dtos.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/StickyNoteEvent.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/db/entity/StickyNotesEntityFactory.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/DbStickyNotesRepository.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/StickyNotesRepository.scala create mode 100644 designer/server/src/test/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpServiceBusinessSpec.scala diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Creator toolbar should allow collapse (persist) and filtering #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Creator toolbar should allow collapse (persist) and filtering #0.png index c5c1c96a2cec9af3367f969733b8e76f64f37aea..4766a9dfb92bee3889a44374b304779f43063756 100644 GIT binary patch literal 14293 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 jR_2ea60wFD`N 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 V2%L{9M-TEV$*am$$UJ!bzW^Thqy7K@ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should add text to note and display it as markdown #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should add text to note and display it as markdown #0.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf3a281fd85c4416ea13818ffcfe552285b54b5 GIT binary patch literal 72354 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%0D{DerFK)!xqQxnyl9dt! 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 z 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 V-$+(mInIElqNt&echfZBe*wGUR|Nn7 literal 0 HcmV?d00001 diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should allow to drag sticky note #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should allow to drag sticky note #0.png new file mode 100644 index 0000000000000000000000000000000000000000..77404342dfd951194295febe09a00fdf7e8de25b GIT binary patch literal 69916 zcma&O1yojD*ER~$-QC^Y-5_<7(g=cdhqQF3(%p@8htl2M-67JU#J^FW=X?Ki-tUYv z24i!t&E9LqnsLp!)=ii)NCpWWA07-03`zF2q$(H~!~q!CD=k=P;LbVm2o>-P+gVD> zSz1+1>5Zy{l!1h@hN`521{eo3J2MCHS1>Se)J|DRF?BcalTIYjgrKi?=Rcg>kbnHJ zyCBc?6^BGC)L5!3n43DiS~~!>sQs9+XsF>D-TgB6WrlHn)Ui;l@%{7WW5}}w&vEMB zRKXrYPNXO}A9-a+W4E7^@BU`G$X$iSxA$AaKQ2{be|S%Xp^7_$d&(oMLLll5_yw52 zk_Dn_>N;k<9@{tn3PBclAO{_YD*kd;9CmUS@y`cflsk;~&uucvBo2FsaQ7>1>~`K* zTxRBj5Tz>XK2iUFAE+>Zu5kT~!gkyK6emVsG($LKy#8cl*@Sj&y48l`zQv98dw$@u zMVUaLy#0jqWg+IDw;iUHn~{q@LnOxw2kyKD$F(uNXuk{HJw|@ldR+AIPMloYaBA5J zFRM0{Ch2?sj}Rr+9~F-y9!>)F&afwnIzK5^=9fv7%EKsrHJ*9zG0T1U*16rF(#A^` z{E#58^=38+=WR<`F3%cyZ6ro6E{_@dX4bdPV*^eH?0O!hHgQrO8DBcuFK|Y%jICQz#lxG%+U|dW(ZGhSB4+7t!Tjn zJy1-&mPlh;n}1|7vXGvGvrI{hhdnnDwmV_tKOc`&JNev zOXSlZhhy66KB^(NRKfCSz1wQi=-yWpdmrR{Sp@I|^~N(&>Eh)eceZhhKZ8SAU*Ny%Z~AUSKsDfW<9t7JcRprK`qL3; z58)gs^=|W>rc)CQqPFgY3;#e-u`o`Bs{tRbD7-vpJnfT4xjj_5K5+;Mf8jQN5KfC!e(@i4@2kPiPmJI%+`0?Fts|ni0TU*Y4i%8NzZ$#+h znP6Q^h6Z!7`K5=;oBHf!tA%=78Z0rDo}XaCAKh7{_J816-90EHCAVG*`^L}+cevV7I#fL$DPGe#_vlSNN!QYE@4!ib;GDOrI zevM{(KZl)l0s3#59rEsPCq=eChnxb7KUQ!-bm%!>Q@pQ~a@C==_bA$J&S}KgmULS? zS1Z52brsd_;>X=akaHjkVRQ54F5xCVOGer$~H5&f7htr5xAjN!HxbfjJG{Tl(!RL$;2fLrmx3vEIsb)& zf}R!odc8+jkayEUI_b-HLhr9)so%V=z^>@&yE_{3(+FMmXk|WKm(|r-PQB$VZ! zA0s_HSylK7y!*VSHS}%ovZnIhD^DQ6n=PXM$8j%A2y)KlcT2eu^GA=kjp`U{f&LGz zQJX@#3(c3T8Fk&dT2Dp#cTpbx`~6X5&ru?^<2|?wQrV+vlbD ze_f_qXw4YOq)7QS41R^Q(%;AbJ3{v$Z5R+gk$n?hOW)Z)?jG2bxLf|Is7Gh~Jov?p z99(lu#`xB=Ry&z!xDM*joWa0EwqlHFuu){ZT@bwno86~1VHk^NZ+=8>$ZQSan05y` zhnnrNe3G;6nw}y2{&dEzkma-lk;Euba||G_P`nA zrW6}(EAaOoYg~$%V8$rnD#Q=b-8}xHz4$(C>{_z(Lnv?ztLD`BWAqS8|5M;EPZ*4u za-7>|YebQ&5#z@sBtp%Y-a>TGd7>WYOKGVh!%J>UkC^vM#mk|GdoQe{{ET>hKZVk* zq9Tj?nAOH>f(b$X2nQc&)PdrGW?fm7i}uG818~{R!S-98zkz$<=(tc%n|`_L+<5cGET>-6`5CR8sj#C9bcZHtJi5;E_X7T-#_b<;<|B6U{M~gQh5<$hD^fQbY6LOJlETQcXO$W*n3b{gDytbsGS(b*C>8 z@r*wIX=(bNP+A7ofoVZk=YZI7LmZiqQ37zlrlr_ll8(YM$3$KRs&RJP^X(6OvwJ$; zjfQN@&=5`rUf6baEPGGhbibapJ6y4Ez%C6yCcjRCytIZbECr{r^SfKF&2M9N)`*GC zxumRz1}Hh^haBi9C8m-&$y3Zls%DcHzU@J1n9Mu9C9ZyO>oS+7l-W|EFoEH&eZQ*# z|D6yAQ|afzB%&;Ak6NwK&{O1YnTz2RUmwR4ICT`Bf?N}fR_WmKHQMkzreTi63@!E; z;(N8i3_pvKtD6}%92>Lysp}n1$4G;z*VIC(az<{X(&gc8HpkjE{5S|ixzg44raU)a zrVn`N{XRV?P&3zad=TSb8q(B`ErU4iLBbooLmQO*RkC0i{tutRWZi|;_8poES*=(c z^k|PV9CSLLhIRm95$WC=En_2ZO`C8ug^rw2578IsIxE|?b8Ju zB&0dKCD|vv=^IkX8!GR8-z;8>LLLIIs`JvQSKHnkzt^}x>dO|upB)K(c`%REP6wHPr}?GoxxRa~ox(fU>hhJea$jgO$_q^+LbarZ0loMVY@ ze61rEOMvyz7k~WUSOTJHzk^BUG-VCjB*wLw-LcjizEBFULLF_=%*wyrzjSv{6Y4Ly zO1ls!EG0R++N!K7$yrIulwR^R7msuqk-GP4;*Ve|;O}pa<7Lcnpg<2jn_(ka5Y_np zRUv9=2*d8Aqi=Mug9&sEN4;0^ITB?3ZeIjB?~kSS%$hiJO_kKN1TVEfms%Yaf2qic zE1Mj+9S?}#l#MUf?z>v_A+6<&521;ql-k8@_rpj|z#hu^Vj?Kw$tEv6e;{JQzHOxb zO}1dYsXFpv*lcjP-DK!Q;pR@LXGUd`Fi)D}bP*2m9gqB5-GNIxn`}W-&f30S<9vw) zLsKCx-0>C0y^+lE^w~vRm?-1EDrL~Qf}vJT*sVe4=)z*z-H;Qj+VnQt6qz|IN9IwC z<|#}Y?`5Nck1J0_z*Nf8dWM>yF&?f#Px$=X-NS@t^K_>78@#3kQmV1_k^2VTX;fKO zS6S>SVdwNXSlr1L7#QYSjgg1rPl}?V5VYUo%l-@u z3Egh`FNNhfm-@~+!l58&uRd`#A?wVC*lX13T)rsHN~N7j)6eQd^Bv^33q$W%;jJa%L0 zqdki$J9oleU+7%7`rQMCIwH+rwiD8N+mdiDjHHF%cGGYiZ^Cx;B%bi+!cby#!CAh7 zh$A)gWaeIx#p6klW&7ZDcPd`kp57T!|2fi=I|>se8V--$?=a76VE6%IyuB@XWQcMK z8p^Sj!(LwPFikiCr~jt&#sb?){!gK>Rcd85ec;^x{X7z%Exv!6n2eb^6;y6xqlZu! zrR|lh8{ItBQ}`xJ&`#5t@(w^w1^Fc`6Ey}4Y? z$(F}#CU&MO8hDcdKQTJtbhvbDT$C4z^JcL>HCMxq7ekUCD7Pl?aqM&0DT41tX^cSW zE+$u^r=Skf>EMx=SyNR`h4)rCHNXZ(h4wx;Kr^_c(N;3jn5jVz*9zg@+0$q0N!rss z>2UEiQ;$SxC@gc_&DIVtObtL0Ej1+)B`}%aSh~EoDH7yOUHj%7YPb-pH9}%mWdz7L z^de)G;BtT;O3{`qjDArRa%yzpae9o|kuo%Xt@bx(2EKyXc$^c%!Bjo#^h91SR*y5w!Gdtk`->9_rR;4P8;wJ~K{A6qDDe>z{aySkdl}-8~hV zgA!(wYvwOqG|)!`cm54&3pu`k<=|h+O7Ts(-6;YV)RmIARx~&C&YvR`~=0D}=-BVp%oxSSM zJPN+J)wQnM@#yBcF}l5rq44B8` z5sJ)RkVegWA}}H+5#4$H*4S2I;gG3XLeOl|hAfyk7kEXu;tvcN+@WUE3*#Cq35|6i z(L~AC$$el*G!;y@tlCXq+oBl|4Vd;B>(WkkYFxmV@+s2T z8SwSh!IJxmGc!|igKdAdzHkOjhQrfMqw|i192AQ_824Yir8i^UvaChV73Z7KFz$N4 zUnhq_y{znUqr3@bc1FfO0^#IBh7GFS*;5m)5SjG;q}GxqgcX37jNd_%_9Y)NG-yTP z!Z}-p6Rq#{)?4ok&M`wyql-&$uJ}+xhLG@MzR{taE&e5=rjnB1KpB?oo92`EH@(pI zEEfIBXeNfl&u_jsV2_6P@o2l4ot62UtE$GmmOUEJUqoTETs7MBQXP|(7TwmP&sM2% zz>DCX%W9c+{l|cvNiEuvT3)aumm+#YAV;c4WHj?t?mWMJiKksk@sfJ6YB| z8|zydGYd3)!~6iItT&Y(MWg$)m#nWvIrmL=3e80S2y*`Q(A5mRJ)J)jA?5rE@>Bto zJ$x{mZ0QAiKFvBmXIxRJ4sB`k?i_@&YR=p`@0{eiN@YR=|ccn|J-1RAm>_*!q=FRJviWx&;m8KuGYC$4LEkvemH# zD>S+hWYo4^_x8c}VG9+w9``vFN6CHb>lZ$I`ZxP6GG*Z;7lv51c?-QXxeJM19e-r2 zqt|;iU>-P6MaLU+L!%mf0o3qYobqUcCKhwww^1d1Aw74>AOuo2YE(^I+kbjO>k+NL z3Rt%f;q*o>+tWw%UDrVKv}V(N6`lV$V?@2~_d5Hcv1*BtDtPUtFl!XK$Kqv81AMkx)N$h|)gggL;$5|vc&DFoq#&ijoos1Eb& zd~8d&Sokb>X^uYmE=RH``n|Lc<6e~R;3QuKciMLua=e5@-^8zJf*|vi-WF?=U`4#) zsyk)zuH0tVk?~|M6UxWO1Ld*8C~9%(NRtTfQiO%Ir}WLJlh{(z z|7qTZ{*dy`Vo4erpAV@pTB`|nk2HNKh8sb-5iO%5qq$UK9Jk#}8yeI}4SHS(Nv_{u zYpau%L-y+Hid@w?$i}HYDo4Z>h%Kol>CBQwHmNjO3xCDQA8%{A*2?NVVs46r(o;OE z(!>zwo+4$gmu@0Y9pRDB;XQOIXltEM=?OuABFW(|Z?^U=+`VryQYmf3O9V?-5p6k% z+4zm$Snq<1S#_}t|8}2nH?4?y1zYvUS$6h_X7au7WyxlQqjzr!W~nlL4oyv2H@Bqa%orS1T9* zFb?%pw_!V8(Q^V#OXxp)&Mqpmwp0b5TZ7^W=_EocenW5=uimR>T{frdJ0$nmbrc@9 z{h;e)RGhpeU7f$ZQoWCbQ;y&G&$v9DF76y}HMP=G`Qm`69dHVw;;TlXk{LqXDtw7I zF}%`9K$g|TrtT?FM*CwSUMyX$L1&vZK1FKk?|Ju~7S#LRso@EEN~${MBa+!ddqYgQ zo9I#QeYDc9tHyV*h;NGT1ObiU2xk;ldomd0#Fr{s&t~u=IbT-ZHF8B^;M!NW`}?A1 z!glX_MTLT0STbA1e*%YQ#-#ayed0^9rf6`1v3xqoi9>=*RELl$hJL#W9k18I@ z{7?J_DCm#*{?iyHYe@(HUAezpocSRBcBz4$wc|fz{=a(GYRw>L1f)Hf+m+tkp`Z5u zzTOiB+T}k#t0Fne%MsadH2K)%S4L(#QkjwvKi-Bjr!vrpfX5vb1o6%2=Sq36zs`+G)xoaefHnXJNC(1(huP*|MP@vz!mA~;weE+9@ zc@Y3EAuXQm2nZ=ea&p$B(z;mgS(PS(sjsXbefq*_VjZ9gnIDBPZp)3fwkpp^{)dA@ z)c;n;-b0V)bG}VMmtUJ|)K+2h)qLBUBvJWwtW*^gYPxl-gnoZsHnM26%lJ}D3kEmX zGW7i-$u>YisSEe2(J;M`bwKDZ{+|Mke=pZsctmSBQI5##$8JF*F~w)(ac0A86C5Y+ z`c%P=|5h}M{Y4Kw*V4QEGc(rPwM+sNoEdh?8-UKG8Yc{y8B-mUqNvGv0{DKURzVf9 zpO-lI;j|CsuKE}meW%?UkKBnfORDaz$a6sEN_7|q85)j4C$=RHzE+NKrmcXOJm*ECSZ{e#}5&vEJ{o0-_ z`E~~&iENS~f31oZUpeGCmo1dWsyfJ>uSo=#d{Q>_W)FXm`0)X!+Y%dxH2fKs^@Wx%SjW(}TOJm_Yp2IsbCXXr_R<7~dIfobgU36#hQN3&vGcKf_iI~29Jx@S19eMvmysvT2W@2kJ{nR zP~5RLxse)&fXI@kc7Vd~Hu#ptXxd}wuRrJ!Lt*mM5^g*D=i0vo(a^$Y`-;N^rDeT> zC&G+&;1^TjWhuhIgi;pG3FsPC0uMrl@8`|PrUh^ve|$2kOy3lg9-B~>Qo~}((+tAM zI14$;E!eW_uIj=7RahueuiJYLKK`ONdl#}){ecr?O(N5oQItCa^qFgK$5R*=7cmP_ zObur|6bgX$&c%lE9JuxWg}D7TV97Z#NJu96MkPVj8D!O;ad~F*9GFRR^Io0D5Dj~l zq<4mvjmm|>@AChXK0CtvyYBh0l$+}LRFru|ieAAta?`-Wo2fO^)qjdR=U4K{lzh&I zRD~AolD9yqJ}eLVLO;>04&dMiy+^OA@AQ)L9|O;2?->VeHskyB8g@jwVqhdcnY6_T z2qD~C*&=9m4LIN@NkDPRaWflF_VII$y)P2`yCZZ?Lun*lkGZQ(x5v#J7Mq<Kl~!mbt~VIwISbXiWL4|Xb%=U$~QWD<+MN@HS1M#1>w=2K33<9!D5Q}E-yeRi|O9p%vx9% z`9QB3`{v6Xg#6U>Yfnr5xX71Ct-+`@Oe{d7UTb@;;SobLD$jvZWxr?X_`iUmzll~e zAomO@+@DuWJzbk`AItl2^eDL|q5%LeDU?2)GukfQo<>xBtraGZ>vnB)N`K zV0oQh{G~1YJGtMv(9gpG5wze4Yc-=z1*MusAn7tbg;SUOajd z`@+jM_UBV@Xy%=cPIh3D&@!fGCCYuLy0uPOP!WbhYA$X1;Gwf)Gp3Xn?D_Zqbszgb zKt~bzA7+|NA3lBrYW<#U+$KU$`oec6b&dJ#Lw@m4JA7W!1n`e>+UH4J0Mu&MTNMw$ zyWc>qnX}2BT^$pJ0d(P5_e&gq^8~sH>M#fD& z01RGqMV7dEx=+ywG6rgzJ;$zp{g!-`s#AgW7e9qsNpEg(D(>eua7TeaApzl*9KBQp zxR?+hjq=XzUH47*_K&0d{l!#55fOG90(sj6g-|z+w-tXRoBKmRoSolv^)Dlk*s*xE zef{4Du*itnt_qyMR@Vx=Iu4GhAeSEr(jz#F&@F;$EIO~6UL|qc)k++|r(<1P`1+V< z2+$VxfAx63`laX)8-JwU2xthj_+q@6n_gH8G=fw}j3Fy-Xv}d~NUK02JPHrbmgjA* z%&k2lG(=+!n2*S}GT>tC0v+IYsH8|rMdt@9J?e5&QlfnM2=~?4ZL_(=&BMcxo@w8; zvRz(m^WEFj#?aCu{8tYj@z|_c=feT0dI02mfaF%<&dyf4mq5hqP>vBsc&BJqQ)Ux> z)CxT&rnvvBW+5_U9}yDUH>al+UYfQQ(zd1mRRa)_%n?yzPGjR`UoO`BAJ6!2K5RA@ z(Xe~$u$n$vvY*=85^@unjQRHhhIpv1JKquEgmZql-grJ)l;u>8`+}l3dH^qhC96oU zFG=YJ*5pmXnJw9hrCyY@k>?F*gC>glY>;l$^yWsZ8~f_r;(A ziM}|#c&Kj1^q_l?D*_ogV|-|pa#st~^k7(=lQw?Z!OrI9SozTUd`N!a8nD?!+9NA+ zKOb_Klq%`zDe!!~_G@QnZ@T2`YUi=t^XHRCgK4^FWwy$1&5)k*4Ex(Z_Pguz0Y94$ z6{mrESx-VUJByt1awJVn%`wc4AM#+|ULui&we^-BDBT%=WV$I45ePypGN`x*N`s%- zb##|e8yhyV!3dFI?AjZ{g%+FBU#JoM_+r?h?^9ei#Pu=7UL@L?S`5-rU_cNQNB~G2?CDiq?<>|PdJ6`&-5q0$xv_|HT?oI)p2ZYue zq75&AkiqFwIKtsNPl|ys8!`?Kj-H-glg7;3Nfn^-sh>Et&$hrv!e(%3C3-OzjeL9V zkZHgA58osYo-QkxwC5*N`tV+M3~d`lPfehv}Yw&!h1 zR0pa^1YG?$zYXPcmj!gMsG|`H;Ce~joAzh3eqMwin5{bMG^FXzjygKJXpJxEI%R9y zgY7aUtf>XRnwtxVu2O)1u-Oc0VKa?(Y}!v8e)If!vD4GfhKuOcOcNp{&tcC?sAmO~ zV3SaKh%W_VsP5;u>6O)0R$N5R)Wnu`qJkwXGp6CZ;-e-@lY$Ugh!(w;0e zX8j@R43&W35V2b{9@$|w{0l&*19Z&bE9Vo+!PJmj;0pwD3k}`~t))8W?Z*Zwg0x`D z=QpkrcIJgp#p}OB9vsrfJ`DoLXC8l@D5Z29B(|TA?RkLC<@WZ~JZhZIpOr6Rhw=C()K>G06w2-0{)$ z8hO$5$lycZx|m~vI6#MU)(6vN`8}5Y7;kY6y$EgmDDwB-ZMQ70R;t) z1Z+DLm>hTrh^J%XV2tKF#BA;z$pOy*E*aS3fk0w&M+jJZ6I&i0FDaIYft{_=fnpI+ z>$j~8kte8-ZZS=jX0Y2PflzbHz~x^X?M^62&KHypGwtl^&;IRgA5ooJ5uVO(Ic{iB zrfRl)7TRy^7#9$ldf4|;1bCb7>`tuq1HnM_tGakC#R1^flbZfEmGEk*A<5jDwysxkR_Z7kwX8Cjp`*3);KL zFHG0PYs`8zrH?y@)mKqFkOr!@w*_u?aQvDntu&_##kQ9F$$R8Ai4}Xb^uS8IEC&xR z4QQnP!vjbAC+LU--8WWgW3IDvg-*2&{2A%aJLXXB8MnxZ(hU1cZP)UNkN3|2s2JPx z@fs8ue)0zxD=SHlF%YEdl(}!KiVc2!cc@QheK!+{j~%U~NL=tey?}flfxe0G`sBfk zK|~(T_@g$}-SsB29R^9tQu7fb%-#Bm&t^59F)a%tWo33~TdoJta?)d36_;?ktY4pC z;taejDdpxczR@c!9sO-TdUT#v422XXfM=~79T6}>!u-6{pW)GJ(#A@PzgX(JeJM;n zwfj`ZJw7vLH+=Iu4jOX5qv^#^zJm%`@=JfE&~pNVzC1cytrS_x}#ymTrJFQl$iph5ku6o7$HU^KoE)ZpHy=zkgIL;{c zralF)76@rp?K!&Uux6$I7pq&XYCuv5DyR+vl0Z7BxR_n-jY(hF!Hl`hmU49%n2!N) zPN*h!q3)ve;@a;hB~9Hjx^nfxRZXQ-W;o@0FCoJIvyf*tI}>2mk^jnAhP2WP;E zLt67hXv%6{9e>J{Y{qnG%24Z4JEdrIg!xsv)cij2l_Ng!cEtkfm&VpljeR4o(*tYI z-dWcs>7%_EKDQe9smFZrwILFGiDN6k_9SbRr@$#}Z{y!miU(cO%H^IYrO+-@*bpY2#->-wqq)OgUJN!>pEcGW`A zT~lf1IjP?)!5CMl2E|7ku<75v8pque$HcJIEXaMcKjZNHDaYIc+^2N+7n%!89La}m zpY`YDN9>L=ny^m(xeNd|mz9++jYKXwxxd(sMc+xY`kS|ap}aVq;;2IF>ft%Ze>&9>p_nB8b*?VjulYx$ z>ztg2tCU~oU{}HD!p}dH7S~5itmsDaymnx{8oU%B))QD;aavF5+^sNg{A#pQjr9 zd?%9rk{fxg=?gr4{Sf%jm z*ovRJ6lwiENkHU*ml591{67?4cfFox{g=`T{ETuiq7s$(Zkqy0<$I8ZFuuOTUt$hX zP3Q|>1vb~1oh{24w(aYYR(m6=idp?vkbDCgTHwP=mtiBrI@=pQiAO-#lkrkxKev#> zm)izP1!3&if)UouX((GaY;Hj3dY~;*q=WI;A8@BG@ku$LWSgm@5;OZsbgR0=*X+5> zY-C=Y+xDJrMVCc@2iWG+6?-As_oeNfu9UZSey`yY@i9*3Yf{=jxeN;VOUAdAD@+y)d|0`Zd z89_w_b6tu&U8zNH7{WI+73W(HqYl}$`tYqh4d;$ok@U=WYsI_{mZR|`B^|jAs}}?} zlq^)UDz=DJlnVjD`nrP%pxrT6l7~8Sjjw4^APU^PQs%iCEy1(Xb? z<~n_JM99}nmiw?tcJu)%W`bA}8mJ<+dsg#^oK(oJ@tcBf$Q#mNu4QyPvU3||FF$-H zDmqfq^P+vKFVK2yo$k*2EOGy#jz0=gMrLZXHmv3b%p-_S9hWaLRL^<;b_&iz)w3x33MK_zFJpZn{rF-+ij3`*Z~dVWIWN9RaVy8jNH*r$!;` z<<3R&?H5`YLhSucJ))!YS%l{KEoZg8E%dJMIx9$7R+dEmc?i+Fej#u#t!D<<0vvpT ztt1igz1b{N7@s>=z#sW!NK#zy$ORzg-z!uRr9t45#OfhUP~6BlY1l9!2cruydxwg; zgrwxE)TW2HpeNeMIYGD?QyBqO*3MF)SkK8X6neqEb&4k|4%Sc0%)FqFWaw&deAY3p zme){+MMDIfzN3c8akjtg?C%bhf6wA|kiU^@-qb3J0OBkQqJ|+#ubb1J*!DW>=#wpN zZpwQhbghVK6P=LBvJ#x zHxy`W)shpPh8+{OGQDHh@iMlvA?3 z!&*v#G&_sDAs~tBFvl@0#r8Y?hBj;QJPBiF`L%%pHD@s*ix`lhkrub7qwBe>1%hTo z_#eyPS~9oGZ|Vq6szebM&bbOR&#TLhY?yf4^^t$12-!4E5|X>7Vg zdX<>W9UJo2UbN9m_(zQOGj;f3w|ks4MSQ%y9XxcRR^2M!ge~{2->#g63mcEEj3)Di z47r;Ae2>;X7EMJ(7xoT=nA~Qb{p~}3YFX#s@Vu4E>IOZ@(G#U1FO*>NhLeBg+=Xl7 zsBlWvs&8c?kO-ws4=dWqu-vMTi!&B=HoQ3u*@bbm0urj(I6EK$XVo|tR=wG8p?V<_ z5~?+4LhPDsVcFLYxm@E5vv;5e{y}~d4Eiz$7!N-k?nj7owZrTUysePxMLC3`Lq$dN zHNU$OXJvZ?#Gbt7&!#j)ORV#JXUAxfx@tmEpbA|NHH=gxQgC`AOhNy=Fa#Hrr#Xv?P9*P`9Cw3Fkh-@+DWe7v zC8y-YXtHpylf7xqcdGJvCDE|M(0)QyO}1bRsVA)93t`p)tu4x{5Zk>lf}8$dt~GUM=IG zEegmSFFeUn@UY<*TGt=w*9!%Wl`7;GMeg2K5`YVY%$a;LuIz7rZL(C`zSoZ{#__qH z6DO)n;&-c--oeR9YT6m7wE-wd*8+pv^a%oH_{C`|8g_&~r;ks1z|j0Z_o#lzA)U&S z%+p^Ceb=MwWk_Ep*P#Gf@AQH?bkw_ptmr*6v3@Oe0PPa$5Ao(^35n38VDi zFxQgl?<$EX^te>Q^JXd-b)F=tqv{~3SzOHT>A_`><@EuE#9ME))?1?N+|pFwAo0U+ zjQ4Aayo9Y-Ra0sP% z7ElPEzn#6cm@2f8AIH?O#S{}2gTfM2rr{TpLc=0*Ti-X35=?lQq zkv!j!7Dc723!31ziMx6YA$#C?_4up98cGMMk2Z0Azk4mLf0ZxfPWh9)kod`G%2shF zuY{M_9r}@}Rv%rlBq}wu5a?k00Nv{+-Rn*K2eN>#Z*WTcaEi(DDSE(VciZjPpYTEyMLB@RxQXihl= zJhETe@rEPw`lI=`blN4RiodOpR{R`83RC>=JAd=MktX-e6xk~O=akQV$Io}ZQB)=; z);~vX>6pq4ceIPhkx5J8B^UGvq<9vn^^Utq*!~1bpwZ!|elxZC6}eNjE+=pg(}s3$ zf>?h+H~Nbpe~WqtsF4`YnwgjRBdv}rGN!RBD8JQ&bg~K_D zfKihmH%p7`E%PL?7I_H>=dX#WHJ1L+t#Xdj9Lv#EIU#ZBRWY-@WO~}}l*a{b^hnBT z1H-e$aH5W>8Iaka%3fVP#pJA#^L#V2ttoX<@hZk1mdV(aon~>jZqN!dQUq~DmoBmR zzL}-qp+!j(A-+Qg!1&Vz>O1d3eh0InE&JkZ8CaOIGY#p^OtE=A8V6Bv6q>_MRrsMi zPAU1JjtoJa@9n{-UsHW{(LVMc$L;badf=w=Ddm3dXCu7~^&sUp8W%L*1d zeL@NL!GmdS%d*Rtf*~fF8LMi#Tn0LA82)_b?=AtVfbya|w^V11i9FFLoJ=ky6?IKx z*Dmo~(CUi_fZ=Fa!WEgsT8G#N+Hi(oD59tspV>uo(sb6S!jnVms6wBfAWrdkoD=vS zQL+eN32FJ!nERU22noaqe#KNO7gO7+qns+~s;!3Sq&YoNUy(%cQgXmlr+JyHo45^)1S*K%z1hVw zybaPqS{2&ef*w$krSk+~NmDI{VIEF9T8^|5)nb8oK__=91`hv|4u0`(vne~Xjm*xA zO$TUVW5$OpNY}=l6nE+FNN8@qP|v{1$rhArTIp`7N)yzCRdRrR>Cgh6rjtxf3opQ` zsEjEIF%2p84yF(;+~3?VHJkP!E$ZvlcIqp}MeRdQ8XAGPr~;D*9%mQq0%}BLO~K_7 zUOjvdR)0`rIPukH46fFd4#`thFar~DM0Y|sc14FSo)iz_)oWNgQ5h$`Npl^d`a1@R zgJ3`6l0Fj1(VUtnA*>{gh(?nCy2|8F9`b(d{ELtVY=c zD*n5|{tL1V)07z*e6p^hRsV6jCF6=RVGr+tT)&7KRlLX#t(PRy_r=hh!7a;^_3YNeZcxgtgHt(IvB$yDP6pS-PVBc4*xHFT4AkKzqJy}#_w0yt+CWy%N2g_Gv z&iJ@051p{)F2KWoTG-{E{RhV|6?$CYsRh(n(U$3wISXr&E)eF7@mz*bH7ec?kMZyA zO&m|yw%r}+o>*-fLe|9Dqf&iYlAUw5?1FxNmmPwHj}Y>~-ojjRl91jZTA_vCT#^bI z0%^j+-!}V_grDCWs3)9?dn{a5GUCh`LDO($zMT~(E}Wwv4_o3x&fJoNRCu%i1Cz!) zw0E^)6wqlO?D^D-xfTSVTRMFXe4+7?2%O73A@<-wF^W($eLn7f9 z^TwuA@^W%2_U0^xO;cFo6i^p$kBw2V5RwsA6cloYkDaPcawY`U3APlTlcd6nHIyOc z6!FPwl${gM4>H3h1yz|NC-9C&w>qIm^;+=1OUS}IfPT(_z&H1e{+$5Kzhb%+kQ)8D z;Hxdl2SoF~l5g)u*_}J?yLkG-Ld#4?F_B`2^4~NiPY*;Q-CQ z6PC*ZvOQ5OGr|W!(}NDSZ`*M}*4`vGsStzlax11P4bgH)?)>;PRyl4S=s23Tf=Q}IqA^F`eg~B#R7b$Pv~(^GG9YxsH3aH59#UzwZSa6(LsoH*R16$h)C$rO7mo$7B%)DRG-~PI zcbbz(z?ZO5#p{R*Epln$#UJP~Fr{+%3SWi8(GsRBC}RfgCBxHFXWQGei>Y9`6xM`5 z6RpM^S2l$vCmHx8bzKu2u2#53b?={tG4#o;@FFLY%&c12ve8nrf2{6F!UFf3?p`a2}ZnW|f zvopg4hQoMJ#bkOvC(5>HL=-jQXA}mWg{V20nJMEGlYa@=8|wJ}>RFmaEI|Jd+8h-7 z9nF#)nxE`pQ+;I-Zw9Iq_Lx2z$d@{hcIBPVlKF|GKuk|}gFtfzNTm`vdk1CgFw%IC z`bTG6olw@;3A14k8`uy@ z!vgc&x;ANBIzfn*{%Cp@c=>8liVLDn`LUU%GAx%HQC0f+iB$mK2wf&BPfV)v%Hus5 zyFCP<*?C*9hbi^Tde!uz#%w2$CekUFDP|-QonPX-1F5PqYOXu=i_U4`+a<-ogP?y8 zvL{+n4C%9zfq0J&1%-*9{4($rKQ{F*@ip?#oxn^GvUh$KUUA#SHOjnSKK5!gq)YU_ zCb-WERo_H0`;?gxK%pF7G-sIhn(19SZx@>X2|Z!4xGUf}NP!>xO@tqym+{C1O*TNo zl;Z`jpgn+uXA=Jvqt`$K9KbS89HHHYPEFW$+Fbmj2uVp>Hc4V0F35cNGeHzU>V`y0 z#V+Y086y=EfRV|5!2(Tfn0Bp*Q?*V5;736YHZ=S&RgB>GGLUwG(m*nyCuPEN zwS{&7V$J*(Q<=0Gk5uVd2*>O3u}(DFUNx(i-ef35Ojww|e=cq49stTaWdYs8w*qI! zpyA7bb7e@FF2AojB7pj7Ib{x)%&G=$s}`->E>0-7wm2D;6x11E2=_`mX;d)1^gI{lJzU3g=%{9|wGGY~bAtz^tgH<1{U~pOvf?tHrThk~ zo7J4}yvfPQ9+gaMk0SCPq<+3!R1<>Oooym@j*T_dM!QCivel6@??LzHyUJTUowki} zCyu!m_%_zufv844T0iKd|8``nqgIKPG^FtX6oPL3w{Q`2z3QVuCv6^E^_v_0h6d zBOY0gn8II9C-b)of{OQvrEk*h=!{!?Bs@OVyu&MN6y=VZt+^W2yf2O?>e>IrS1a#1 zgB66!doV)`#S>Cv>oaJQOPsOFXdul|*3ba^a?jg)!d=2c{R{5zu4ENTf5d z=bbG($CQO!(D?Xw4C0)weKWIv4%?f~ei<^GmI|2+bL750lJn}s!JLk#j3(rY?nZ1t zVBG8F(NGP@nx(S2*O!5j`aZQ7|ICyo*PxoX5-?`dI3YATeuSipCSHe(B`ngY7wV{}&G zKU&p~-~A;kSOX)3(OBbRf!mN+)v(9ga=R+-VhHiWMhbQ^9GG!McoXffOx1sf^j_Yn zdB9fX%lv@OC7rq+s&d0Ig3}{ycP<^NJV$8hI(+)z2rXVlNDWmD_8jd34G6RAOw{41 zx%DM~xD~uPTh+47I)ep-X_x$K3+EBJP-W+tY>4|S+A~0m{i7T2z5O!H=iXe>s)E3J zE}Ow}y8OuR+*sD6&W^972rL1OfPhJiE1H5<3!J}mECA>5bYB0$BpB6hZoqu8bA#hm zp^(CO_4#SKN?@yH5`E4yy!sFKFA)UW?B6qHs7zyjodb|eC;hKu01wsi{s%PuOZ;Q%p5UwJ!1%hX|Vc9nO}MsYlrr8`MN|{KSuc-?ve~s5bk@xn17T6J9A` zrW*g}ubxa5G??G=q z6p-%NGy;;+-QD@mt?%=G@AseYoHNE5LpE;K+H1|Y&joy^wkR<|BB=o~QQ+`4Q~h4lx#K zvhCi!{Aw&FVpid+V{C@ys-*oK0)+TwMB<*W6+w$-(Bq*fkWeu?rd!4ux^*M$HBh%|kKQahdxiS1rS;|R%3fmCO(M?`Gh#pBVBI}n!D@Pd zgvw_*J!wP9Lp%LWCfo}5*JJbYNefO!ZV0Z#`M!v(EW=pq`xhy`Mvh*jc%}su^{FTO zzr#A`OTRQVrR$Cs??3<3O%PhDl+d%0#yH-vj*ikCs_OOhzcsYy?j0{a1()o3Yb_C; zSMY^`TA$~pjQC`4DvfW?Jr?9cht1f&s)UFxMP1#YkyW9QW^1M^lHn9Iq07HJo`5O* z4V$F-e%)SIaxaN!HlH81w&8e=*x)bWMy=6Usq7cHN4z!VQx;kdbNf?uRq4253T0C- zFfa*k?wqMdCVeUCaQRIh)bdQ-)Z{(*$Gv|SJQ?<9S{tP9jO)n3+|L9)(68H&nzY~F zwnTe&)Jt%`5=p=WHLf^Z%-2;|MAOqKH+$BIw%ZjGL;XC&YO#W6ewdUxEKxP)Y1Qh$#V zZ`$WRFH*Xkm4q%kQ2k653KvXaYS!)+n!ENNSbTyQV?V?jHyVUW#nSr5p0QEyv94Fe zr)r^#Sa7gdJ1CVtH6&!`X_jmX4I_gDA#QAmCqfDc`A*crOrzXXm@v$t2Lqq7J zj2uZ2lEx?-irx+(Aqm6LbXVM<*+I;qW+@|;XDn!u%|681e59y^04M&zN!PL&IzcwS zJ_i|I8sA~)xU9-tsw31WeOd#AV z=)B`-*EhG}pf?4y?gn2sGxVBTNJPc)d0&KD1SzAONbo$yKWOZBt$$RmMG>>eXnKC$ z2$E)J|JIIbq#okzgdJhE7?3dE5gnb{ftQb(RmHRX3$qLak)*3>zMT*gho?$;11-@z zc1QIwQ4;@oNwQMElsSQVL)SIyQp^zXDJ43!Qlm4q1Sw4>?K3JDzgp_^St zvSAu`v(>Gt{yQ_`cs6;yhooN8|76UwFhLj#{$91UFk`&D+IT1}ijt+(hmX&1JX?2P zq&zlWrEDwTai@67e%(;gjw<@|94e_7-9JM6wV0d}RXnop$Hb0~$9DGLLrnJv$@0{O z_I_CxL%%z2tHO9EBxP01(MEfT1|z}ueuU2ddHyAbX0;V%vz|cwYaPl4 zYoC9qnFh_>J%cSvPnFS+Pgam7cMsWD(|?!bmG5JF4UID0rOBqWi;^kqoVlb9K7Rky zSrq{r%tK;6wS0?J6vWx1`oKNi>u}#D@KSox~yhn-lv~=11}Qd zNl{V$ZQUlHPziqp-A)qgqXLn_JQWKKyL>AxntP|LkDZ?7Wd+~l# zY7*!i?{Ot&c@e|pYm-r9(a}1m-qm<>#?@Tv`|f45*`9rGXEL-|d$tK}FVbwIeAHf0Ass~-aNxoMM67j(>fI5uOY7px)u{3Vt3rqY4jUr=;=_`Q7hEGW_^(w2J;w({we;NHTG#2_pLoY=9;hb6MJ7Ux}9xE^{?6Z zH_N%kp$Vpo7?Ka-_L85^3HRgJuaDwmy>LxD9F$P6a~fPe4BF3zM3vkmqeD=Pj&7^b z+19t=gY$@--l|RKQdQ)ZL~_mDeX9KR0R)ubsqeX{!>s>yo3Wq_uYh*_^;9-_(nf9M z^I2O4Ske)jFQxF-MqhmUA`E!oo;I)Dv?O$xwp1WO`!q;FS#|7_Zdv;H+O3A}r-d^6 z@lEQ_0Q_C=)%=O8ps&%*CBZfnp`B@IZ_XKscy_-mZ^TZdca`9AC4*oIzs*JFRLMiw ziZNY@*LS31?y@^@UJivCYSg+hWE=fc@SfFI-(AAs(lVo90>B@sE;qxlA9WCCKS!tf zv$6Pez4CY{cyC6$9>tyZ!*aRidi-1B$pnr%%rFNCti9jXGPRJ1XUJ-$c7;Gm2Eb2* z5pW{f9!YMnKTFXJ&cZQMvtj?&Mc~pi_SYOTSKDDLI7JdS5*h&+An2JE&=Pv7qj_Dh zWJC$3^ew!(rTunLQS#Hz1kX~j;)oc0N8$b3XzeVXre0Vi?`VpjVhqXqB`$l$C10KZ zlFVDJRPhP9+K!s&)XIo3^1MRc?w#6WSA$*$ulJfA({0ZP*)q{Ae|NDQeD!+q!u0^Y z&;*n*QWr4F5(n;OwN z)8c#pmo6H>)N&P(YcXA!lm;{0y_!wC>8{RlV7x*ChZrEeUKDR9rnIkITN_%(sa({q z?n$Z-65R0(3T!JIG^`4yr||mulA&cA(P&?zaDp-rd*pv)h;GJDHl{=Bts6suy>v6F z58cM3i{C)A1nO)iWYvEDup~4e$2x3TftWnwUHPymBlE%9f}X)}G;g5e3q^hRpiF*Y zU7(%rgW>Q0_)xRtCH(f~a?X(Lt`W}BD%VqVzAM`1PeH^PZB+@{-3jybU#b9LgXk8N zASr5NivC}L8;mh1*db@qzGo~LbYjmh^I-74KY~}aQ2(#(I-Nckl1MuKSWcA`G1&nx z+|DCNpc*ysa?c9uWO1!)2H zhRu0gCf(hG3|Imx1)k~d#e^o)KVUt3b?SnIjir1g;jWwf9(PCt0bWm?V%8 zgMijyTroWP6EkWVaQVi%u6wx?H0t~Mx{{JkhYS9iE;|J{Jf}O->NOvD&ZCklN4#NrT1$!8& zVL{g~CY=H!!fwC_hedyB8D?8@uDi9@KejhUc%r`Bk$AhVQN}as1{if!QoCL^M&UW` zB>_imxNgmFI5;BMq357XO4`2(qwG^PaNW+Rz~x)xPaY|jme{LXHTzz{`t<3>hLqoM*q9lAO35cD!?! zcR+5#v~+*8@8yiA4!-*m82z z61#6$F>;^F=y-2?(%+&Ag>PHcM}daW_AuV<=h4+(glZmK($#Km3E6yT*74x;Mq(+P z4uq1_25qv3@=-eQSyW|-|r%=c?d)~1`wDO z*)#^Voe~?srf@4`d^i?yzbR=wzp-^nk6(TJKXgVMRVWRZz+gAagaIxBT6i*=fQ3MXTiBoSV;z#B2+A5eD#T{iC;tNU}J7NBE zT0I9n>e;CUH!X+JGhzxLbHQROsA2jCug>;*@?=w7_uGfc=rw*syFP7R?^upn+&vcp ziM?nFd?F4EDxGRE6%$twY&_(Q#qq1>UErN(xRUZLrW7ThGiUTVS8ycWf(ePvf)|ga zQ}aG2N6Rp)O9!P|jAIu;9K9YwQ8xNuhBwA~l+UPQx*Sn)aJVzXXUOiw*x1t5!M({z`H zmLaEn34;kN%INUtw4@TBc*d@h%Og1`nx5#vq?C7;2Ih%<^-faWW_Q)l#k%Wh5rg`W z(ol0-UE8x0{x5XZTJCsqkVlkrb^s(>a1z81t$DPAblmRzG@cdYKK|1iy8pFh?sc`7|!pwcbBL$Mtv??eZk7wESlEvwJY84&k-j zFFR5A3rQ}wL%$-88(iv-;pfSD$G}U+S5)-&n%T`HSxlaYa*lSNwfh@|Q27ff=^Q@qij*+t?sayxx6uCYr)& z>2fjm`_guRch2kHaA|2NI4sN%0-W7h$33zn?f7eHX+D*Dm}zncprl@iEhav^-I&Oy zW&s~G9^0y#aX3{MOgVnWlF^^u7iY*sK{*wWSE%LOiOWdL@u&}C-?Ug*j_ou@qVmqF-yYgIiZrDrB3tJqCS!v5W>NRf9j7`>E0CR8|F-aI%b`v!A!N};H z))M7f8k%71s@>6am^Y|l4ST-FH5^8roAyroo_erZv#{j}1Q@Mud8E7A@pfE&6S9ArKux}-lX%S3NDT4|OJI3izK%*)EcE1Z3^rf5GOiHC3B>Pi*P zy)eL&(MEKx%Ov}TuIqmCHW0|c;C%i&ZK9RSg!zRvbLd|;d>_*NO6RH~$cA#M`O}Q~ zOOZ!(N!l+~_5SPsVwLo{<{7P2*DdwS>vn@*NE`UP7}sU++Xi2}n8Ulrg8lbjM*!3F zKho&O^EPOW@rUiKKTR^HB*(`OR!*7Nbhq0ymc?zPN|4?B9V?7kjndJG-jXm!69kd? z`XTrj$z+_N)0wFt=s4r#;}ZW_&ILIvTBSJI4&Yz}Bz8LvT4jsE6K ztUp_nB$Ko*f;fU0zS6ZAw2=8m4{tQzvm^Aq&9lb%Yd4j}G1j;86-pHw=BoLpIo9!* z2|C>;kU3*92H6o_l$B&#zs)?;Uz@r8UX}~T`7hmvq&?~A&a%_Ec4gj@Q# zt%b!9cd9+I^L|z){c2JKm)esNx1I7fHB+R<1XU@IDKBpx{^l?`r#mauF>KCo$^6?- zbOtdalEbmsiX>iOL}G~}q)Z(J6vzR?RJHZgF9MD5_8~iXB3;ZPzZs*IZ+erHI2;n-$eFK;A*BbMoA=A%*hiTsu`DVl9{N4&y#HaOe zG_u+Rppm+c<@cB7`uLoGbG5)7PqEsv8`poQw9_i@XA_)k!$HuCLeF7@PRPjj)r!%5 zf&a?PV)@<-A5-l#JHIJ3|0qZOB)c?;peE!_TMw?81A#+~xItaH?zE4b=Xa0gIhrwn zte?#*54B(@i?4afceed&4*E+E9xvsq^Z$Ozy9jO@SOINC_(#ZVh7{;W_n5}ojjdlvGHf^+_kNH?38!)_?+U6_R~r;D8zmn3XQNp3?TJDDOv z?@OQ184LC(^`SVO{emN|I}`LzZo;{UA?chtNs654Y@ zQ7vKXBjgD#g5B;W8G}f3&7aqot{9E=jzSiQxsrR(3OdmB zXV*(va)S}F3X|CcWNd8tftGj#xrV1t_R>X^qzboT z67L5f@pgEPN}5Z@WrNioh;^^TvSP-u0YP}hEJ{kirYJXRw%qzc6f*2c1{61CvNlIa6L6TQLN7&z%t1PKx$+%aecLaUf%2o{zDHF(S9_wD`R7M1CHt? z6Yj{!yqk=Blt_ihhl%;3gShNp$au1gUDiMbo|(rbDKqmYq0&PcR2?cB8g{7igBZUA zBp|#F{X?Ew-eAsj=nRweeupDv+Nr7`tyo5^XF^*bJ%OvDNGyp1;g0!#&Y3f6AGUZ z^B=9_(F?KG$1*ZtdJXOs|0NjRr1*k8Ur5im;_ij38Hs9_qCW$+tX*lzsP`A1Xtrh< zp`)m^cp4jun1Xs^-bYigeTS!~0B1@>IZm3$e)=rSwybf(F?v`N;0bJ@gsqe~;V&yq z+2UWTJd3$IdNg2~5!0#SjT_KTEDvhEp{5hwF^(#AIsQG9u5jM4iMgwfFsjbJ&Uv?c z%#bP5#*+nhPJBt4U9HP64GLaDi{jGtPPO?j;0T|ztgY>8?)qq!CnpfD(ZlF^dY-B2 z$hL-iY_fh}b{s3~&cGf88vd_M>3ji9;vEh)6A*4Mht^YfT>hV~0SlRDo*OS(J!_FvkAnV#?x?Dy$BL#4h`%gMs zgbJ4ySrMj>rlIy|e4zM2K01Y3KOWzeeTCgC{P2}^$JMw@rl01qe)U~xrGA#d=iZiWXi7p?wg z!OLipi5CYzyMw7Qm|u%XSx9}7^@5!WdVu!$!P%j^hpH|&5-8p6N2d*xsX|K*pmY{Y zJ7!4X48n6?QbZf5_BV}mPwScG{IEL8*IV(+NKDUC&o0p@{>)+CqM^>c15$& zlK`e|uLy-cL5n#R*fDICHn57kLaMh)I%$W*EQ5~`PmKkD`n^Ldt5kb5a=iH|CSvut zbnUQn=2AV9Z|#XV3PJDqmD?Ys=O-!L=3Et~tu21i=I({Tp2zh1hsD9(*D5N+o?Dba zMA!1T)9Al+d#Ez@ri9r2VXhI@mnvznVm94vk;6I{2HD*GJCwyxl>{LwPtaS8G~7I zwt@E7?&t3Uk%Lh*z6)bdEweK5Iqx^?UN_&7!9VUmaTP){Rp|L5Mk%t@n##8FqA+DE zv1(D;*g{>@T!KJ~wyGH!8JU8V5g|8r3l;TDQB#v|o6FdcrOO7WAgtDl81%N~!!NKC zmXzMBO3FA;#LwIadmudPe#zXDu{2^w&!X@-)$V3ki+c}ci(sbV=BnO;?v;_ml0gm) z!?(-B(QFX@Wc3)%0oQE z>5*=!dPHth*5_;L+lW_c&*D!BD#~#UxwlU+ld5fy;xB4Ul9M%Smb>~SCBq@h@me~b z+i>(GgBK`4?U9sJuY8M6l+!&n7{dn+?x6PSbsijP0@oLf9in0fN3rK7W@g6nl$0Hx z(==P2mdbPv4YjgG$*z3Mr9(n{x;j%Kw|pm&k=xWd5U@O$SuvSNLwwcu)c-N!VO0KI z2g%(Dg&5_k46Zk)1hLbTr%mt0A)eP1NBu%N`E_}bPoZh8Y_ zi6qF>c*fX;+n|%hmNFX{Tt;5t8C|ovC4Jz!S>-&{z!8TVA_*oa|TDl9OX*-)z8a$W~( zER+hYZI+KuLpdzwl844;9$_pQUbGLWDXtch@>M^`NVz|tIkaV&aU8`0lmAKWT}L_% z4Snlknf+A{^z(BZ5fd9`Qqnglf-l43zBT7GwE{;I)3smG7de=!q(nq-ot+fb)Fc4I ztzM|b!JS5GQCe0LzLm^N?+P!5!ut|4_tzfJOU8(fGWD}KFNGeI;B9EvkyfmtnfJ>2 z{%hLYx-h?t%~Y0r3}WowikSJ9{D$n%FaF#f7|VtQc4G^eSa^e$6S`gV_dIdY-;rVc zUaoMP6$cM;cpDy-n_1@nd;gj(?Nm~Zf|eAO#1O@nWf&}q`A!ZWKsnhv!{c6xz zy$!ax@oVrUJL@PQk6bOwLS%*9sTFB7C&67o^qAExIQ^o^@pv)y^HJ{mmJb)m$#4zd zN$nB~1^Ib0bTd8Ajh)t=vgC|aEUf3gz>~wXMM>K9bX}_TCL_BjQg2Kbn)4K>5W0@8 zyF%zn<@C+%Q!}&;3L;Pk1P8o1o#c0gnP?Y@akLqo;b2e&z=tqPce&j~E7!7{!)?+j53GHPjfp|n z@zW3Sa2m+n`K%*X-xvCfiY}Z4XeE&-D7*A8t&}(oB{KRe<<}JE7mPb>C9-3;h))Ns z&Z_F-5n>gkZoMD+)f}B+hB$JKwBD;#r6%N+w7XudYPn{@IZGpQ6TZ{zSmMd3sHwq{ z&KUKEeyu}uJ=}gfSa&6Q#n*z4>l>qL_MSI0CtRL-$eN~RRmk8 zvBgjs*pB@=T38-hE-V2gc%J=z+64N-K6HAYlg)llm>wsCE3o52y(Ha6P|&)GFAZ49 z5r}LcU)k4ykvMc~yg3a}Gqo}`f~2S2AgIZ6lR!+Klx*RHXK^rim8`|9-W~1GY1vP~ zKZb`NnV#;lXKI$Tw|`o{3|3W8($IWDytjc9L=(K@FG+sCmvO=c{I8=^)OPBk{MR_g zLI0N!buEr6{)>xaW#w9z^_(ewn~BrM2p1K_Ut@bp8~s+>@iG=|Cd>OT97q#%`6H=G z>Ym;|5E8suVdJ#w={(w5;i_5t{uEc>=<#Fy@foJcGE+MDt(>F`+gYq-p6zfC3SU&Zx=$%BQHjOvzA%CWOHpOjo)OU4lT$Tic^f zcy67;3$H)n;7lxI!rF-46x`hZDtlmxlySu7Jo#K6JtEO{{zS5as}9$`74ZXirndzN^t z5nY|kL7B3N$|ZRDz?I;~0ts%nK6#}6Y8$1;!A)}+A=?&3B$55A&*j6)*a=5t)W_Ztr?xR`tO0i}XTebo1BbRACT3>n}#wXvjn4&?T`oX)RU_vcyPqloL;k`C;Nt1_s!{i4=g`Eoq8 z0vnk_y8X=Js%N>%OjwDn8WXyMGnINji{q4BR(Qrryv~xtmRgBVk zhA-qORLZHtXS$R@y9qy6d4KHZNN}eyiXSE`Uh1ciPC8+IOWM70DklmunsAU$ExO7Y z>#(_I3n;2TdJA#jW#~a@sMCZ3x$cjg$31FMq|dg1f@illMW1wtN7jaiIS9FIV7fTy zq`gpn5ognmiB^^`uFI01Td~HryI;Dfq@mUOOrHO>uoFjWC` zj5P|%3KF_?Z!t*iHrH~=H|pD#hw4X7zh#M)dkPC8@zBRf zFVP^;GU`{eV;1y7hF#82drGFooiWjQ?E`}rNpM>*Gi3B7gxlJgf%F)d%==@#q!=Ra z@6T60b90h$;MU+^T=Dyf6K{FZ#SPsKDk3zTHwk?`GOxVJC+--B&Ar#XvZ^vW_6iHB zf1zlb3G(7zclZ5V;`c{oF)sb^GaN;5gga}%Pc+odGdZ`t`LE+ySjU6W{V?q&+?935SJfMzj-lwkJ5YqL-m>!|s zdNfEuOHHoiTX1@IdDFwbQLfn_=(4{1*lOJ4$M6P7Q6bqynZbi?*<=UkrFtw~Ka*w` zTk;D4IP}BOA&1P0gMNO4LZ(s!=u;{_=|P^rq4D`wls3MopB=2k(UqV*r5mpENuajW z3o>r(g{zn>XwxV?CKFPvyH<)blmRGz)J$+Hm_I#rQ*f#Rx+>N4X$;Uoulf}(;KY`EV^(E_|i z^f!!IrlZn>aZeR;pjbyEl2mek3O`1<){Zw#NH>o$yyAHDt<|r4}_A?sfl2Y64 z8Kq5O@p;B02&a&ZLkX7K)oPnK)lboJU8K<4Kv!#Te|aNYyw<^Ya*Zy8nySU**6r9J zRe>q#`ZFcwO0JCNQk-lQrVj*Ly?ju@S0ChR zO$h%4N3FJ0E}<64Y&2HG;iP>k|D8o7_o|DQ5thvDwh;IJ+WhDgl&Mkj^}FW~P#_IN z$a;XCX_c`OSPz1;E-Z9v<9p5p2Mchbj5az_$}BFO1mtHONS<*xs^>Mc#-06elFP6W z%vzK)d!+@7+Q;)LK0|p}=7|EU0+B?X$bONyy)gTQb>i1xVm~Pq3FKf|MKa5s{yK)v zn`s#v@lhfAP|B__`Ps4COCFTKauGR$lZ9m@G!!FiWv($6!*()3t!o}3wTl&o6N>6) z=8%YakG`Ubc@f&G>qu+@&8=^)#jTyC18;6)raoumGnp26zLP501LaikUq2LgAc<}> zywn@u7gT490@!48*ps_xUvEh`XEJW74p|k;>SbwNBJf3+^P%O|l=y0?p~Fl{vCy0c z*w&bN0AhgM@5AQho2>jpmAzN-I3*CtJG1zeQe$VM&J!ciaqA1qF6QVq16P+lp+DBB zs6F&&&b|$d`cU%l12>9Xxyd3oHM7pjtn&xg|C>y@CC!vEKp1{}3I9_=Q-ZIV3YPo< z<4}KujI02{IA<2nCb^BBR6?N+gJ{kM(&R-IltnzbbF203S3FHqmCVEwD9f~lzu!l3HeiCB~%k(1LQ z1uZ=bcmu~;;9L(Xdr{h4FC^cC)?`v!`@RIeyfMU)UeW5>zBVsdDp z8UF9&vKJF7Wra3yu4A3bHQyA#j1ZwSBQWb-q-R~@F?$Rhz=5~9zwSd&!&c*m&kwu# zoE6_I$;c~!c6T+R9O+rMzOurzCy|%;PQ*u1w5jwa#{fYYB^p04gKq!ln!IAvd3ONw)fx7|Lp!8^lY(zRK7z|U8`5fOx(Q3q69NoTXos$zI%q<49!A)JlfUt+&GKz6Eja@UDb;9h3W{X_%od&+&gvYg0# zXOZr{DND~#TnsDK)W?hFJAfrOT#d7bO7(8++-}&&WQ%RtBL zR)EuJo%O@P193uUiulaT*N@=fYR(t)P4_nzz&D`QTYNN%v~G#^z4?ROs#9u@aSS&n zz{bpXclatL_;Nx<-1!;&sowG_0kCiyyTv2gf-52qK?(lILX+F%JZ{aX*TuE3sH!9^ zucp-)p+T3=vKD-tbT`1KPPju(z`3pmie^O}1u=ICL~J!jhQXiDd+D^*#*E$>bJ^J?{-43q zziv~U%eW^cME%nM187+N8%6;J15a#~5Z#awog_eL@8@~yx;(pSu|L(L)ctn54>bC? z+mq|~*VEF^&Bck@;Ja%M#uf7mAoibZa9H*Fu4K$DOlXWgV^dsG_+Aj`l$ z2xX?ysN0+t`je!7NcMpSJhm4KMYmRLe52Fja_cfFM>+H+*FzoP+IC>+j4NBvc@AR9MqsR5X;I?cn%(9leLw9 zIg>P4UL$PMs`EVxeT9J9-I8Bb3^o*hNB?hY9(yC*Gdr;8DN9sOx0A-uBb0#~F|A4RI*o{5z`bh^vTEeyPXOmSfQvol#!)AeF5 z1ql`TLoXkRg670WK)49%FIlP?hG%^*Oa^pD`(VHJ0oH_;*TYfBzej!Hs zM!-nUNfnBt>zQJ$TC!Rov6oj~kd%5Od_xFBg&>Dc^x)b&=ZlG{es;PIz9tqD6-G+I z{lrgS?Qs@36AU0PKQSPhsqDLLg>f#SN@#YoL}=i~VCbZ!zC@&HROcq@rhuez6(m;P z;c{3|UnkT|wHQ;K8M}4+pE!hLLaTGI;kcFh20!?poSd=!vB*JK#CHnQ($W%;!Sdjt z4(@HRJl)px9D`8Q!r=w_qCPjF6Fm_TvDx-kfrHBEUG$M6UX`};${s5z|%a)7PufsOd(@B7(zI0@1K{b-O)9ukfMY0oPL$vn$TfY2e! z+{ztLq2M-ina#MUo#JGH$o|NW`+O#Y+uxD3Ni|q-_$n4cfLC3_swgY!Y#O+GMq`FV zMRa|)l}6nxpcVBGTHN1gX6asb5@QS=~mN_mE!Ra*-m1r6)8 zszxUy^q)1((aoTc>ZLICZ5#~Qfs%_n zrz5bH`6=3IAQ3VSH!&ZaXrd|54cw^E7pX%&#i+e?YkomS-gbC?OkQk_obqFB1nbew zt)#RpDLX~tu8OKkMR85+prTTAa70MkCW_HVwihJMhM0gp2O}`70+RDvY1vjqB~1w{ zFLd9s{E@4?=R{aOVvl=Yy_zC{VMEXkA)6;N!G0PplNJp-QfWR>hc+K^tzTe(GqnE~HCYiub02{d_ySi7oy=)mQ75;{p>sCQvd+>;;63^*375R!ZBG=La9`8FT z$FvoH7Ys~;%i1f*3EU#$!-rn#;Vig@&(Xak-#mN>+N5u4!=@-l$6glet6# zgGQ4wn&C2h+c2i5r$fRb$oTsuP2Ms;e0W#+AWot*C_9!sxGGGOht(CCp1TT&4yObkcb2gP1!%b-NyTWRG zN+;vRtbsD#FY|5PMf?Gtyh8Onl;QTzF4*v1QvXX|1thOf{xR}Qy zxJQ2v{(XId@}vd2YxSih%jXCgN6_c)p#xT_OV1!}%dS-$H5q?6P{YDmcEVdfr6>Ox zSy|Zu;G9(NYajY*g@m z2xr0BHaBhJfA>h^(G{TWS76SK)si*en^;+PE&+Lqg;PZB$VI@SrzhWATu4~NuiT$Uq(@5L$b65p zVJVl*j}*)ZFzy3Wpw}Ibd+_U^X&c?)=vH_casuICM;dI}t*l%(NdOCgj&zzaK@0Fk zG`V#{S(8ZtK^f-g#B!~2oW#HGz$0xQ0XV>ifs6avObP=y@LNf_mjJC!*SP_J5UW-* zSwoqSJk9Gy7BmZzSnQ{Tmp3;zWxp+9PBJngZZG}R${EwQj2vKxKYr6dwo?FvktXfx z&%39xaukf5$$P3=s&dv#SWKFBN1GYLQ9pm`(d!Ss`#z%7Gsw_Fr$dh*;?^uED(=5H zIyu=NpZcde#n~fbV!2`IZLqOtjG370{Y<9!PU`fzlaDCMX<$b2`0G$yi$^*lrP!3h zI_`946O4q;2#|CyveNr&a^s<1COge|rY2e|{ zZn|x$S?Z~S&%x+!9ymP5Ll2DlJu3>(IdLiCDajvuD7e<{bYO$~&CpR|^3^7Q^AOwdz zamE1Q9z160r%(k60Gs|nJeN#wcJmvSVD=3fZp$lD1|&^~H7710M1O=iqnJM*&(?tm zc;-_c9XlrnfDF~YxN4^U@IH!h^u8v-E_5g60XzwZDY__cf{? z4wXL%R;#MNf9;Hz<_jXSdnu@C|1|Mtx*nj9@8nAJ8Lgy<&sMVSC5EYa(4~#tUR7%g z`5=;brzE3783sowZJq^TnJ{TT!E$T41C-bwAhNa)N^^S7pC z<E_(rJe#?x{KLJViYcN|$Pje-(5B}SVo<9$gIA*I@SHsckfhNG_Qn`n$B1F(6Nx|!Mb*RO@f&bbs_m?SuUioYxH2!KKhtmdHL zn{^8Y@mdE4KE8~q+pDQsc;KIPBRF7>DW$1DDSLY8w!XEqT&I1=F?Hg=$?s{^y@~n0 zp4uP~$lRA!Y4=CL(+zR~EYwYgF1Icnx=gAn_wE7Y@-{fiNUdQ>Y}ebOOB`GqL@*Cf z_5cPV8h{N?uvspMYao}lTUF%T!7>J*y}Yaxq~WM5dGVg(b{G@58)f2zzE8O*1Xk$X z51vI88HsAa*&YewRot`lenf_#3CX^F`&{$K+Oaep*t#~aBs~%pZI_5>rv!M9loXv! zU|<=!?>hyJi0S6Z@Q6FS+b7SSb>#zsW_-rdr8Df0|F9|oHChh|2`L-o4iLhY58Wo` z|Aklh)wSCiI__Qs2Vu7pBVltZ?1UFFzS-4n(x{CG;Mqv`6(d7FT`fV%Bmw0r);qbO zmu-W1XZ|b*RqLSF_u*dtfe6b=fE@ahh&rKkI^XS|!Ylv}-2N2HtB6`eA9atSxk@^r+$qAN=0XjuC}tRiH*8 zW;D@;fDrvb-nCH&4EQa3{eT=8aA05;FrQDZe^%-;l|Hde$yfamX=I2V46FmP+8@D#(5`KJNo*bet* zfFYcIR=6G==%=FogAF=Nz=MS(JR1p4M|U{+H1G;wy&he#`tYaMMi6iYh#xjwoQs%* zgdHFIy#T>bCk&p@+Z_A#>yv-?l-#?Jak2VgR_b=RL7De+F 7GMGL3#YL4T; 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 P0UXK->heW*&HVls^qopq literal 0 HcmV?d00001 diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should disable sticky note when scenario is not saved #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Sticky notes should disable sticky note when scenario is not saved #0.png new file mode 100644 index 0000000000000000000000000000000000000000..e3022e7c644e3404b5e5668ce228b101fd764dd3 GIT binary patch literal 71149 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 literal 0 HcmV?d00001 diff --git a/designer/client/cypress/e2e/components.cy.ts b/designer/client/cypress/e2e/components.cy.ts index 88587a19555..cf77c52d321 100644 --- a/designer/client/cypress/e2e/components.cy.ts +++ b/designer/client/cypress/e2e/components.cy.ts @@ -233,7 +233,7 @@ describe("Components list", () => { cy.matchQuery("?TEXT=xxx"); cy.viewport(1600, 500); cy.wait(500); //ensure "loading" mask is hidden - cy.get("#app-container>main").matchImage(); + cy.get("#app-container>main").matchImage({ maxDiffThreshold: 0.01 }); }); it("should allow filtering by processing mode", () => { diff --git a/designer/client/cypress/e2e/creatorToolbar.cy.ts b/designer/client/cypress/e2e/creatorToolbar.cy.ts index 0eb4e53d80e..304f048f375 100644 --- a/designer/client/cypress/e2e/creatorToolbar.cy.ts +++ b/designer/client/cypress/e2e/creatorToolbar.cy.ts @@ -26,6 +26,7 @@ describe("Creator toolbar", () => { cy.contains(/^types$/i).click(); cy.contains(/^services$/i).click(); cy.contains(/^sinks$/i).click(); + cy.contains(/^sticky notes$/i).click(); cy.reload(); cy.get("@toolbar").matchImage(); cy.get("@toolbar").find("input").type("var"); diff --git a/designer/client/cypress/e2e/stickyNotes.cy.ts b/designer/client/cypress/e2e/stickyNotes.cy.ts new file mode 100644 index 00000000000..5a8faef69b0 --- /dev/null +++ b/designer/client/cypress/e2e/stickyNotes.cy.ts @@ -0,0 +1,65 @@ +describe("Sticky notes", () => { + const seed = "stickyNotes"; + + before(() => { + cy.deleteAllTestProcesses({ filter: seed, force: true }); + }); + + beforeEach(() => { + cy.visitNewProcess(seed, "stickyNotes"); + }); + + const screenshotOptions: Cypress.MatchImageOptions = { + screenshotConfig: { clip: { x: 0, y: 0, width: 1400, height: 600 } }, + }; + + it("should allow to drag sticky note", () => { + cy.layoutScenario(); + cy.contains(/^sticky notes$/i) + .should("exist") + .scrollIntoView(); + cy.get("[data-testid='component:sticky note']") + .should("be.visible") + .drag("#nk-graph-main", { + target: { + x: 600, + y: 300, + }, + force: true, + }); + + cy.get("[data-testid=graphPage]").matchImage(screenshotOptions); + }); + + it("should add text to note and display it as markdown", () => { + cy.layoutScenario(); + cy.contains(/^sticky notes$/i) + .should("exist") + .scrollIntoView(); + cy.get("[data-testid='component:sticky note']") + .should("be.visible") + .drag("#nk-graph-main", { + target: { + x: 600, + y: 300, + }, + force: true, + }); + cy.get(".sticky-note-content").dblclick(); + cy.get(".sticky-note-content textarea").type("# Title\n- p1\n- p2\n\n[link](href)"); + cy.get("[model-id='request']").click(); + cy.get("[data-testid=graphPage]").matchImage(screenshotOptions); + }); + + it("should disable sticky note when scenario is not saved", () => { + cy.layoutScenario(); + cy.contains(/^sticky notes$/i) + .should("exist") + .scrollIntoView(); + + cy.dragNode("request", { x: 600, y: 300 }); + + cy.get("[data-testid='component:sticky note']").should("have.class", "tool disabled"); + cy.get("[data-testid=graphPage]").matchImage(screenshotOptions); + }); +}); diff --git a/designer/client/cypress/fixtures/stickyNotes.json b/designer/client/cypress/fixtures/stickyNotes.json new file mode 100644 index 00000000000..d31aa75ff85 --- /dev/null +++ b/designer/client/cypress/fixtures/stickyNotes.json @@ -0,0 +1,58 @@ +{ + "metaData": { + "id": "sticky", + "additionalFields": { + "description": null, + "properties": { + "inputSchema": "{}", + "outputSchema": "{}", + "slug": "sticky" + }, + "metaDataType": "RequestResponseMetaData", + "showDescription": false + } + }, + "nodes": [ + { + "id": "request", + "ref": { + "typ": "request", + "parameters": [] + }, + "additionalFields": { + "description": null, + "layoutData": { + "x": 0, + "y": 0 + } + }, + "type": "Source" + }, + { + "id": "response", + "ref": { + "typ": "response", + "parameters": [ + { + "name": "Raw editor", + "expression": { + "language": "spel", + "expression": "false" + } + } + ] + }, + "endResult": null, + "isDisabled": null, + "additionalFields": { + "description": null, + "layoutData": { + "x": 0, + "y": 180 + } + }, + "type": "Sink" + } + ], + "additionalBranches": [] +} diff --git a/designer/client/src/actions/actionTypes.ts b/designer/client/src/actions/actionTypes.ts index 306aaea65fc..31218cd0e4b 100644 --- a/designer/client/src/actions/actionTypes.ts +++ b/designer/client/src/actions/actionTypes.ts @@ -10,6 +10,8 @@ export type ActionTypes = | "DELETE_NODES" | "NODES_CONNECTED" | "NODES_DISCONNECTED" + | "STICKY_NOTES_UPDATED" + | "STICKY_NOTE_DELETED" | "VALIDATION_RESULT" | "COPY_SELECTION" | "CUT_SELECTION" diff --git a/designer/client/src/actions/nk/assignSettings.ts b/designer/client/src/actions/nk/assignSettings.ts index fb54a22f52b..f26d0dab223 100644 --- a/designer/client/src/actions/nk/assignSettings.ts +++ b/designer/client/src/actions/nk/assignSettings.ts @@ -39,6 +39,13 @@ export type FeaturesSettings = { redirectAfterArchive: boolean; usageStatisticsReports: UsageStatisticsReports; surveySettings: SurveySettings; + stickyNotesSettings: StickyNotesSettings; +}; + +export type StickyNotesSettings = { + maxContentLength: number; + maxNotesCount: number; + enabled: boolean; }; export type TestDataSettings = { diff --git a/designer/client/src/actions/nk/process.ts b/designer/client/src/actions/nk/process.ts index d7209ee2de0..b00dfffe989 100644 --- a/designer/client/src/actions/nk/process.ts +++ b/designer/client/src/actions/nk/process.ts @@ -6,6 +6,9 @@ import { getProcessDefinitionData } from "../../reducers/selectors/settings"; import { ProcessDefinitionData, ScenarioGraph } from "../../types"; import { ThunkAction } from "../reduxTypes"; import HttpService from "./../../http/HttpService"; +import { layoutChanged, Position } from "./ui/layout"; +import { flushSync } from "react-dom"; +import { Dimensions, StickyNote } from "../../common/StickyNote"; export type ScenarioActions = | { type: "CORRECT_INVALID_SCENARIO"; processDefinitionData: ProcessDefinitionData } @@ -17,6 +20,7 @@ export function fetchProcessToDisplay(processName: ProcessName, versionId?: Proc return HttpService.fetchProcessDetails(processName, versionId).then((response) => { dispatch(displayTestCapabilities(processName, response.data.scenarioGraph)); + dispatch(fetchStickyNotesForScenario(processName, response.data.processVersionId)); dispatch({ type: "DISPLAY_PROCESS", scenario: response.data, @@ -56,6 +60,45 @@ export function displayTestCapabilities(processName: ProcessName, scenarioGraph: ); } +const refreshStickyNotes = (dispatch, scenarioName: string, scenarioVersionId: number) => { + return HttpService.getStickyNotes(scenarioName, scenarioVersionId).then((stickyNotes) => { + flushSync(() => { + dispatch({ type: "STICKY_NOTES_UPDATED", stickyNotes: stickyNotes.data }); + dispatch(layoutChanged()); + }); + }); +}; + +export function fetchStickyNotesForScenario(scenarioName: string, scenarioVersionId: number): ThunkAction { + return (dispatch) => refreshStickyNotes(dispatch, scenarioName, scenarioVersionId); +} + +export function stickyNoteUpdated(scenarioName: string, scenarioVersionId: number, stickyNote: StickyNote): ThunkAction { + return (dispatch) => { + HttpService.updateStickyNote(scenarioName, scenarioVersionId, stickyNote).then((_) => { + refreshStickyNotes(dispatch, scenarioName, scenarioVersionId); + }); + }; +} + +export function stickyNoteDeleted(scenarioName: string, stickyNoteId: number): ThunkAction { + return (dispatch) => { + HttpService.deleteStickyNote(scenarioName, stickyNoteId).then(() => { + flushSync(() => { + dispatch({ type: "STICKY_NOTE_DELETED", stickyNoteId }); + }); + }); + }; +} + +export function stickyNoteAdded(scenarioName: string, scenarioVersionId: number, position: Position, dimensions: Dimensions): ThunkAction { + return (dispatch) => { + HttpService.addStickyNote(scenarioName, scenarioVersionId, position, dimensions).then((_) => { + refreshStickyNotes(dispatch, scenarioName, scenarioVersionId); + }); + }; +} + export function displayCurrentProcessVersion(processName: ProcessName) { return fetchProcessToDisplay(processName); } diff --git a/designer/client/src/actions/notificationActions.tsx b/designer/client/src/actions/notificationActions.tsx index bd3250bfef9..44a2beff88b 100644 --- a/designer/client/src/actions/notificationActions.tsx +++ b/designer/client/src/actions/notificationActions.tsx @@ -2,6 +2,7 @@ import React from "react"; import Notifications from "react-notification-system-redux"; import CheckCircleOutlinedIcon from "@mui/icons-material/CheckCircleOutlined"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined"; import Notification from "../components/notifications/Notification"; import { Action } from "./reduxTypes"; @@ -25,3 +26,10 @@ export function info(message: string): Action { children: } message={message} />, }); } + +export function warn(message: string): Action { + return Notifications.warning({ + autoDismiss: 10, + children: } message={message} />, + }); +} diff --git a/designer/client/src/assets/json/nodeAttributes.json b/designer/client/src/assets/json/nodeAttributes.json index 2e18a54a2ff..46910e6201b 100644 --- a/designer/client/src/assets/json/nodeAttributes.json +++ b/designer/client/src/assets/json/nodeAttributes.json @@ -38,6 +38,9 @@ "Aggregate": { "name": "Aggregate" }, + "StickyNote": { + "name": "StickyNote" + }, "CustomNode": { "name": "CustomNode" }, diff --git a/designer/client/src/common/StickyNote.ts b/designer/client/src/common/StickyNote.ts new file mode 100644 index 00000000000..d525b8fd992 --- /dev/null +++ b/designer/client/src/common/StickyNote.ts @@ -0,0 +1,16 @@ +import { LayoutData } from "../types"; + +export type Dimensions = { width: number; height: number }; +export type ColorValueHex = `#${string}`; + +export interface StickyNote { + id?: string; + noteId: number; + content: string; + layoutData: LayoutData; + dimensions: Dimensions; + color: ColorValueHex; + targetEdge?: string; + editedBy: string; + editedAt: string; +} diff --git a/designer/client/src/components/ComponentDragPreview.tsx b/designer/client/src/components/ComponentDragPreview.tsx index 1028f91776b..c927d701401 100644 --- a/designer/client/src/components/ComponentDragPreview.tsx +++ b/designer/client/src/components/ComponentDragPreview.tsx @@ -1,11 +1,13 @@ import { css } from "@emotion/css"; -import React, { forwardRef, useEffect, useMemo, useState } from "react"; +import React, { forwardRef, ReactPortal, useEffect, useMemo, useState } from "react"; import { useDragDropManager, useDragLayer } from "react-dnd"; import { createPortal } from "react-dom"; import { useDebouncedValue } from "rooks"; import { NodeType } from "../types"; import { ComponentPreview } from "./ComponentPreview"; import { DndTypes } from "./toolbars/creator/Tool"; +import { StickyNotePreview } from "./StickyNotePreview"; +import { StickyNoteType } from "../types/stickyNote"; function useNotNull(value: T) { const [current, setCurrent] = useState(() => value); @@ -53,17 +55,22 @@ export const ComponentDragPreview = forwardRef nu return null; } - return createPortal( -
-
- -
-
, - document.body, - ); + function createPortalForPreview(child: JSX.Element): ReactPortal { + return createPortal( +
+
+ {child} +
+
, + document.body, + ); + } + + if (node?.type === StickyNoteType) return createPortalForPreview(); + return createPortalForPreview(); }); diff --git a/designer/client/src/components/ComponentPreview.tsx b/designer/client/src/components/ComponentPreview.tsx index dacb8dd7dae..8053083663a 100644 --- a/designer/client/src/components/ComponentPreview.tsx +++ b/designer/client/src/components/ComponentPreview.tsx @@ -74,6 +74,7 @@ export function ComponentPreview({ node, isActive, isOver }: { node: NodeType; i })); const colors = isOver ? nodeColorsHover : nodeColors; + return (
diff --git a/designer/client/src/components/StickyNotePreview.tsx b/designer/client/src/components/StickyNotePreview.tsx new file mode 100644 index 00000000000..af37f0fd504 --- /dev/null +++ b/designer/client/src/components/StickyNotePreview.tsx @@ -0,0 +1,62 @@ +import { css, cx } from "@emotion/css"; +import React from "react"; +import { BORDER_RADIUS, CONTENT_PADDING, iconBackgroundSize, iconSize } from "./graph/EspNode/esp"; +import { PreloadedIcon, stickyNoteIconSrc } from "./toolbars/creator/ComponentIcon"; +import { alpha, useTheme } from "@mui/material"; +import { getBorderColor, getStickyNoteBackgroundColor } from "../containers/theme/helpers"; +import { STICKY_NOTE_CONSTRAINTS, STICKY_NOTE_DEFAULT_COLOR } from "./graph/EspNode/stickyNote"; + +const PREVIEW_SCALE = 0.9; +const ACTIVE_ROTATION = 2; +const INACTIVE_SCALE = 1.5; + +export function StickyNotePreview({ isActive, isOver }: { isActive?: boolean; isOver?: boolean }): JSX.Element { + const theme = useTheme(); + const scale = isOver ? 1 : PREVIEW_SCALE; + const rotation = isActive ? (isOver ? -ACTIVE_ROTATION : ACTIVE_ROTATION) : 0; + const finalScale = isActive ? 1 : INACTIVE_SCALE; + + const nodeStyles = css({ + position: "relative", + width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH, + height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT, + borderRadius: BORDER_RADIUS, + boxSizing: "content-box", + display: "inline-flex", + filter: `drop-shadow(0 4px 8px ${alpha(theme.palette.common.black, 0.5)})`, + borderWidth: 0.5, + borderStyle: "solid", + transformOrigin: "80% 50%", + transform: `translate(-80%, -50%) scale(${scale}) rotate(${rotation}deg) scale(${finalScale})`, + opacity: isActive ? undefined : 0, + transition: "all .5s, opacity .3s", + willChange: "transform, opacity, border-color, background-color", + }); + + const colors = css({ + opacity: 0.5, + borderColor: getBorderColor(theme), + backgroundColor: getStickyNoteBackgroundColor(theme, STICKY_NOTE_DEFAULT_COLOR).main, + }); + + const imageStyles = css({ + padding: iconSize / 2 - CONTENT_PADDING / 2, + margin: CONTENT_PADDING / 2, + borderRadius: BORDER_RADIUS, + width: iconBackgroundSize / 2, + height: iconBackgroundSize / 2, + color: theme.palette.common.black, + "> svg": { + height: iconSize, + width: iconSize, + }, + }); + + return ( +
+
+ +
+
+ ); +} diff --git a/designer/client/src/components/graph/EspNode/stickyNote.ts b/designer/client/src/components/graph/EspNode/stickyNote.ts new file mode 100644 index 00000000000..62d59811c61 --- /dev/null +++ b/designer/client/src/components/graph/EspNode/stickyNote.ts @@ -0,0 +1,128 @@ +import { Theme } from "@mui/material"; +import { dia, shapes, util, V } from "jointjs"; +import { getBorderColor } from "../../../containers/theme/helpers"; +import { StickyNote } from "../../../common/StickyNote"; +import { marked } from "marked"; +import { StickyNoteElement } from "../StickyNoteElement"; +import MarkupNodeJSON = dia.MarkupNodeJSON; + +export const STICKY_NOTE_CONSTRAINTS = { + MIN_WIDTH: 100, + MAX_WIDTH: 3000, + DEFAULT_WIDTH: 300, + MIN_HEIGHT: 100, + MAX_HEIGHT: 3000, + DEFAULT_HEIGHT: 250, +} as const; + +export const BORDER_RADIUS = 3; +export const CONTENT_PADDING = 5; +export const ICON_SIZE = 20; +export const STICKY_NOTE_DEFAULT_COLOR = "#eae672"; +export const MARKDOWN_EDITOR_NAME = "markdown-editor"; + +const border: dia.MarkupNodeJSON = { + selector: "border", + tagName: "path", + className: "body", + attributes: { + width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH, + height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT, + strokeWidth: 1, + fill: "none", + rx: BORDER_RADIUS, + }, +}; + +const icon: dia.MarkupNodeJSON = { + selector: "icon", + tagName: "use", + attributes: { + opacity: 1, + width: ICON_SIZE, + height: ICON_SIZE, + x: ICON_SIZE / 2, + y: ICON_SIZE / 2, + }, +}; + +const body: dia.MarkupNodeJSON = { + selector: "body", + tagName: "path", +}; + +const renderer = new marked.Renderer(); +renderer.link = function (href, title, text) { + return `
${text}`; +}; +renderer.image = function (href, title, text) { + // SVG don't support HTML img inside foreignObject + return `${text} (attached img)`; +}; + +const foreignObject = (stickyNote: StickyNote): MarkupNodeJSON => { + let parsed; + try { + parsed = marked.parse(stickyNote.content, { renderer }); + } catch (error) { + console.error("Failed to parse markdown:", error); + parsed = "Error: Could not parse content. See error logs in console"; + } + const singleMarkupNode = util.svg/* xml */ ` + +
+ +
${parsed}
+
+
+ `[0]; + return singleMarkupNode as MarkupNodeJSON; +}; + +export const stickyNotePath = "M 0 0 L 19 0 L 19 19 L 0 19 L 0 0"; + +const defaults = (theme: Theme) => + util.defaultsDeep( + { + size: { + width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH, + height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT, + }, + attrs: { + body: { + refD: stickyNotePath, + strokeWidth: 2, + fill: "#eae672", + filter: { + name: "dropShadow", + args: { + dx: 1, + dy: 1, + blur: 5, + opacity: 0.4, + }, + }, + }, + foreignObject: { + width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH, + height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT - ICON_SIZE - CONTENT_PADDING * 4, + y: CONTENT_PADDING * 4 + ICON_SIZE, + fill: getBorderColor(theme), + }, + border: { + refD: stickyNotePath, + stroke: getBorderColor(theme), + }, + }, + }, + shapes.devs.Model.prototype.defaults, + ); + +const protoProps = (theme: Theme, stickyNote: StickyNote) => { + return { + markup: [body, border, foreignObject(stickyNote), icon], + }; +}; + +export const StickyNoteShape = (theme: Theme, stickyNote: StickyNote) => + StickyNoteElement(defaults(theme), protoProps(theme, stickyNote)) as typeof shapes.devs.Model; diff --git a/designer/client/src/components/graph/EspNode/stickyNoteElements.ts b/designer/client/src/components/graph/EspNode/stickyNoteElements.ts new file mode 100644 index 00000000000..13649530032 --- /dev/null +++ b/designer/client/src/components/graph/EspNode/stickyNoteElements.ts @@ -0,0 +1,137 @@ +import { ProcessDefinitionData } from "../../../types"; +import { Theme } from "@mui/material"; +import { StickyNote } from "../../../common/StickyNote"; +import { dia, elementTools, shapes } from "jointjs"; +import { stickyNoteIcon } from "../../toolbars/creator/ComponentIcon"; +import { createStickyNoteId } from "../../../types/stickyNote"; +import { getStickyNoteBackgroundColor } from "../../../containers/theme/helpers"; +import { CONTENT_PADDING, ICON_SIZE, MARKDOWN_EDITOR_NAME, STICKY_NOTE_CONSTRAINTS, StickyNoteShape } from "./stickyNote"; +import { Events } from "../types"; + +export type ModelWithTool = { + model: shapes.devs.Model; + tools: dia.ToolsView; +}; + +export function makeStickyNoteElement( + processDefinitionData: ProcessDefinitionData, + theme: Theme, +): (stickyNote: StickyNote) => ModelWithTool { + return (stickyNote: StickyNote) => { + const attributes: shapes.devs.ModelAttributes = { + id: createStickyNoteId(stickyNote.noteId), + noteId: stickyNote.noteId, + attrs: { + size: { + width: stickyNote.dimensions.width, + height: stickyNote.dimensions.height, + }, + body: { + fill: getStickyNoteBackgroundColor(theme, stickyNote.color).main, + opacity: 1, + }, + foreignObject: { + width: stickyNote.dimensions.width, + height: stickyNote.dimensions.height - ICON_SIZE - CONTENT_PADDING * 4, + color: theme.palette.getContrastText(getStickyNoteBackgroundColor(theme, stickyNote.color).main), + }, + icon: { + xlinkHref: stickyNoteIcon(), + opacity: 1, + color: theme.palette.getContrastText(getStickyNoteBackgroundColor(theme, stickyNote.color).main), + }, + border: { + stroke: getStickyNoteBackgroundColor(theme, stickyNote.color).dark, + strokeWidth: 1, + }, + }, + rankDir: "R", + }; + + const ThemedStickyNoteShape = StickyNoteShape(theme, stickyNote); + const stickyNoteModel = new ThemedStickyNoteShape(attributes); + + const removeButtonTool = new elementTools.Remove({ + focusOpacity: 0.5, + rotate: true, + x: stickyNote.dimensions.width - 20, + y: "0%", + offset: { x: 0, y: 20 }, + className: "sticky-note-remove-tool", + action: function () { + stickyNoteModel.trigger(Events.CELL_DELETED, stickyNoteModel); + }, + }); + + const ResizeTool = elementTools.Control.extend({ + children: [ + { + tagName: "path", + selector: "handle", + attributes: { + d: "M 4 0 L 4 4 L 0 4 L 0 5 L 5 5 L 5 0 L 4 0", + stroke: getStickyNoteBackgroundColor(theme, stickyNote.color).light, + cursor: "se-resize", + }, + }, + { + tagName: "rect", + selector: "extras", + attributes: { + "pointer-events": "none", + fill: "none", + stroke: getStickyNoteBackgroundColor(theme, stickyNote.color).light, + "stroke-dasharray": "2,3", + rx: 6, + ry: 6, + }, + }, + ], + documentEvents: { + mousemove: "onPointerMove", + touchmove: "onPointerMove", + mouseup: "onPointerUpCustom", + touchend: "onPointerUpCustom", + touchcancel: "onPointerUp", + }, + getPosition: function (view) { + const model = view.model; + const { width, height } = model.size(); + return { x: width, y: height }; + }, + setPosition: function (view, coordinates) { + const model = view.model; + model.resize( + Math.max( + Math.min(STICKY_NOTE_CONSTRAINTS.MAX_WIDTH, Math.round(coordinates.x - 10)), + STICKY_NOTE_CONSTRAINTS.MIN_WIDTH, + ), + Math.max( + Math.min(STICKY_NOTE_CONSTRAINTS.MAX_HEIGHT, Math.round(coordinates.y - 10)), + STICKY_NOTE_CONSTRAINTS.MIN_HEIGHT, + ), + ); + }, + onPointerUpCustom: function (evt: dia.Event) { + this.onPointerUp(evt); + stickyNoteModel.trigger(Events.CELL_RESIZED, stickyNoteModel); + }, + }); + + const tools: dia.ToolsView = new dia.ToolsView({ + tools: [ + new ResizeTool({ + selector: "body", + scale: 2, + }), + removeButtonTool, + ], + }); + stickyNoteModel.resize( + Math.max(stickyNote.dimensions.width, STICKY_NOTE_CONSTRAINTS.MIN_WIDTH), + Math.max(stickyNote.dimensions.height, STICKY_NOTE_CONSTRAINTS.MIN_HEIGHT), + ); + stickyNoteModel.attr(`${MARKDOWN_EDITOR_NAME}/props/value`, stickyNote.content); + return { model: stickyNoteModel, tools }; + }; +} diff --git a/designer/client/src/components/graph/Graph.tsx b/designer/client/src/components/graph/Graph.tsx index 5f861458287..f1f045dc0b7 100644 --- a/designer/client/src/components/graph/Graph.tsx +++ b/designer/client/src/components/graph/Graph.tsx @@ -18,7 +18,14 @@ import { Scenario } from "../Process/types"; import { createUniqueArrowMarker } from "./arrowMarker"; import { updateNodeCounts } from "./EspNode/element"; import { getDefaultLinkCreator } from "./EspNode/link"; -import { applyCellChanges, calcLayout, createPaper, isModelElement } from "./GraphPartialsInTS"; +import { + applyCellChanges, + calcLayout, + createPaper, + getStickyNoteCopyFromCell, + isModelElement, + isStickyNoteElement, +} from "./GraphPartialsInTS"; import { getCellsToLayout } from "./GraphPartialsInTS/calcLayout"; import { isEdgeConnected } from "./GraphPartialsInTS/EdgeUtils"; import { updateLayout } from "./GraphPartialsInTS/updateLayout"; @@ -39,6 +46,11 @@ import { Events, GraphProps } from "./types"; import { filterDragHovered, getLinkNodes, setLinksHovered } from "./utils/dragHelpers"; import * as GraphUtils from "./utils/graphUtils"; import { handleGraphEvent } from "./utils/graphUtils"; +import { StickyNote } from "../../common/StickyNote"; +import { StickyNoteElement, StickyNoteElementView } from "./StickyNoteElement"; +import { STICKY_NOTE_CONSTRAINTS } from "./EspNode/stickyNote"; +import { NotificationActions } from "../../http/HttpService"; +import i18next from "i18next"; function clamp(number: number, max: number) { return Math.round(Math.min(max, Math.max(-max, number))); @@ -55,6 +67,15 @@ type Props = GraphProps & { theme: Theme; translation: UseTranslationResponse; handleStatisticsEvent: (event: TrackEventParams) => void; + notifications: NotificationActions; +}; + +export const nuGraphNamespace = { + ...shapes, + stickyNote: { + StickyNoteElement, + StickyNoteElementView, + }, }; function handleActionOnLongPress( @@ -111,6 +132,7 @@ export class Graph extends React.Component { model: this.graph, el: this.getEspGraphRef(), validateConnection: this.twoWayValidateConnection, + cellViewNamespace: nuGraphNamespace, validateMagnet: this.validateMagnet, interactive: (cellView: dia.CellView) => { const { model } = cellView; @@ -212,6 +234,24 @@ export class Graph extends React.Component { this.handleInjectBetweenNodes(cell.model, linkBelowCell); batchGroupBy.end(group); } + if (isStickyNoteElement(cell.model)) { + this.processGraphPaper.hideTools(); + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t( + "notification.warn.cannotMoveOnUnsavedVersion", + "Save scenario before making any changes to sticky notes", + ), + ); + return; + } + cell.showTools(); + const updatedStickyNote = getStickyNoteCopyFromCell(this.props.stickyNotes, cell.model); + if (!updatedStickyNote) return; + const position = cell.model.get("position"); + updatedStickyNote.layoutData = { x: position.x, y: position.y }; + this.updateStickyNote(this.props.scenario.name, this.props.scenario.processVersionId, updatedStickyNote); + } }) .on(Events.LINK_CONNECT, (linkView: dia.LinkView, evt: dia.Event, targetView: dia.CellView, targetMagnet: SVGElement) => { if (this.props.isFragment === true) return; @@ -236,12 +276,16 @@ export class Graph extends React.Component { return linkBelowCell; } - drawGraph = (scenarioGraph: ScenarioGraph, layout: Layout, processDefinitionData: ProcessDefinitionData): void => { + drawGraph = ( + scenarioGraph: ScenarioGraph, + stickyNotes: StickyNote[], + layout: Layout, + processDefinitionData: ProcessDefinitionData, + ): void => { const { theme } = this.props; this.redrawing = true; - - applyCellChanges(this.processGraphPaper, scenarioGraph, processDefinitionData, theme); + applyCellChanges(this.processGraphPaper, scenarioGraph, stickyNotes, processDefinitionData, theme); if (isEmpty(layout)) { this.forceLayout(); @@ -304,7 +348,7 @@ export class Graph extends React.Component { constructor(props: Props) { super(props); - this.graph = new dia.Graph(); + this.graph = new dia.Graph({}, { cellNamespace: nuGraphNamespace }); this.bindNodeRemove(); this.bindNodesMoving(); } @@ -337,8 +381,22 @@ export class Graph extends React.Component { } }; + const showStickyNoteTools = (cellView: dia.CellView) => { + cellView.showTools(); + }; + + const hideToolsOnBlankClick = (evt: dia.Event) => { + evt.preventDefault(); + this.processGraphPaper.hideTools(); + }; + const selectNode = (cellView: dia.CellView, evt: dia.Event) => { if (this.props.isFragment === true) return; + this.processGraphPaper.hideTools(); + if (isStickyNoteElement(cellView.model)) { + if (!this.props.isPristine) return; + showStickyNoteTools(cellView); + } if (this.props.nodeSelectionEnabled) { const nodeDataId = cellView.model.attributes.nodeData?.id; if (!nodeDataId) { @@ -353,7 +411,10 @@ export class Graph extends React.Component { } }; - this.processGraphPaper.on(Events.CELL_POINTERDOWN, handleGraphEvent(handleActionOnLongPress(showNodeDetails, selectNode), null)); + this.processGraphPaper.on( + Events.CELL_POINTERDOWN, + handleGraphEvent(handleActionOnLongPress(showNodeDetails, selectNode), null, (view) => !isStickyNoteElement(view.model)), + ); this.processGraphPaper.on( Events.LINK_POINTERDOWN, handleGraphEvent( @@ -362,16 +423,21 @@ export class Graph extends React.Component { ), ); - this.processGraphPaper.on(Events.CELL_POINTERCLICK, handleGraphEvent(null, selectNode)); + this.processGraphPaper.on( + Events.CELL_POINTERCLICK, + handleGraphEvent(null, selectNode, (view) => !isStickyNoteElement(view.model)), + ); this.processGraphPaper.on(Events.CELL_POINTERDBLCLICK, handleGraphEvent(null, showNodeDetails)); + this.processGraphPaper.on(Events.BLANK_POINTERCLICK, hideToolsOnBlankClick); this.hooverHandling(); } componentDidMount(): void { this.processGraphPaper = this.createPaper(); - this.drawGraph(this.props.scenario.scenarioGraph, this.props.layout, this.props.processDefinitionData); + this.drawGraph(this.props.scenario.scenarioGraph, this.props.stickyNotes, this.props.layout, this.props.processDefinitionData); this.processGraphPaper.unfreeze(); + this.processGraphPaper.hideTools(); this._prepareContentForExport(); // event handlers binding below. order sometimes matters @@ -414,6 +480,62 @@ export class Graph extends React.Component { this.highlightHoveredLink(); }); + this.graph.on(Events.CELL_RESIZED, (cell: dia.Element) => { + if (isStickyNoteElement(cell)) { + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t("notification.warn.cannotResizeOnUnsavedVersion", "Save scenario before resizing sticky note"), + ); + return; + } + const updatedStickyNote = getStickyNoteCopyFromCell(this.props.stickyNotes, cell); + if (!updatedStickyNote) return; + const position = cell.get("position"); + const size = cell.get("size"); + // TODO move max width and height to some config? + const width = Math.max( + STICKY_NOTE_CONSTRAINTS.MIN_WIDTH, + Math.min(STICKY_NOTE_CONSTRAINTS.MAX_WIDTH, Math.round(size.width)), + ); + const height = Math.max( + STICKY_NOTE_CONSTRAINTS.MIN_HEIGHT, + Math.min(STICKY_NOTE_CONSTRAINTS.MAX_HEIGHT, Math.round(size.height)), + ); + updatedStickyNote.layoutData = { x: position.x, y: position.y }; + updatedStickyNote.dimensions = { width, height }; + this.updateStickyNote(this.props.scenario.name, this.props.scenario.processVersionId, updatedStickyNote); + } + }); + + this.graph.on(Events.CELL_CONTENT_UPDATED, (cell: dia.Element, content: string) => { + if (isStickyNoteElement(cell)) { + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t("notification.warn.cannotUpdateOnUnsavedVersion", "Save scenario before updating sticky note"), + ); + return; + } + const updatedStickyNote = getStickyNoteCopyFromCell(this.props.stickyNotes, cell); + if (!updatedStickyNote) return; + if (updatedStickyNote.content == content) return; + updatedStickyNote.content = content; + this.updateStickyNote(this.props.scenario.name, this.props.scenario.processVersionId, updatedStickyNote); + } + }); + + this.graph.on(Events.CELL_DELETED, (cell: dia.Element) => { + if (isStickyNoteElement(cell)) { + if (!this.props.isPristine) { + this.props.notifications.warn( + i18next.t("notification.warn.cannotDeleteOnUnsavedVersion", "Save scenario before deleting sticky note"), + ); + return; + } + const noteId = Number(cell.get("noteId")); + this.deleteStickyNote(this.props.scenario.name, noteId); + } + }); + //we want to inject node during 'Drag and Drop' from toolbox this.graph.on(Events.ADD, (cell: dia.Element) => { if (isModelElement(cell)) { @@ -436,15 +558,41 @@ export class Graph extends React.Component { } } + addStickyNote(scenarioName: string, scenarioVersionId: number, position: Position): void { + if (this.props.isFragment === true) return; + const canAddStickyNote = this.props.capabilities.editFrontend; + if (canAddStickyNote) { + const dimensions = { width: STICKY_NOTE_CONSTRAINTS.DEFAULT_WIDTH, height: STICKY_NOTE_CONSTRAINTS.DEFAULT_HEIGHT }; + this.props.stickyNoteAdded(scenarioName, scenarioVersionId, position, dimensions); + } + } + + updateStickyNote(scenarioName: string, scenarioVersionId: number, stickyNote: StickyNote): void { + if (this.props.isFragment === true) return; + const canUpdateStickyNote = this.props.capabilities.editFrontend; + if (canUpdateStickyNote) { + this.props.stickyNoteUpdated(scenarioName, scenarioVersionId, stickyNote); + } + } + + deleteStickyNote(scenarioName: string, stickyNoteId: number): void { + if (this.props.isFragment === true) return; + const canUpdateStickyNote = this.props.capabilities.editFrontend; + if (canUpdateStickyNote) { + this.props.stickyNoteDeleted(scenarioName, stickyNoteId); + } + } + // eslint-disable-next-line react/no-deprecated componentWillUpdate(nextProps: Props): void { const processChanged = !isEqual(this.props.scenario.scenarioGraph, nextProps.scenario.scenarioGraph) || !isEqual(this.props.scenario.validationResult, nextProps.scenario.validationResult) || + !isEqual(this.props.stickyNotes, nextProps.stickyNotes) || !isEqual(this.props.layout, nextProps.layout) || !isEqual(this.props.processDefinitionData, nextProps.processDefinitionData); if (processChanged) { - this.drawGraph(nextProps.scenario.scenarioGraph, nextProps.layout, nextProps.processDefinitionData); + this.drawGraph(nextProps.scenario.scenarioGraph, nextProps.stickyNotes, nextProps.layout, nextProps.processDefinitionData); } //when e.g. layout changed we have to remember to highlight nodes diff --git a/designer/client/src/components/graph/GraphPartialsInTS/applyCellChanges.ts b/designer/client/src/components/graph/GraphPartialsInTS/applyCellChanges.ts index d09c02db54d..ccb78ecb7b6 100644 --- a/designer/client/src/components/graph/GraphPartialsInTS/applyCellChanges.ts +++ b/designer/client/src/components/graph/GraphPartialsInTS/applyCellChanges.ts @@ -7,26 +7,32 @@ import NodeUtils from "../NodeUtils"; import { isEdgeConnected } from "./EdgeUtils"; import { updateChangedCells } from "./updateChangedCells"; import { Theme } from "@mui/material"; +import { StickyNote } from "../../../common/StickyNote"; +import { makeStickyNoteElement, ModelWithTool } from "../EspNode/stickyNoteElements"; export function applyCellChanges( paper: dia.Paper, scenarioGraph: ScenarioGraph, + stickyNotes: StickyNote[], processDefinitionData: ProcessDefinitionData, theme: Theme, ): void { const graph = paper.model; const nodeElements = NodeUtils.nodesFromScenarioGraph(scenarioGraph).map(makeElement(processDefinitionData, theme)); + const stickyNotesModelsWithTools: ModelWithTool[] = stickyNotes.map(makeStickyNoteElement(processDefinitionData, theme)); + const stickyNotesModels = stickyNotesModelsWithTools.map((a) => a.model); const edges = NodeUtils.edgesFromScenarioGraph(scenarioGraph); const indexed = flatMap(groupBy(edges, "from"), (edges) => edges.map((edge, i) => ({ ...edge, index: ++i }))); const edgeElements = indexed.filter(isEdgeConnected).map((value) => makeLink(value, paper, theme)); - const cells = [...nodeElements, ...edgeElements]; + const cells = [...nodeElements, ...edgeElements, ...stickyNotesModels]; const currentCells = graph.getCells(); const currentIds = currentCells.map((c) => c.id); const newCells = cells.filter((cell) => !currentIds.includes(cell.id)); + const newStickyNotesModelsWithTools = stickyNotesModelsWithTools.filter((s) => !currentIds.includes(s.model.id)); const deletedCells = currentCells.filter((oldCell) => !cells.find((cell) => cell.id === oldCell.id)); const changedCells = cells.filter((cell) => { const old = graph.getCell(cell.id); @@ -36,4 +42,17 @@ export function applyCellChanges( graph.removeCells(deletedCells); updateChangedCells(graph, changedCells); graph.addCells(newCells); + + newStickyNotesModelsWithTools.forEach((m) => { + try { + const view = m.model.findView(paper); + if (!view) { + console.warn(`View not found for stickyNote model: ${m.model.id}`); + return; + } + view.addTools(m.tools); + } catch (error) { + console.error(`Failed to add tools to stickyNote view:`, error); + } + }); } diff --git a/designer/client/src/components/graph/GraphPartialsInTS/cellUtils.ts b/designer/client/src/components/graph/GraphPartialsInTS/cellUtils.ts index 4723616cc14..896dcef5d82 100644 --- a/designer/client/src/components/graph/GraphPartialsInTS/cellUtils.ts +++ b/designer/client/src/components/graph/GraphPartialsInTS/cellUtils.ts @@ -1,4 +1,6 @@ import { dia, shapes } from "jointjs"; +import { StickyNote } from "../../../common/StickyNote"; +import { cloneDeep } from "lodash"; export const isLink = (c: dia.Cell): c is dia.Link => c.isLink(); export const isElement = (c: dia.Cell): c is dia.Element => c?.isElement(); @@ -7,6 +9,17 @@ export function isModelElement(el: dia.Cell): el is shapes.devs.Model { return el instanceof shapes.devs.Model; } +export function isStickyNoteElement(el: dia.Cell): el is shapes.devs.Model { + return isElement(el) && el.get("type") === `stickyNote.StickyNoteElement`; +} + +export function getStickyNoteCopyFromCell(stickyNotes: StickyNote[], el: dia.Cell): StickyNote | undefined { + const noteId = el.get("noteId"); + if (!isStickyNoteElement(el) || !noteId) return undefined; + const stickyNote = stickyNotes.find((note) => note.noteId == noteId); + return stickyNote ? cloneDeep(stickyNote) : undefined; +} + export function isConnected(el: dia.Element): boolean { return el.graph.getNeighbors(el).length > 0; } diff --git a/designer/client/src/components/graph/GraphWrapped.tsx b/designer/client/src/components/graph/GraphWrapped.tsx index d9b5351e844..4847d6123d7 100644 --- a/designer/client/src/components/graph/GraphWrapped.tsx +++ b/designer/client/src/components/graph/GraphWrapped.tsx @@ -1,7 +1,7 @@ import { useTheme } from "@mui/material"; import React, { forwardRef, useRef } from "react"; import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { useForkRef } from "rooks"; import { useEventTracking } from "../../containers/event-tracking"; import { getProcessCategory, getSelectionState, isPristine } from "../../reducers/selectors/graph"; @@ -12,10 +12,13 @@ import { Graph } from "./Graph"; import { GraphStyledWrapper } from "./graphStyledWrapper"; import { NodeDescriptionPopover } from "./NodeDescriptionPopover"; import { GraphProps } from "./types"; +import { bindActionCreators } from "redux"; +import * as NotificationActions from "../../actions/notificationActions"; // Graph wrapped to make partial (for now) refactor to TS and hooks export default forwardRef(function GraphWrapped(props, forwardedRef): JSX.Element { const { openNodeWindow } = useWindows(); + const dispatch = useDispatch(); const userSettings = useSelector(getUserSettings); const pristine = useSelector(isPristine); const processCategory = useSelector(getProcessCategory); @@ -25,7 +28,7 @@ export default forwardRef(function GraphWrapped(props, forwar const theme = useTheme(); const translation = useTranslation(); const { trackEvent } = useEventTracking(); - + const notifications = bindActionCreators(NotificationActions, dispatch); const graphRef = useRef(); const ref = useForkRef(graphRef, forwardedRef); @@ -45,6 +48,7 @@ export default forwardRef(function GraphWrapped(props, forwar theme={theme} translation={translation} handleStatisticsEvent={trackEvent} + notifications={notifications} /> diff --git a/designer/client/src/components/graph/NodeDescriptionPopover.tsx b/designer/client/src/components/graph/NodeDescriptionPopover.tsx index 3bf06feba19..ec6cc328045 100644 --- a/designer/client/src/components/graph/NodeDescriptionPopover.tsx +++ b/designer/client/src/components/graph/NodeDescriptionPopover.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { Graph } from "./Graph"; import { MarkdownStyled } from "./node-modal/MarkdownStyled"; import { Events } from "./types"; +import { isStickyNoteElement } from "./GraphPartialsInTS"; const useTimeout = >( callback: (...args: A) => void, @@ -119,6 +120,7 @@ export function NodeDescriptionPopover(props: NodeDescriptionPopoverProps) { const lastTarget = useRef(null); const enterTimer = useTimeout((view: dia.CellView, el: Element) => { + if (isStickyNoteElement(view.model)) return; //Dont use it for stickyNotes setData( el ? [t("graph.node.counts.title", "number of messages that passed downstream"), el] diff --git a/designer/client/src/components/graph/ProcessGraph.tsx b/designer/client/src/components/graph/ProcessGraph.tsx index 097c02daac2..771dab0f911 100644 --- a/designer/client/src/components/graph/ProcessGraph.tsx +++ b/designer/client/src/components/graph/ProcessGraph.tsx @@ -3,17 +3,29 @@ import { g } from "jointjs"; import { mapValues } from "lodash"; import { useDrop } from "react-dnd"; import { useDispatch, useSelector } from "react-redux"; -import { getScenario, getLayout, getProcessCounts } from "../../reducers/selectors/graph"; +import { getScenario, getLayout, getProcessCounts, getStickyNotes } from "../../reducers/selectors/graph"; import { setLinksHovered } from "./utils/dragHelpers"; import { Graph } from "./Graph"; import GraphWrapped from "./GraphWrapped"; import { RECT_HEIGHT, RECT_WIDTH } from "./EspNode/esp"; import NodeUtils from "./NodeUtils"; import { DndTypes } from "../toolbars/creator/Tool"; -import { injectNode, layoutChanged, nodeAdded, nodesConnected, nodesDisconnected, resetSelection, toggleSelection } from "../../actions/nk"; +import { + injectNode, + layoutChanged, + nodeAdded, + nodesConnected, + nodesDisconnected, + resetSelection, + stickyNoteAdded, + stickyNoteUpdated, + stickyNoteDeleted, + toggleSelection, +} from "../../actions/nk"; import { NodeType } from "../../types"; import { Capabilities } from "../../reducers/selectors/other"; import { bindActionCreators } from "redux"; +import { StickyNoteType } from "../../types/stickyNote"; export const ProcessGraph = forwardRef(function ProcessGraph( { capabilities }, @@ -21,6 +33,7 @@ export const ProcessGraph = forwardRef(fu ): JSX.Element { const scenario = useSelector(getScenario); const processCounts = useSelector(getProcessCounts); + const stickyNotes = useSelector(getStickyNotes); const layout = useSelector(getLayout); const graph = useRef(); @@ -33,8 +46,12 @@ export const ProcessGraph = forwardRef(fu const relOffset = graph.current.processGraphPaper.clientToLocalPoint(clientOffset); // to make node horizontally aligned const nodeInputRelOffset = relOffset.offset(RECT_WIDTH * -0.8, RECT_HEIGHT * -0.5); - graph.current.addNode(monitor.getItem(), mapValues(nodeInputRelOffset, Math.round)); - setLinksHovered(graph.current.graph); + if (item?.type === StickyNoteType) { + graph.current.addStickyNote(scenario.name, scenario.processVersionId, mapValues(nodeInputRelOffset, Math.round)); + } else { + graph.current.addNode(monitor.getItem(), mapValues(nodeInputRelOffset, Math.round)); + setLinksHovered(graph.current.graph); + } }, hover: (item: NodeType, monitor) => { const node = item; @@ -67,6 +84,9 @@ export const ProcessGraph = forwardRef(fu layoutChanged, injectNode, nodeAdded, + stickyNoteAdded, + stickyNoteUpdated, + stickyNoteDeleted, resetSelection, toggleSelection, }, @@ -81,6 +101,7 @@ export const ProcessGraph = forwardRef(fu connectDropTarget={connectDropTarget} isDraggingOver={isDraggingOver} capabilities={capabilities} + stickyNotes={stickyNotes} divId={"nk-graph-main"} nodeSelectionEnabled scenario={scenario} diff --git a/designer/client/src/components/graph/StickyNoteElement.ts b/designer/client/src/components/graph/StickyNoteElement.ts new file mode 100644 index 00000000000..9d59f7035de --- /dev/null +++ b/designer/client/src/components/graph/StickyNoteElement.ts @@ -0,0 +1,52 @@ +import { dia } from "jointjs"; +import { Events } from "./types"; +import { MARKDOWN_EDITOR_NAME } from "./EspNode/stickyNote"; +import MarkupNodeJSON = dia.MarkupNodeJSON; + +export interface StickyNoteDefaults { + position?: { x: number; y: number }; + size?: { width: number; height: number }; + attrs?: Record; +} + +export interface StickyNoteProtoProps { + markup: (dia.MarkupNodeJSON | MarkupNodeJSON)[]; + [key: string]: unknown; +} + +export const StickyNoteElement = (defaults?: StickyNoteDefaults, protoProps?: StickyNoteProtoProps) => + dia.Element.define("stickyNote.StickyNoteElement", defaults, protoProps); + +export const StickyNoteElementView = dia.ElementView.extend({ + events: { + "click .sticky-note-markdown-editor": "stopPropagation", + "keydown textarea": "selectAll", + "focusout .sticky-note-markdown-editor": "onChange", + "dblclick .sticky-note-content": "showEditor", + }, + + stopPropagation: function (evt) { + evt.stopPropagation(); + }, + + showEditor: function (evt) { + evt.stopPropagation(); + this.model.attr(`${MARKDOWN_EDITOR_NAME}/props/disabled`, false); + evt.currentTarget.querySelector("textarea").focus({ preventScroll: true }); + }, + + selectAll: function (evt) { + if (evt.code === "KeyA") { + if (evt.ctrlKey || evt.metaKey) { + evt.preventDefault(); + evt.target.select(); + } + } + }, + + onChange: function (evt) { + this.model.trigger(Events.CELL_CONTENT_UPDATED, this.model, evt.target.value); + this.model.attr(`${MARKDOWN_EDITOR_NAME}/props/value`, evt.target.value); + this.model.attr(`${MARKDOWN_EDITOR_NAME}/props/disabled`, true); + }, +}); diff --git a/designer/client/src/components/graph/fragmentGraph.tsx b/designer/client/src/components/graph/fragmentGraph.tsx index 645a696d21d..4694935ee44 100644 --- a/designer/client/src/components/graph/fragmentGraph.tsx +++ b/designer/client/src/components/graph/fragmentGraph.tsx @@ -4,19 +4,21 @@ import React, { forwardRef } from "react"; import { Graph } from "./Graph"; import { GraphProps } from "./types"; -export const FragmentGraphPreview = forwardRef>( - function FragmentGraphPreview({ processCounts, scenario, nodeIdPrefixForFragmentTests }, ref) { - return ( - - ); - }, -); +export const FragmentGraphPreview = forwardRef< + Graph, + Pick +>(function FragmentGraphPreview({ processCounts, scenario, stickyNotes, nodeIdPrefixForFragmentTests }, ref) { + return ( + + ); +}); diff --git a/designer/client/src/components/graph/graphStyledWrapper.ts b/designer/client/src/components/graph/graphStyledWrapper.ts index c0ab9fb6fee..3a6afcffa39 100644 --- a/designer/client/src/components/graph/graphStyledWrapper.ts +++ b/designer/client/src/components/graph/graphStyledWrapper.ts @@ -1,5 +1,5 @@ import { CSSProperties } from "react"; -import { css, styled, Theme } from "@mui/material"; +import { alpha, css, styled, Theme } from "@mui/material"; import { blend } from "@mui/system"; import { blendLighten } from "../../containers/theme/helpers"; @@ -181,6 +181,41 @@ export const GraphStyledWrapper = styled("div")(({ theme }) => transition: "all 0.25s ease-in-out", }, }, + ".sticky-note-markdown": { + width: "100%", + height: "100%", + paddingLeft: "10px", + paddingRight: "10px", + }, + ".sticky-note-markdown-editor": { + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1), + backgroundColor: alpha(theme.palette.common.white, 0.3), + color: theme.palette.common.black, + fontFamily: theme.typography.fontFamily, + fontSize: theme.typography.body1.fontSize, + resize: "none", + width: "100%", + height: "100%", + borderStyle: "none", + borderColor: "Transparent", + whiteSpace: "pre-line", + overflow: "hidden", + }, + ".sticky-note-markdown-editor:focus": { + outline: "none", + boxShadow: `0 0 0 2px ${theme.palette.primary.main}`, + }, + ".sticky-note-content": { + width: "100%", + height: "100%", + }, + ".joint-sticky-note-remove-tool > circle": { + fill: "#ca344c", + }, + ".sticky-note-markdown-editor:disabled": { + display: "none", + }, }, ]), ); diff --git a/designer/client/src/components/graph/node-modal/node/FragmentContent.tsx b/designer/client/src/components/graph/node-modal/node/FragmentContent.tsx index b96d03547ac..c3fe66b3c60 100644 --- a/designer/client/src/components/graph/node-modal/node/FragmentContent.tsx +++ b/designer/client/src/components/graph/node-modal/node/FragmentContent.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from "react"; import { useSelector } from "react-redux"; import HttpService from "../../../../http/HttpService"; -import { getProcessCounts } from "../../../../reducers/selectors/graph"; +import { getProcessCounts, getStickyNotes } from "../../../../reducers/selectors/graph"; import { FragmentNodeType } from "../../../../types"; import { ErrorBoundary, DialogErrorFallbackComponent } from "../../../common/error-boundary"; import NodeUtils from "../../NodeUtils"; @@ -14,6 +14,7 @@ import { Scenario } from "../../../Process/types"; export function FragmentContent({ nodeToDisplay }: { nodeToDisplay: FragmentNodeType }): JSX.Element { const processCounts = useSelector(getProcessCounts); + const stickyNotes = useSelector(getStickyNotes); const processDefinitionData = useSelector(getProcessDefinitionData); const [fragmentContent, setFragmentContent] = useState(null); @@ -40,6 +41,7 @@ export function FragmentContent({ nodeToDisplay }: { nodeToDisplay: FragmentNode )} diff --git a/designer/client/src/components/graph/types.ts b/designer/client/src/components/graph/types.ts index ed27ac699fd..d5b6279b82c 100644 --- a/designer/client/src/components/graph/types.ts +++ b/designer/client/src/components/graph/types.ts @@ -7,10 +7,14 @@ import { nodesConnected, nodesDisconnected, resetSelection, + stickyNoteAdded, + stickyNoteDeleted, + stickyNoteUpdated, toggleSelection, } from "../../actions/nk"; import { Capabilities } from "../../reducers/selectors/other"; import { Scenario } from "../Process/types"; +import { StickyNote } from "../../common/StickyNote"; type ScenarioGraphProps = { nodesConnected: typeof nodesConnected; @@ -18,9 +22,13 @@ type ScenarioGraphProps = { layoutChanged: typeof layoutChanged; injectNode: typeof injectNode; nodeAdded: typeof nodeAdded; + stickyNoteAdded: typeof stickyNoteAdded; + stickyNoteUpdated: typeof stickyNoteUpdated; + stickyNoteDeleted: typeof stickyNoteDeleted; resetSelection: typeof resetSelection; toggleSelection: typeof toggleSelection; + stickyNotes: StickyNote[]; scenario: Scenario; divId: string; nodeIdPrefixForFragmentTests?: string; @@ -38,6 +46,7 @@ type ScenarioGraphProps = { type FragmentGraphProps = { scenario: Scenario; + stickyNotes: StickyNote[]; divId: string; nodeIdPrefixForFragmentTests: string; processCounts: ProcessCounts; @@ -65,6 +74,9 @@ export enum Events { CELL_MOUSEENTER = "cell:mouseenter", CELL_MOUSELEAVE = "cell:mouseleave", CELL_MOVED = "cellCustom:moved", + CELL_RESIZED = "cellCustom:resized", + CELL_CONTENT_UPDATED = "cellCustom:contentUpdated", + CELL_DELETED = "cellCustom:deleted", BLANK_POINTERCLICK = "blank:pointerclick", BLANK_POINTERDOWN = "blank:pointerdown", BLANK_POINTERUP = "blank:pointerup", diff --git a/designer/client/src/components/graph/utils/graphUtils.ts b/designer/client/src/components/graph/utils/graphUtils.ts index 3da1cda198c..25c445b0431 100644 --- a/designer/client/src/components/graph/utils/graphUtils.ts +++ b/designer/client/src/components/graph/utils/graphUtils.ts @@ -127,8 +127,9 @@ export const handleGraphEvent = ( touchEvent: ((view: T, event: dia.Event) => void) | null, mouseEvent: ((view: T, event: dia.Event) => void) | null, + preventDefault: (view: T) => boolean = () => true, ) => (view: T, event: dia.Event) => { - event.preventDefault(); + if (preventDefault(view)) event.preventDefault(); return isTouchEvent(event) ? touchEvent?.(view, event) : mouseEvent?.(view, event); }; diff --git a/designer/client/src/components/toolbars/creator/ComponentIcon.tsx b/designer/client/src/components/toolbars/creator/ComponentIcon.tsx index 1b8341b5931..bd3c350151a 100644 --- a/designer/client/src/components/toolbars/creator/ComponentIcon.tsx +++ b/designer/client/src/components/toolbars/creator/ComponentIcon.tsx @@ -7,6 +7,7 @@ import { NodeType, ProcessDefinitionData } from "../../../types"; import { InlineSvg } from "../../SvgDiv"; import { Icon } from "./Icon"; import { createRoot } from "react-dom/client"; +import { StickyNoteType } from "../../../types/stickyNote"; let preloadedIndex = 0; const preloadBeImage = memoize((src: string): string | null => { @@ -22,6 +23,11 @@ const preloadBeImage = memoize((src: string): string | null => { return `#${id}`; }); +export const stickyNoteIconSrc = `/assets/components/${StickyNoteType}.svg`; +export function stickyNoteIcon(): string | null { + return preloadBeImage(stickyNoteIconSrc); +} + export function getComponentIconSrc(node: NodeType, { components }: ProcessDefinitionData): string | null { // missing type means that node is the fake properties component // TODO we should split properties node logic and normal components logic diff --git a/designer/client/src/components/toolbars/creator/StickyNoteComponent.tsx b/designer/client/src/components/toolbars/creator/StickyNoteComponent.tsx new file mode 100644 index 00000000000..23e2fc5760c --- /dev/null +++ b/designer/client/src/components/toolbars/creator/StickyNoteComponent.tsx @@ -0,0 +1,19 @@ +import { StickyNoteType } from "../../../types/stickyNote"; +import { ComponentGroup } from "../../../types"; + +const noteModel = { id: "StickyNoteToAdd", type: StickyNoteType, isDisabled: false }; +export const stickyNoteComponentGroup = (pristine: boolean) => { + return [ + { + components: [ + { + node: noteModel, + label: "sticky note", + componentId: StickyNoteType + "_" + pristine, + disabled: () => !pristine, + }, + ], + name: "sticky notes", + } as ComponentGroup, + ]; +}; diff --git a/designer/client/src/components/toolbars/creator/ToolBox.tsx b/designer/client/src/components/toolbars/creator/ToolBox.tsx index def6637eb36..f7217a53d02 100644 --- a/designer/client/src/components/toolbars/creator/ToolBox.tsx +++ b/designer/client/src/components/toolbars/creator/ToolBox.tsx @@ -1,15 +1,18 @@ -import { lighten, styled } from "@mui/material"; -import { getLuminance } from "@mui/system/colorManipulator"; import React, { useMemo } from "react"; -import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import "react-treeview/react-treeview.css"; import { filterComponentsByLabel } from "../../../common/ProcessDefinitionUtils"; -import { blendDarken, blendLighten } from "../../../containers/theme/helpers"; -import { getProcessDefinitionData } from "../../../reducers/selectors/settings"; +import { getProcessDefinitionData, getStickyNotesSettings } from "../../../reducers/selectors/settings"; import { ComponentGroup } from "../../../types"; import Tool from "./Tool"; import { ToolboxComponentGroup } from "./ToolboxComponentGroup"; +import { useTranslation } from "react-i18next"; +import { lighten, styled } from "@mui/material"; +import { blendDarken, blendLighten } from "../../../containers/theme/helpers"; +import { getLuminance } from "@mui/system/colorManipulator"; +import { isPristine } from "../../../reducers/selectors/graph"; +import { concat } from "lodash"; +import { stickyNoteComponentGroup } from "./StickyNoteComponent"; const StyledToolbox = styled("div")(({ theme }) => ({ fontSize: "14px", @@ -112,16 +115,17 @@ type ToolBoxProps = { export default function ToolBox(props: ToolBoxProps): JSX.Element { const processDefinitionData = useSelector(getProcessDefinitionData); + const stickyNotesSettings = useSelector(getStickyNotesSettings); + const pristine = useSelector(isPristine); const { t } = useTranslation(); const componentGroups: ComponentGroup[] = useMemo(() => processDefinitionData.componentGroups, [processDefinitionData]); - const filters = useMemo(() => props.filter?.toLowerCase().split(/\s/).filter(Boolean), [props.filter]); - - const groups = useMemo( - () => componentGroups.map(filterComponentsByLabel(filters)).filter((g) => g.components.length > 0), - [componentGroups, filters], - ); + const stickyNoteToolGroup = useMemo(() => stickyNoteComponentGroup(pristine), [pristine, props, t]); + const groups = useMemo(() => { + const allComponentGroups = stickyNotesSettings.enabled ? concat(componentGroups, stickyNoteToolGroup) : componentGroups; + return allComponentGroups.map(filterComponentsByLabel(filters)).filter((g) => g.components.length > 0); + }, [componentGroups, filters, stickyNoteToolGroup, stickyNotesSettings]); return ( diff --git a/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx b/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx index 4397aee00e1..966e138f255 100644 --- a/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx +++ b/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx @@ -73,7 +73,13 @@ export function ToolboxComponentGroup(props: Props): JSX.Element { const elements = useMemo( () => componentGroup.components.map((component) => ( - + )), [highlights, componentGroup.components], ); diff --git a/designer/client/src/containers/theme/helpers.ts b/designer/client/src/containers/theme/helpers.ts index 8f6c16fe3d3..9f3cab9a602 100644 --- a/designer/client/src/containers/theme/helpers.ts +++ b/designer/client/src/containers/theme/helpers.ts @@ -1,6 +1,7 @@ import { rgbToHex, Theme } from "@mui/material"; import { blend } from "@mui/system"; import { getLuminance } from "@mui/system/colorManipulator"; +import { STICKY_NOTE_DEFAULT_COLOR } from "../../components/graph/EspNode/stickyNote"; export const blendDarken = (color: string, opacity: number) => rgbToHex(blend(color, "#000000", opacity)); export const blendLighten = (color: string, opacity: number) => rgbToHex(blend(color, "#ffffff", opacity)); @@ -14,3 +15,12 @@ export function getNodeBorderColor(theme: Theme) { ? blendDarken(theme.palette.background.paper, 0.4) : blendLighten(theme.palette.background.paper, 0.6); } + +export function getStickyNoteBackgroundColor(theme: Theme, color: string) { + const isValidColor = CSS.supports("color", color); + return theme.palette.augmentColor({ + color: { + main: isValidColor ? color : STICKY_NOTE_DEFAULT_COLOR, + }, + }); +} diff --git a/designer/client/src/containers/theme/nuTheme.tsx b/designer/client/src/containers/theme/nuTheme.tsx index fc837aee834..3ca43d5c420 100644 --- a/designer/client/src/containers/theme/nuTheme.tsx +++ b/designer/client/src/containers/theme/nuTheme.tsx @@ -165,6 +165,7 @@ export const nuTheme = (mode: PaletteMode, setMode: Dispatch ({ backgroundColor: theme.palette.warning.main, + color: blendDarken(theme.palette.text.primary, 0.9), }), standardInfo: ({ theme }) => ({ backgroundColor: theme.palette.primary.main, diff --git a/designer/client/src/http/HttpService.ts b/designer/client/src/http/HttpService.ts index ae65597f9d5..9fcf4cdadc6 100644 --- a/designer/client/src/http/HttpService.ts +++ b/designer/client/src/http/HttpService.ts @@ -3,7 +3,7 @@ import { AxiosError, AxiosResponse } from "axios"; import FileSaver from "file-saver"; import i18next from "i18next"; import { Moment } from "moment"; -import { ProcessingType, SettingsData, ValidationData, ValidationRequest } from "../actions/nk"; +import { Position, ProcessingType, SettingsData, ValidationData, ValidationRequest } from "../actions/nk"; import { GenericValidationRequest, TestAdhocValidationRequest } from "../actions/nk/adhocTesting"; import api from "../api"; import { UserData } from "../common/models/User"; @@ -33,10 +33,12 @@ import { EventTrackingSelectorType, EventTrackingType } from "../containers/even import { BackendNotification } from "../containers/Notifications"; import { ProcessCounts } from "../reducers/graph"; import { AuthenticationSettings } from "../reducers/settings"; -import { Expression, NodeType, ProcessAdditionalFields, ProcessDefinitionData, ScenarioGraph, VariableTypes } from "../types"; +import { Expression, LayoutData, NodeType, ProcessAdditionalFields, ProcessDefinitionData, ScenarioGraph, VariableTypes } from "../types"; import { Instant, WithId } from "../types/common"; import { fixAggregateParameters, fixBranchParametersTemplate } from "./parametersUtils"; import { handleAxiosError } from "../devHelpers"; +import { Dimensions, StickyNote } from "../common/StickyNote"; +import { STICKY_NOTE_DEFAULT_COLOR } from "../components/graph/EspNode/stickyNote"; type HealthCheckProcessDeploymentType = { status: string; @@ -124,9 +126,10 @@ export type ComponentUsageType = { lastAction: ProcessActionType; }; -type NotificationActions = { +export type NotificationActions = { success(message: string): void; error(message: string, error: string, showErrorText: boolean): void; + warn(message: string): void; }; export interface TestProcessResponse { @@ -682,6 +685,57 @@ class HttpService { return promise; } + addStickyNote(scenarioName: string, scenarioVersionId: number, position: Position, dimensions: Dimensions) { + const promise = api.post(`/processes/${encodeURIComponent(scenarioName)}/stickyNotes`, { + scenarioVersionId, + content: "", + layoutData: position, + color: STICKY_NOTE_DEFAULT_COLOR, //TODO add config for default sticky note color? For now this is default. + dimensions: dimensions, + }); + promise.catch((error) => { + const errorMsg: string = error?.response?.data; + this.#addError("Failed to add sticky note" + (errorMsg ? ": " + errorMsg : ""), error, true); + }); + return promise; + } + + deleteStickyNote(scenarioName: string, stickyNoteId: number) { + const promise = api.delete(`/processes/${encodeURIComponent(scenarioName)}/stickyNotes/${stickyNoteId}`); + promise.catch((error) => + this.#addError( + i18next.t("notification.error.failedToDeleteStickyNote", `Failed to delete sticky note with id: ${stickyNoteId}`), + error, + true, + ), + ); + return promise; + } + + updateStickyNote(scenarioName: string, scenarioVersionId: number, stickyNote: StickyNote) { + const promise = api.put(`/processes/${encodeURIComponent(scenarioName)}/stickyNotes`, { + noteId: stickyNote.noteId, + scenarioVersionId, + content: stickyNote.content, + layoutData: stickyNote.layoutData, + color: stickyNote.color, + dimensions: stickyNote.dimensions, + }); + promise.catch((error) => { + const errorMsg = error?.response?.data; + this.#addError("Failed to update sticky note" + errorMsg ? ": " + errorMsg : "", error, true); + }); + return promise; + } + + getStickyNotes(scenarioName: string, scenarioVersionId: number): Promise> { + const promise = api.get(`/processes/${encodeURIComponent(scenarioName)}/stickyNotes?scenarioVersionId=${scenarioVersionId}`); + promise.catch((error) => + this.#addError(i18next.t("notification.error.failedToGetStickyNotes", "Failed to get sticky notes"), error, true), + ); + return promise; + } + fetchProcessCounts(processName: string, dateFrom: Moment, dateTo: Moment): Promise> { //we use offset date time instead of timestamp to pass info about user time zone to BE const format = (date: Moment) => date?.format("YYYY-MM-DDTHH:mm:ssZ"); diff --git a/designer/client/src/reducers/graph/reducer.ts b/designer/client/src/reducers/graph/reducer.ts index b5bbaacafd9..333cfd8d70f 100644 --- a/designer/client/src/reducers/graph/reducer.ts +++ b/designer/client/src/reducers/graph/reducer.ts @@ -16,9 +16,12 @@ import { selectionState } from "./selectionState"; import { GraphState } from "./types"; import { addNodesWithLayout, + addStickyNotesWithLayout, adjustBranchParametersAfterDisconnect, createEdge, enrichNodeWithProcessDependentData, + prepareNewStickyNotesWithLayout, + removeStickyNoteFromLayout, updateAfterNodeDelete, updateLayoutAfterNodeIdChange, } from "./utils"; @@ -238,6 +241,18 @@ const graphReducer: Reducer = (state = emptyGraphState, action) => { layout: action.layout, }); } + case "STICKY_NOTES_UPDATED": { + const { stickyNotes, layout } = prepareNewStickyNotesWithLayout(state, action.stickyNotes); + return { + ...addStickyNotesWithLayout(state, { stickyNotes, layout }), + }; + } + case "STICKY_NOTE_DELETED": { + const { stickyNotes, layout } = removeStickyNoteFromLayout(state, action.stickyNoteId); + return { + ...addStickyNotesWithLayout(state, { stickyNotes, layout }), + }; + } case "NODES_WITH_EDGES_ADDED": { const { nodes, layout, idMapping, processDefinitionData, edges } = action; diff --git a/designer/client/src/reducers/graph/types.ts b/designer/client/src/reducers/graph/types.ts index 4f4002057f5..f22bd1e4623 100644 --- a/designer/client/src/reducers/graph/types.ts +++ b/designer/client/src/reducers/graph/types.ts @@ -1,6 +1,7 @@ import { Layout, RefreshData } from "../../actions/nk"; import { Scenario } from "../../components/Process/types"; import { TestCapabilities, TestFormParameters, TestResults } from "../../common/TestResultUtils"; +import { StickyNote } from "../../common/StickyNote"; export interface NodeCounts { errors?: number; @@ -13,6 +14,7 @@ export type ProcessCounts = Record; export type GraphState = { scenarioLoading: boolean; scenario?: Scenario; + stickyNotes?: StickyNote[]; selectionState?: string[]; layout: Layout; testCapabilities?: TestCapabilities; diff --git a/designer/client/src/reducers/graph/utils.ts b/designer/client/src/reducers/graph/utils.ts index f19cbc50075..dbc2b175692 100644 --- a/designer/client/src/reducers/graph/utils.ts +++ b/designer/client/src/reducers/graph/utils.ts @@ -5,6 +5,8 @@ import { ExpressionLang } from "../../components/graph/node-modal/editors/expres import NodeUtils from "../../components/graph/NodeUtils"; import { BranchParams, Edge, EdgeType, NodeId, NodeType, ProcessDefinitionData } from "../../types"; import { GraphState } from "./types"; +import { StickyNote } from "../../common/StickyNote"; +import { createStickyNoteId } from "../../types/stickyNote"; export function updateLayoutAfterNodeIdChange(layout: Layout, oldId: NodeId, newId: NodeId): Layout { return map(layout, (n) => (oldId === n.id ? { ...n, id: newId } : n)); @@ -77,6 +79,33 @@ export function prepareNewNodesWithLayout( }; } +export function removeStickyNoteFromLayout(state: GraphState, stickyNoteId: number): { layout: NodePosition[]; stickyNotes: StickyNote[] } { + const { layout } = state; + const stickyNoteLayoutId = createStickyNoteId(stickyNoteId); + const updatedStickyNotes = state.stickyNotes.filter((n) => n.noteId !== stickyNoteId); + const updatedLayout = updatedStickyNotes.map((stickyNote) => { + return { id: stickyNote.id, position: stickyNote.layoutData }; + }); + return { + stickyNotes: [...updatedStickyNotes], + layout: [...layout.filter((l) => l.id !== stickyNoteLayoutId), ...updatedLayout], + }; +} + +export function prepareNewStickyNotesWithLayout( + state: GraphState, + stickyNotes: StickyNote[], +): { layout: NodePosition[]; stickyNotes: StickyNote[] } { + const { layout } = state; + const updatedLayout = stickyNotes.map((stickyNote) => { + return { id: createStickyNoteId(stickyNote.noteId), position: stickyNote.layoutData }; + }); + return { + stickyNotes: [...stickyNotes], + layout: [...layout, ...updatedLayout], + }; +} + export function addNodesWithLayout( state: GraphState, changes: { @@ -103,6 +132,17 @@ export function addNodesWithLayout( }; } +export function addStickyNotesWithLayout( + state: GraphState, + { stickyNotes, layout }: ReturnType, +): GraphState { + return { + ...state, + stickyNotes: stickyNotes, + layout, + }; +} + export function createEdge( fromNode: NodeType, toNode: NodeType, diff --git a/designer/client/src/reducers/selectors/graph.ts b/designer/client/src/reducers/selectors/graph.ts index 3fbf656d70a..473f3251439 100644 --- a/designer/client/src/reducers/selectors/graph.ts +++ b/designer/client/src/reducers/selectors/graph.ts @@ -8,6 +8,8 @@ import { ProcessCounts } from "../graph"; import { RootState } from "../index"; import { getProcessState } from "./scenarioState"; import { TestFormParameters } from "../../common/TestResultUtils"; +import { StickyNote } from "../../common/StickyNote"; +import { getStickyNotesSettings } from "./settings"; export const getGraph = (state: RootState) => state.graphReducer.history.present; @@ -75,6 +77,9 @@ export const getTestParameters = createSelector(getGraph, (g) => g.testFormParam export const getTestResults = createSelector(getGraph, (g) => g.testResults); export const getProcessCountsRefresh = createSelector(getGraph, (g) => g.processCountsRefresh || null); export const getProcessCounts = createSelector(getGraph, (g): ProcessCounts => g.processCounts || ({} as ProcessCounts)); +export const getStickyNotes = createSelector([getGraph, getStickyNotesSettings], (g, settings) => + settings.enabled ? g.stickyNotes || ([] as StickyNote[]) : ([] as StickyNote[]), +); export const getShowRunProcessDetails = createSelector( [getTestResults, getProcessCounts], (testResults, processCounts) => testResults || processCounts, diff --git a/designer/client/src/reducers/selectors/settings.ts b/designer/client/src/reducers/selectors/settings.ts index e222a76b8ae..382290a60ea 100644 --- a/designer/client/src/reducers/selectors/settings.ts +++ b/designer/client/src/reducers/selectors/settings.ts @@ -14,6 +14,7 @@ export const getEnvironmentAlert = createSelector(getFeatureSettings, (s) => s?. export const getTabs = createSelector(getFeatureSettings, (s): DynamicTabData[] => uniqBy(s.tabs || [], (t) => t.id)); export const getTargetEnvironmentId = createSelector(getFeatureSettings, (s) => s?.remoteEnvironment?.targetEnvironmentId); export const getSurveySettings = createSelector(getFeatureSettings, (s) => s?.surveySettings); +export const getStickyNotesSettings = createSelector(getFeatureSettings, (s) => s?.stickyNotesSettings); export const getLoggedUser = createSelector(getSettings, (s) => s.loggedUser); export const getLoggedUserId = createSelector(getLoggedUser, (s) => s.id); export const getProcessDefinitionData = createSelector(getSettings, (s) => s.processDefinitionData || ({} as ProcessDefinitionData)); diff --git a/designer/client/src/types/component.ts b/designer/client/src/types/component.ts index 6999e20af94..acd979bd43d 100644 --- a/designer/client/src/types/component.ts +++ b/designer/client/src/types/component.ts @@ -5,6 +5,7 @@ export type Component = { node: NodeType; label: string; componentId: string; + disabled?: () => boolean; }; export type ComponentGroup = { components: Component[]; diff --git a/designer/client/src/types/node.ts b/designer/client/src/types/node.ts index c021f21c87e..014a4c13670 100644 --- a/designer/client/src/types/node.ts +++ b/designer/client/src/types/node.ts @@ -1,7 +1,8 @@ import { ProcessAdditionalFields, ReturnedType } from "./scenarioGraph"; import { FragmentInputParameter } from "../components/graph/node-modal/fragment-input-definition/item"; +import { StickyNoteType } from "./stickyNote"; -type Type = "FragmentInput" | string; +type Type = "FragmentInput" | typeof StickyNoteType | string; export type LayoutData = { x: number; y: number }; diff --git a/designer/client/src/types/stickyNote.ts b/designer/client/src/types/stickyNote.ts new file mode 100644 index 00000000000..eb1758c2660 --- /dev/null +++ b/designer/client/src/types/stickyNote.ts @@ -0,0 +1,4 @@ +export const StickyNoteType = "StickyNote"; +export function createStickyNoteId(noteId: number) { + return StickyNoteType + "_" + noteId; +} diff --git a/designer/server/src/main/resources/defaultDesignerConfig.conf b/designer/server/src/main/resources/defaultDesignerConfig.conf index b6c2e5c724f..9084c19c84b 100644 --- a/designer/server/src/main/resources/defaultDesignerConfig.conf +++ b/designer/server/src/main/resources/defaultDesignerConfig.conf @@ -211,6 +211,12 @@ testDataSettings: { resultsMaxBytes: 50000000 } +stickyNotesSettings: { + maxContentLength: 5000, + maxNotesCount: 5 + enabled: true +} + scenarioLabelSettings: { validationRules = [ { diff --git a/designer/server/src/main/resources/web/static/assets/components/StickyNote.svg b/designer/server/src/main/resources/web/static/assets/components/StickyNote.svg new file mode 100644 index 00000000000..db4797cc376 --- /dev/null +++ b/designer/server/src/main/resources/web/static/assets/components/StickyNote.svg @@ -0,0 +1 @@ + diff --git a/designer/server/src/main/scala/db/migration/V1_060__CreateStickyNotesDefinition.scala b/designer/server/src/main/scala/db/migration/V1_060__CreateStickyNotesDefinition.scala new file mode 100644 index 00000000000..eef502e1b11 --- /dev/null +++ b/designer/server/src/main/scala/db/migration/V1_060__CreateStickyNotesDefinition.scala @@ -0,0 +1,89 @@ +package db.migration + +import com.typesafe.scalalogging.LazyLogging +import db.migration.V1_060__CreateStickyNotesDefinition.StickyNotesDefinitions +import pl.touk.nussknacker.ui.db.migration.SlickMigration +import slick.jdbc.JdbcProfile +import slick.sql.SqlProfile.ColumnOption.NotNull +import shapeless.syntax.std.tuple._ +import java.sql.Timestamp +import java.util.UUID +import scala.concurrent.ExecutionContext.Implicits.global + +trait V1_060__CreateStickyNotesDefinition extends SlickMigration with LazyLogging { + + import profile.api._ + + private val definitions = new StickyNotesDefinitions(profile) + + override def migrateActions: DBIOAction[Any, NoStream, Effect.All] = { + logger.info("Starting migration V1_060__CreateStickyNotesDefinition") + for { + _ <- definitions.stickyNotesEntityTable.schema.create + _ <- + sqlu"""ALTER TABLE "sticky_notes" ADD CONSTRAINT "sticky_notes_scenario_version_fk" FOREIGN KEY ("scenario_id", "scenario_version_id") REFERENCES "process_versions" ("process_id", "id") ON DELETE CASCADE;""" + } yield logger.info("Execution finished for migration V1_060__CreateStickyNotesDefinition") + } + +} + +object V1_060__CreateStickyNotesDefinition { + + class StickyNotesDefinitions(val profile: JdbcProfile) { + import profile.api._ + val stickyNotesEntityTable = TableQuery[StickyNotesEntity] + + class StickyNotesEntity(tag: Tag) extends Table[StickyNoteEventEntityData](tag, "sticky_notes") { + + def id = column[Long]("id", O.PrimaryKey, O.AutoInc) + def noteCorrelationId = column[UUID]("note_correlation_id", NotNull) + def content = column[String]("content", NotNull) + def layoutData = column[String]("layout_data", NotNull) + def color = column[String]("color", NotNull) + def dimensions = column[String]("dimensions", NotNull) + def targetEdge = column[Option[String]]("target_edge") + def eventCreator = column[String]("event_creator", NotNull) + def eventDate = column[Timestamp]("event_date", NotNull) + def eventType = column[String]("event_type", NotNull) + def scenarioId = column[Long]("scenario_id", NotNull) + def scenarioVersionId = column[Long]("scenario_version_id", NotNull) + + def tupleWithoutAutoIncId = ( + noteCorrelationId, + content, + layoutData, + color, + dimensions, + targetEdge, + eventCreator, + eventDate, + eventType, + scenarioId, + scenarioVersionId + ) + + override def * = + (id :: tupleWithoutAutoIncId.productElements).tupled <> ( + StickyNoteEventEntityData.apply _ tupled, StickyNoteEventEntityData.unapply + ) + + } + + } + + final case class StickyNoteEventEntityData( + id: Long, + noteCorrelationId: UUID, + content: String, + layoutData: String, + color: String, + dimensions: String, + targetEdge: Option[String], + eventCreator: String, + eventDate: Timestamp, + eventType: String, + scenarioId: Long, + scenarioVersionId: Long + ) + +} diff --git a/designer/server/src/main/scala/db/migration/hsql/V1_060__CreateStickyNotes.scala b/designer/server/src/main/scala/db/migration/hsql/V1_060__CreateStickyNotes.scala new file mode 100644 index 00000000000..86c5dd34568 --- /dev/null +++ b/designer/server/src/main/scala/db/migration/hsql/V1_060__CreateStickyNotes.scala @@ -0,0 +1,8 @@ +package db.migration.hsql + +import db.migration.V1_060__CreateStickyNotesDefinition +import slick.jdbc.{HsqldbProfile, JdbcProfile} + +class V1_060__CreateStickyNotes extends V1_060__CreateStickyNotesDefinition { + override protected lazy val profile: JdbcProfile = HsqldbProfile +} diff --git a/designer/server/src/main/scala/db/migration/postgres/V1_060__CreateStickyNotes.scala b/designer/server/src/main/scala/db/migration/postgres/V1_060__CreateStickyNotes.scala new file mode 100644 index 00000000000..e2887ece894 --- /dev/null +++ b/designer/server/src/main/scala/db/migration/postgres/V1_060__CreateStickyNotes.scala @@ -0,0 +1,8 @@ +package db.migration.postgres + +import db.migration.V1_060__CreateStickyNotesDefinition +import slick.jdbc.{JdbcProfile, PostgresProfile} + +class V1_060__CreateStickyNotes extends V1_060__CreateStickyNotesDefinition { + override protected lazy val profile: JdbcProfile = PostgresProfile +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/SettingsResources.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/SettingsResources.scala index 5c17a33ea9c..e2915233594 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/SettingsResources.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/SettingsResources.scala @@ -7,6 +7,7 @@ import io.circe.{Decoder, Encoder} import io.circe.generic.JsonCodec import pl.touk.nussknacker.ui.config.{FeatureTogglesConfig, UsageStatisticsReportsConfig} import pl.touk.nussknacker.engine.api.CirceUtil.codecs._ +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.StickyNotesSettings import pl.touk.nussknacker.ui.statistics.{Fingerprint, FingerprintService} import java.net.URL @@ -42,7 +43,8 @@ class SettingsResources( testDataSettings = config.testDataSettings, redirectAfterArchive = config.redirectAfterArchive, usageStatisticsReports = - UsageStatisticsReportsSettings(usageStatisticsReportsConfig, fingerprint.toOption) + UsageStatisticsReportsSettings(usageStatisticsReportsConfig, fingerprint.toOption), + stickyNotesSettings = config.stickyNotesSettings ) val authenticationSettings = AuthenticationSettings( authenticationMethod @@ -134,6 +136,7 @@ object TopTabType extends Enumeration { tabs: Option[List[TopTab]], intervalTimeSettings: IntervalTimeSettings, testDataSettings: TestDataSettings, + stickyNotesSettings: StickyNotesSettings, redirectAfterArchive: Boolean, usageStatisticsReports: UsageStatisticsReportsSettings, ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpService.scala new file mode 100644 index 00000000000..0c29c5257c3 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpService.scala @@ -0,0 +1,230 @@ +package pl.touk.nussknacker.ui.api + +import cats.data.EitherT +import com.typesafe.scalalogging.LazyLogging +import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessName, VersionId} +import pl.touk.nussknacker.security.Permission +import pl.touk.nussknacker.security.Permission.Permission +import pl.touk.nussknacker.ui.api.description.StickyNotesApiEndpoints +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{ + StickyNote, + StickyNoteAddRequest, + StickyNoteCorrelationId, + StickyNoteId, + StickyNoteUpdateRequest, + StickyNotesError, + StickyNotesSettings +} +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.StickyNotesError.{ + NoPermission, + NoScenario, + StickyNoteContentTooLong, + StickyNoteCountLimitReached +} +import pl.touk.nussknacker.ui.process.repository.stickynotes.StickyNotesRepository +import pl.touk.nussknacker.ui.process.repository.DBIOActionRunner +import pl.touk.nussknacker.ui.process.ProcessService +import pl.touk.nussknacker.ui.security.api.{AuthManager, LoggedUser} + +import scala.concurrent.{ExecutionContext, Future} + +class StickyNotesApiHttpService( + authManager: AuthManager, + stickyNotesRepository: StickyNotesRepository, + scenarioService: ProcessService, + scenarioAuthorizer: AuthorizeProcess, + dbioActionRunner: DBIOActionRunner, + stickyNotesSettings: StickyNotesSettings +)(implicit executionContext: ExecutionContext) + extends BaseHttpService(authManager) + with LazyLogging { + + private val securityInput = authManager.authenticationEndpointInput() + + private val endpoints = new StickyNotesApiEndpoints(securityInput) + + expose { + endpoints.stickyNotesGetEndpoint + .serverSecurityLogic(authorizeKnownUser[StickyNotesError]) + .serverLogicEitherT { implicit loggedUser => + { case (scenarioName, versionId) => + for { + scenarioId <- getScenarioIdByName(scenarioName) + _ <- isAuthorized(scenarioId, Permission.Read) + processActivity <- fetchStickyNotes(scenarioId, versionId) + } yield processActivity.toList + } + } + } + + expose { + endpoints.stickyNotesAddEndpoint + .serverSecurityLogic(authorizeKnownUser[StickyNotesError]) + .serverLogicEitherT { implicit loggedUser => + { case (scenarioName, requestBody) => + for { + scenarioId <- getScenarioIdByName(scenarioName) + _ <- isAuthorized(scenarioId, Permission.Write) + count <- getStickyNotesCount(scenarioId, requestBody.scenarioVersionId) + _ <- validateStickyNotesCount(count, stickyNotesSettings) + _ <- validateStickyNoteContent(requestBody.content, stickyNotesSettings) + processActivity <- addStickyNote(scenarioId, requestBody) + } yield processActivity + } + } + } + + expose { + endpoints.stickyNotesUpdateEndpoint + .serverSecurityLogic(authorizeKnownUser[StickyNotesError]) + .serverLogicEitherT { implicit loggedUser => + { case (scenarioName, requestBody) => + for { + scenarioId <- getScenarioIdByName(scenarioName) + _ <- isAuthorized(scenarioId, Permission.Write) + _ <- validateStickyNoteContent(requestBody.content, stickyNotesSettings) + processActivity <- updateStickyNote(requestBody) + } yield processActivity.toInt + } + } + } + + expose { + endpoints.stickyNotesDeleteEndpoint + .serverSecurityLogic(authorizeKnownUser[StickyNotesError]) + .serverLogicEitherT { implicit loggedUser => + { case (scenarioName, noteId) => + for { + scenarioId <- getScenarioIdByName(scenarioName) + _ <- isAuthorized(scenarioId, Permission.Write) + processActivity <- deleteStickyNote(noteId) + } yield processActivity.toInt + } + } + + } + + private def getScenarioIdByName(scenarioName: ProcessName) = { + EitherT.fromOptionF( + scenarioService.getProcessId(scenarioName), + NoScenario(scenarioName) + ) + } + + private def isAuthorized(scenarioId: ProcessId, permission: Permission)( + implicit loggedUser: LoggedUser + ): EitherT[Future, StickyNotesError, Unit] = + EitherT( + scenarioAuthorizer + .check(scenarioId, permission, loggedUser) + .map[Either[StickyNotesError, Unit]] { + case true => Right(()) + case false => Left(NoPermission) + } + ) + + private def fetchStickyNotes(scenarioId: ProcessId, versionId: VersionId)( + implicit loggedUser: LoggedUser + ): EitherT[Future, StickyNotesError, Seq[StickyNote]] = + EitherT + .right( + dbioActionRunner.run( + stickyNotesRepository.findStickyNotes(scenarioId, versionId) + ) + ) + + private def getStickyNotesCount(scenarioId: ProcessId, versionId: VersionId): EitherT[Future, StickyNotesError, Int] = + EitherT + .right( + dbioActionRunner + .run( + stickyNotesRepository.countStickyNotes(scenarioId, versionId) + ) + ) + + private def deleteStickyNote(noteId: StickyNoteId)( + implicit loggedUser: LoggedUser + ): EitherT[Future, StickyNotesError, Int] = + for { + note <- EitherT.fromOptionF( + dbioActionRunner.run( + stickyNotesRepository.findStickyNoteById(noteId) + ), + StickyNotesError.NoStickyNote(noteId) + ) + _ <- isAuthorized(note.scenarioId, Permission.Write) + result <- EitherT.right( + dbioActionRunner.run( + stickyNotesRepository.deleteStickyNote(noteId) + ) + ) + } yield result + + private def validateStickyNotesCount( + stickyNotesCount: Int, + stickyNotesConfig: StickyNotesSettings + ): EitherT[Future, StickyNotesError, Unit] = + EitherT.fromEither( + Either.cond( + stickyNotesCount < stickyNotesConfig.maxNotesCount, + (), + StickyNoteCountLimitReached(stickyNotesConfig.maxNotesCount) + ) + ) + + private def addStickyNote(scenarioId: ProcessId, requestBody: StickyNoteAddRequest)( + implicit loggedUser: LoggedUser + ): EitherT[Future, StickyNotesError, StickyNoteCorrelationId] = + EitherT + .right( + dbioActionRunner.run( + stickyNotesRepository.addStickyNote( + requestBody.content, + requestBody.layoutData, + requestBody.color, + requestBody.dimensions, + requestBody.targetEdge, + scenarioId, + requestBody.scenarioVersionId + ) + ) + ) + + private def validateStickyNoteContent( + content: String, + stickyNotesConfig: StickyNotesSettings + ): EitherT[Future, StickyNotesError, Unit] = + EitherT.fromEither( + Either.cond( + content.length <= stickyNotesConfig.maxContentLength, + (), + StickyNoteContentTooLong(content.length, stickyNotesConfig.maxContentLength) + ) + ) + + private def updateStickyNote(requestBody: StickyNoteUpdateRequest)( + implicit loggedUser: LoggedUser + ): EitherT[Future, StickyNotesError, Int] = for { + note <- EitherT.fromOptionF( + dbioActionRunner.run( + stickyNotesRepository.findStickyNoteById(requestBody.noteId) + ), + StickyNotesError.NoStickyNote(requestBody.noteId) + ) + _ <- isAuthorized(note.scenarioId, Permission.Write) + result <- EitherT.right( + dbioActionRunner.run( + stickyNotesRepository.updateStickyNote( + requestBody.noteId, + requestBody.content, + requestBody.layoutData, + requestBody.color, + requestBody.dimensions, + requestBody.targetEdge, + requestBody.scenarioVersionId + ) + ) + ) + } yield result + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/StickyNotesApiEndpoints.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/StickyNotesApiEndpoints.scala new file mode 100644 index 00000000000..4dff4ac3d0e --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/StickyNotesApiEndpoints.scala @@ -0,0 +1,219 @@ +package pl.touk.nussknacker.ui.api.description + +import io.circe.Encoder +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.process.{ProcessName, VersionId} +import pl.touk.nussknacker.engine.api.typed.typing._ +import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions +import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions.SecuredEndpoint +import pl.touk.nussknacker.security.AuthCredentials +import pl.touk.nussknacker.ui.api.TapirCodecs +import pl.touk.nussknacker.ui.api.TapirCodecs.ScenarioNameCodec._ +import pl.touk.nussknacker.ui.api.description.StickyNotesApiEndpoints.Examples.{ + noScenarioExample, + noStickyNoteExample, + stickyNoteContentTooLongExample, + stickyNoteCountLimitReachedExample +} +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.StickyNoteId +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.StickyNotesError.{ + NoScenario, + NoStickyNote, + StickyNoteContentTooLong, + StickyNoteCountLimitReached +} +import sttp.model.StatusCode.{BadRequest, NotFound, Ok} +import sttp.tapir.EndpointIO.Example +import sttp.tapir._ +import sttp.tapir.json.circe.jsonBody + +import java.time.Instant + +class StickyNotesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpointDefinitions { + + import stickynotes.Dtos._ + import TapirCodecs.VersionIdCodec._ + + lazy val encoder: Encoder[TypingResult] = TypingResult.encoder + + private val exampleInstantDate = Instant.ofEpochMilli(1730136602) + + private val exampleStickyNote = StickyNote( + StickyNoteId(1), + "##Title \nNote1", + LayoutData(20, 30), + "#99aa20", + Dimensions(300, 200), + None, + "Marco", + exampleInstantDate + ) + + lazy val stickyNotesGetEndpoint: SecuredEndpoint[ + (ProcessName, VersionId), + StickyNotesError, + List[StickyNote], + Any + ] = { + baseNuApiEndpoint + .summary("Returns sticky nodes for given scenario with version") + .tag("StickyNotes") + .get + .in("processes" / path[ProcessName]("scenarioName") / "stickyNotes" / query[VersionId]("scenarioVersionId")) + .out( + statusCode(Ok).and( + jsonBody[List[StickyNote]] + .examples( + List( + Example.of( + summary = Some("List of valid sticky notes returned for scenario"), + value = List( + exampleStickyNote, + exampleStickyNote.copy(noteId = StickyNoteId(2)) + ) + ) + ) + ) + ) + ) + .errorOut( + oneOf[StickyNotesError]( + noScenarioExample + ) + ) + .withSecurity(auth) + } + + lazy val stickyNotesUpdateEndpoint + : SecuredEndpoint[(ProcessName, StickyNoteUpdateRequest), StickyNotesError, Unit, Any] = { + baseNuApiEndpoint + .summary("Updates sticky note with new values") + .tag("StickyNotes") + .put + .in("processes" / path[ProcessName]("scenarioName") / "stickyNotes") + .in( + jsonBody[StickyNoteUpdateRequest] + .example( + StickyNoteUpdateRequest( + StickyNoteId(1), + VersionId(1), + "", + LayoutData(12, 33), + "#441022", + Dimensions(300, 200), + None + ) + ) + ) + .out( + statusCode(Ok) + ) + .errorOut( + oneOf[StickyNotesError]( + noScenarioExample, + noStickyNoteExample, + stickyNoteContentTooLongExample + ) + ) + .withSecurity(auth) + } + + lazy val stickyNotesAddEndpoint + : SecuredEndpoint[(ProcessName, StickyNoteAddRequest), StickyNotesError, StickyNoteCorrelationId, Any] = { + baseNuApiEndpoint + .summary("Creates new sticky note with given content") + .tag("StickyNotes") + .post + .in("processes" / path[ProcessName]("scenarioName") / "stickyNotes") + .in( + jsonBody[StickyNoteAddRequest] + .example(StickyNoteAddRequest(VersionId(1), "", LayoutData(12, 33), "#441022", Dimensions(300, 200), None)) + ) + .out(jsonBody[StickyNoteCorrelationId]) + .errorOut( + oneOf[StickyNotesError]( + noScenarioExample, + stickyNoteContentTooLongExample, + stickyNoteCountLimitReachedExample + ) + ) + .withSecurity(auth) + } + + lazy val stickyNotesDeleteEndpoint: SecuredEndpoint[(ProcessName, StickyNoteId), StickyNotesError, Unit, Any] = { + baseNuApiEndpoint + .summary("Deletes stickyNote by given noteId") + .tag("StickyNotes") + .delete + .in("processes" / path[ProcessName]("scenarioName") / "stickyNotes" / path[StickyNoteId]("noteId")) + .out( + statusCode(Ok) + ) + .errorOut( + oneOf[StickyNotesError]( + noStickyNoteExample, + noScenarioExample + ) + ) + .withSecurity(auth) + } + +} + +object StickyNotesApiEndpoints { + + object Examples { + + val noScenarioExample: EndpointOutput.OneOfVariant[NoScenario] = + oneOfVariantFromMatchType( + NotFound, + plainBody[NoScenario] + .example( + Example.of( + summary = Some("No scenario {scenarioName} found"), + value = NoScenario(ProcessName("s1")) + ) + ) + ) + + val noStickyNoteExample: EndpointOutput.OneOfVariant[NoStickyNote] = + oneOfVariantFromMatchType( + NotFound, + plainBody[NoStickyNote] + .example( + Example.of( + summary = Some("No sticky note with id: 3 was found"), + value = NoStickyNote(StickyNoteId(3)) + ) + ) + ) + + val stickyNoteContentTooLongExample: EndpointOutput.OneOfVariant[StickyNoteContentTooLong] = + oneOfVariantFromMatchType( + BadRequest, + plainBody[StickyNoteContentTooLong] + .example( + Example.of( + summary = Some("Provided note content is too long (5004 characters). Max content length is 5000."), + value = StickyNoteContentTooLong(count = 5004, max = 5000) + ) + ) + ) + + val stickyNoteCountLimitReachedExample: EndpointOutput.OneOfVariant[StickyNoteCountLimitReached] = + oneOfVariantFromMatchType( + BadRequest, + plainBody[StickyNoteCountLimitReached] + .example( + Example.of( + summary = Some( + "Cannot add another sticky note, since max number of sticky notes was reached: 5 (see configuration)." + ), + value = StickyNoteCountLimitReached(max = 5) + ) + ) + ) + + } + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/Dtos.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/Dtos.scala new file mode 100644 index 00000000000..82e0e0e5d4a --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/Dtos.scala @@ -0,0 +1,119 @@ +package pl.touk.nussknacker.ui.api.description.stickynotes + +import derevo.circe.{decoder, encoder} +import derevo.derive +import io.circe.generic.JsonCodec +import io.circe.{Decoder, Encoder} +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.process.{ProcessName, VersionId} +import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions +import pl.touk.nussknacker.ui.api.BaseHttpService.CustomAuthorizationError +import sttp.tapir.{Codec, CodecFormat, Schema} +import sttp.tapir.derevo.schema + +import java.time.Instant +import java.util.UUID + +object Dtos { + import pl.touk.nussknacker.ui.api.TapirCodecs.VersionIdCodec.{schema => versionIdSchema} + + final case class StickyNoteId(value: Long) extends AnyVal + + object StickyNoteId { + implicit val encoder: Encoder[StickyNoteId] = Encoder.encodeLong.contramap(_.value) + implicit val decoder: Decoder[StickyNoteId] = Decoder.decodeLong.map(StickyNoteId(_)) + } + + final case class StickyNoteCorrelationId(value: UUID) extends AnyVal + + object StickyNoteCorrelationId { + implicit val encoder: Encoder[StickyNoteCorrelationId] = Encoder.encodeUUID.contramap(_.value) + implicit val decoder: Decoder[StickyNoteCorrelationId] = Decoder.decodeUUID.map(StickyNoteCorrelationId(_)) + } + + implicit lazy val stickyNoteCorrelationIdSchema: Schema[StickyNoteCorrelationId] = + Schema.schemaForUUID.as[StickyNoteCorrelationId] + implicit lazy val stickyNoteIdSchema: Schema[StickyNoteId] = Schema.schemaForLong.as[StickyNoteId] + + @derive(encoder, decoder, schema) + case class Dimensions( + width: Long, + height: Long + ) + + @derive(encoder, decoder, schema) + case class StickyNote( + noteId: StickyNoteId, + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String], + editedBy: String, + editedAt: Instant + ) + + @derive(encoder, decoder, schema) + case class StickyNoteAddRequest( + scenarioVersionId: VersionId, + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String] + ) + + @derive(encoder, decoder, schema) + case class StickyNoteUpdateRequest( + noteId: StickyNoteId, + scenarioVersionId: VersionId, + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String] + ) + + @JsonCodec case class StickyNotesSettings( + maxContentLength: Int, + maxNotesCount: Int, + enabled: Boolean + ) + + object StickyNotesSettings { + val default: StickyNotesSettings = StickyNotesSettings(maxContentLength = 5000, maxNotesCount = 5, enabled = true) + } + + sealed trait StickyNotesError + + implicit lazy val layoutDataSchema: Schema[LayoutData] = Schema.derived + + object StickyNotesError { + + final case class NoScenario(scenarioName: ProcessName) extends StickyNotesError + final case object NoPermission extends StickyNotesError with CustomAuthorizationError + final case class StickyNoteContentTooLong(count: Int, max: Int) extends StickyNotesError + final case class StickyNoteCountLimitReached(max: Int) extends StickyNotesError + final case class NoStickyNote(noteId: StickyNoteId) extends StickyNotesError + + implicit val noScenarioCodec: Codec[String, NoScenario, CodecFormat.TextPlain] = + BaseEndpointDefinitions.toTextPlainCodecSerializationOnly[NoScenario](e => s"No scenario ${e.scenarioName} found") + + implicit val noStickyNoteCodec: Codec[String, NoStickyNote, CodecFormat.TextPlain] = + BaseEndpointDefinitions.toTextPlainCodecSerializationOnly[NoStickyNote](e => + s"No sticky note with id: ${e.noteId} was found" + ) + + implicit val stickyNoteContentTooLongCodec: Codec[String, StickyNoteContentTooLong, CodecFormat.TextPlain] = + BaseEndpointDefinitions.toTextPlainCodecSerializationOnly[StickyNoteContentTooLong](e => + s"Provided note content is too long (${e.count} characters). Max content length is ${e.max}." + ) + + implicit val stickyNoteCountLimitReachedCodec: Codec[String, StickyNoteCountLimitReached, CodecFormat.TextPlain] = + BaseEndpointDefinitions.toTextPlainCodecSerializationOnly[StickyNoteCountLimitReached](e => + s"Cannot add another sticky note, since max number of sticky notes was reached: ${e.max} (see configuration)." + ) + + } + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/StickyNoteEvent.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/StickyNoteEvent.scala new file mode 100644 index 00000000000..64d62164c90 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/stickynotes/StickyNoteEvent.scala @@ -0,0 +1,14 @@ +package pl.touk.nussknacker.ui.api.description.stickynotes + +import io.circe.{Decoder, Encoder} + +object StickyNoteEvent extends Enumeration { + implicit val typeEncoder: Encoder[StickyNoteEvent.Value] = Encoder.encodeEnumeration(StickyNoteEvent) + implicit val typeDecoder: Decoder[StickyNoteEvent.Value] = Decoder.decodeEnumeration(StickyNoteEvent) + + type StickyNoteEvent = Value + val StickyNoteCreated: Value = Value("CREATED") + val StickyNoteUpdated: Value = Value("UPDATED") + val StickyNoteDeleted: Value = Value("DELETED") + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/config/FeatureTogglesConfig.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/config/FeatureTogglesConfig.scala index 1ee85981ab3..410a29037f1 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/config/FeatureTogglesConfig.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/config/FeatureTogglesConfig.scala @@ -7,6 +7,7 @@ import net.ceedubs.ficus.readers.ValueReader import pl.touk.nussknacker.engine.definition.component.Components.ComponentDefinitionExtractionMode import pl.touk.nussknacker.engine.util.config.FicusReaders import pl.touk.nussknacker.ui.api._ +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.StickyNotesSettings import pl.touk.nussknacker.ui.config.Implicits.parseOptionalConfig import pl.touk.nussknacker.ui.process.migrate.HttpRemoteEnvironmentConfig @@ -28,7 +29,8 @@ final case class FeatureTogglesConfig( testDataSettings: TestDataSettings, enableConfigEndpoint: Boolean, redirectAfterArchive: Boolean, - componentDefinitionExtractionMode: ComponentDefinitionExtractionMode + componentDefinitionExtractionMode: ComponentDefinitionExtractionMode, + stickyNotesSettings: StickyNotesSettings ) object FeatureTogglesConfig extends LazyLogging { @@ -56,8 +58,10 @@ object FeatureTogglesConfig extends LazyLogging { val tabs = parseOptionalConfig[List[TopTab]](config, "tabs") val intervalTimeSettings = config.as[IntervalTimeSettings]("intervalTimeSettings") val testDataSettings = config.as[TestDataSettings]("testDataSettings") - val redirectAfterArchive = config.getAs[Boolean]("redirectAfterArchive").getOrElse(true) - val componentDefinitionExtractionMode = parseComponentDefinitionExtractionMode(config) + val stickyNotesSettings = + config.getAs[StickyNotesSettings]("stickyNotesSettings").getOrElse(StickyNotesSettings.default) + val redirectAfterArchive = config.getAs[Boolean]("redirectAfterArchive").getOrElse(true) + val componentDefinitionExtractionMode = parseComponentDefinitionExtractionMode(config) FeatureTogglesConfig( development = isDevelopmentMode, @@ -76,6 +80,7 @@ object FeatureTogglesConfig extends LazyLogging { enableConfigEndpoint = enableConfigEndpoint, redirectAfterArchive = redirectAfterArchive, componentDefinitionExtractionMode = componentDefinitionExtractionMode, + stickyNotesSettings = stickyNotesSettings ) } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/NuTables.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/NuTables.scala index 034df3f48bc..a3e7344c378 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/NuTables.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/NuTables.scala @@ -11,7 +11,8 @@ trait NuTables with ScenarioActivityEntityFactory with ScenarioLabelsEntityFactory with AttachmentEntityFactory - with DeploymentEntityFactory { + with DeploymentEntityFactory + with StickyNotesEntityFactory { protected val profile: JdbcProfile } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/entity/StickyNotesEntityFactory.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/entity/StickyNotesEntityFactory.scala new file mode 100644 index 00000000000..2bff4a1b639 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/db/entity/StickyNotesEntityFactory.scala @@ -0,0 +1,112 @@ +package pl.touk.nussknacker.ui.db.entity + +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId} +import pl.touk.nussknacker.ui.api.description.stickynotes.StickyNoteEvent +import pl.touk.nussknacker.ui.api.description.stickynotes.StickyNoteEvent.StickyNoteEvent +import slick.lifted.{ProvenShape, TableQuery => LTableQuery} +import slick.sql.SqlProfile.ColumnOption.NotNull +import io.circe.syntax._ +import io.circe._ +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{ + Dimensions, + StickyNote, + StickyNoteCorrelationId, + StickyNoteId +} + +import java.sql.Timestamp +import java.util.UUID + +trait StickyNotesEntityFactory extends BaseEntityFactory { + + import profile.api._ + + val processVersionsTable: LTableQuery[ProcessVersionEntityFactory#ProcessVersionEntity] + + class StickyNotesEntity(tag: Tag) extends Table[StickyNoteEventEntityData](tag, "sticky_notes") { + + def id = column[StickyNoteId]("id", O.PrimaryKey, O.AutoInc) + def noteCorrelationId = column[StickyNoteCorrelationId]("note_correlation_id", NotNull) + def content = column[String]("content", NotNull) + def layoutData = column[LayoutData]("layout_data", NotNull) + def color = column[String]("color", NotNull) + def dimensions = column[Dimensions]("dimensions", NotNull) + def targetEdge = column[Option[String]]("target_edge") + def eventCreator = column[String]("event_creator", NotNull) + def eventDate = column[Timestamp]("event_date", NotNull) + def eventType = column[StickyNoteEvent]("event_type", NotNull) + def scenarioId = column[ProcessId]("scenario_id", NotNull) + def scenarioVersionId = column[VersionId]("scenario_version_id", NotNull) + + def * : ProvenShape[StickyNoteEventEntityData] = ( + id, + noteCorrelationId, + content, + layoutData, + color, + dimensions, + targetEdge, + eventCreator, + eventDate, + eventType, + scenarioId, + scenarioVersionId + ) <> (StickyNoteEventEntityData.apply _ tupled, StickyNoteEventEntityData.unapply) + + def scenarioVersion = + foreignKey("sticky_notes_scenario_version_fk", (scenarioId, scenarioVersionId), processVersionsTable)( + t => (t.processId, t.id), + onUpdate = ForeignKeyAction.Cascade, + onDelete = ForeignKeyAction.Cascade + ) + + } + + implicit def stickyNoteEventColumnTyped: BaseColumnType[StickyNoteEvent] = + MappedColumnType.base[StickyNoteEvent, String](_.toString, StickyNoteEvent.withName) + + implicit def stickyNoteIdColumnTyped: BaseColumnType[StickyNoteId] = + MappedColumnType.base[StickyNoteId, Long](_.value, StickyNoteId(_)) + implicit def stickyNoteCorrelationIdColumnTyped: BaseColumnType[StickyNoteCorrelationId] = + MappedColumnType.base[StickyNoteCorrelationId, UUID](_.value, StickyNoteCorrelationId(_)) + + implicit def layoutDataColumnTyped: BaseColumnType[LayoutData] = MappedColumnType.base[LayoutData, String]( + _.asJson.noSpaces, + jsonStr => + parser.parse(jsonStr).flatMap(Decoder[LayoutData].decodeJson) match { + case Right(layoutData) => layoutData + case Left(error) => throw error + } + ) + + implicit def dimensionsColumnTyped: BaseColumnType[Dimensions] = MappedColumnType.base[Dimensions, String]( + _.asJson.noSpaces, + jsonStr => + parser.parse(jsonStr).flatMap(Decoder[Dimensions].decodeJson) match { + case Right(dimensions) => dimensions + case Left(error) => throw error + } + ) + + val stickyNotesTable: LTableQuery[StickyNotesEntityFactory#StickyNotesEntity] = LTableQuery(new StickyNotesEntity(_)) + +} + +final case class StickyNoteEventEntityData( + id: StickyNoteId, + noteCorrelationId: StickyNoteCorrelationId, + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String], + eventCreator: String, + eventDate: Timestamp, + eventType: StickyNoteEvent, + scenarioId: ProcessId, + scenarioVersionId: VersionId +) { + def toStickyNote: StickyNote = + StickyNote(id, content, layoutData, color, dimensions, targetEdge, eventCreator, eventDate.toInstant) +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/DbStickyNotesRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/DbStickyNotesRepository.scala new file mode 100644 index 00000000000..8acf546cf28 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/DbStickyNotesRepository.scala @@ -0,0 +1,174 @@ +package pl.touk.nussknacker.ui.process.repository.stickynotes + +import com.typesafe.scalalogging.LazyLogging +import db.util.DBIOActionInstances.DB +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId} +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{ + Dimensions, + StickyNote, + StickyNoteCorrelationId, + StickyNoteId +} +import pl.touk.nussknacker.ui.api.description.stickynotes.StickyNoteEvent +import pl.touk.nussknacker.ui.db.entity.StickyNoteEventEntityData +import pl.touk.nussknacker.ui.db.{DbRef, NuTables} +import pl.touk.nussknacker.ui.process.repository.DbioRepository +import pl.touk.nussknacker.ui.security.api.LoggedUser +import slick.dbio.DBIOAction + +import java.sql.Timestamp +import java.time.Clock +import java.util.UUID +import scala.concurrent.ExecutionContext + +class DbStickyNotesRepository private (override protected val dbRef: DbRef, override val clock: Clock)( + implicit executionContext: ExecutionContext +) extends DbioRepository + with NuTables + with StickyNotesRepository + with LazyLogging { + + import profile.api._ + + override def findStickyNotes(scenarioId: ProcessId, scenarioVersionId: VersionId): DB[Seq[StickyNote]] = { + run( + stickyNotesTable + .filter(event => event.scenarioId === scenarioId && event.scenarioVersionId <= scenarioVersionId) + .groupBy(_.noteCorrelationId) + .map { case (noteCorrelationId, notes) => (noteCorrelationId, notes.map(_.eventDate).max) } + .join(stickyNotesTable) + .on { case ((noteCorrelationId, eventDate), event) => + event.noteCorrelationId === noteCorrelationId && event.eventDate === eventDate + } + .map { case ((_, _), event) => event } + .result + .map(events => events.filter(_.eventType != StickyNoteEvent.StickyNoteDeleted).map(_.toStickyNote)) + ) + } + + override def countStickyNotes(scenarioId: ProcessId, scenarioVersionId: VersionId): DB[Int] = { + run( + stickyNotesTable + .filter(event => event.scenarioId === scenarioId && event.scenarioVersionId <= scenarioVersionId) + .groupBy(_.noteCorrelationId) + .map { case (noteCorrelationId, notes) => (noteCorrelationId, notes.map(_.eventDate).max) } + .join(stickyNotesTable) + .on { case ((noteCorrelationId, eventDate), event) => + event.noteCorrelationId === noteCorrelationId && event.eventDate === eventDate + } + .map { case ((_, _), event) => event } + .result + .map(events => events.count(_.eventType != StickyNoteEvent.StickyNoteDeleted)) + ) + } + + override def findStickyNoteById( + noteId: StickyNoteId + )(implicit user: LoggedUser): DB[Option[StickyNoteEventEntityData]] = { + run( + stickyNotesTable + .filter(_.id === noteId) + .result + .headOption + ) + } + + override def addStickyNote( + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String], + scenarioId: ProcessId, + scenarioVersionId: VersionId + )( + implicit user: LoggedUser + ): DB[StickyNoteCorrelationId] = { + val now = Timestamp.from(clock.instant()) + val entity = StickyNoteEventEntityData( + id = StickyNoteId(0), // ignored since id is AutoInc + noteCorrelationId = StickyNoteCorrelationId(UUID.randomUUID()), + content = content, + layoutData = layoutData, + color = color, + dimensions = dimensions, + targetEdge = targetEdge, + eventDate = now, + eventCreator = user.id, + eventType = StickyNoteEvent.StickyNoteCreated, + scenarioId = scenarioId, + scenarioVersionId = scenarioVersionId + ) + run(stickyNotesTable += entity).flatMap { + case 0 => DBIOAction.failed(new IllegalStateException(s"This is odd, no sticky note was added")) + case 1 => DBIOAction.successful(entity.noteCorrelationId) + case n => + DBIOAction.failed( + new IllegalStateException(s"This is odd, more than one sticky note were added (added $n records).") + ) + } + + } + + private def updateStickyNote(id: StickyNoteId, updateAction: StickyNoteEventEntityData => StickyNoteEventEntityData)( + implicit user: LoggedUser + ): DB[Int] = { + run(for { + actionResult <- stickyNotesTable.filter(_.id === id).result.headOption.flatMap { + case None => + DBIOAction.failed( + new NoSuchElementException(s"Trying to update record (id=${id.value}) which is not present in the database") + ) + case Some(latestEvent) => + val newEvent = updateAction(latestEvent) + stickyNotesTable += newEvent + } + } yield actionResult) + } + + override def updateStickyNote( + noteId: StickyNoteId, + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String], + scenarioVersionId: VersionId, + )( + implicit user: LoggedUser + ): DB[Int] = { + val now = Timestamp.from(clock.instant()) + def updateAction(latestEvent: StickyNoteEventEntityData): StickyNoteEventEntityData = latestEvent.copy( + eventDate = now, + eventCreator = user.id, + eventType = StickyNoteEvent.StickyNoteUpdated, + content = content, + color = color, + dimensions = dimensions, + targetEdge = targetEdge, + layoutData = layoutData, + scenarioVersionId = scenarioVersionId + ) + updateStickyNote(noteId, updateAction) + } + + override def deleteStickyNote(noteId: StickyNoteId)(implicit user: LoggedUser): DB[Int] = { + val now = Timestamp.from(clock.instant()) + def updateAction(latestEvent: StickyNoteEventEntityData): StickyNoteEventEntityData = latestEvent.copy( + eventDate = now, + eventCreator = user.id, + eventType = StickyNoteEvent.StickyNoteDeleted + ) + updateStickyNote(noteId, updateAction) + } + +} + +object DbStickyNotesRepository { + + def create(dbRef: DbRef, clock: Clock)( + implicit executionContext: ExecutionContext, + ): StickyNotesRepository = new DbStickyNotesRepository(dbRef, clock) + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/StickyNotesRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/StickyNotesRepository.scala new file mode 100644 index 00000000000..6f762c7ad49 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/stickynotes/StickyNotesRepository.scala @@ -0,0 +1,55 @@ +package pl.touk.nussknacker.ui.process.repository.stickynotes + +import db.util.DBIOActionInstances.DB +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId} +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{ + Dimensions, + StickyNote, + StickyNoteCorrelationId, + StickyNoteId +} +import pl.touk.nussknacker.ui.db.entity.StickyNoteEventEntityData +import pl.touk.nussknacker.ui.security.api.LoggedUser + +import java.time.Clock + +trait StickyNotesRepository { + + def clock: Clock + + def findStickyNotes( + scenarioId: ProcessId, + scenarioVersionId: VersionId + ): DB[Seq[StickyNote]] + + def countStickyNotes( + scenarioId: ProcessId, + scenarioVersionId: VersionId + ): DB[Int] + + def addStickyNote( + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String], + scenarioId: ProcessId, + scenarioVersionId: VersionId + )(implicit user: LoggedUser): DB[StickyNoteCorrelationId] + + def updateStickyNote( + noteId: StickyNoteId, + content: String, + layoutData: LayoutData, + color: String, + dimensions: Dimensions, + targetEdge: Option[String], + scenarioVersionId: VersionId, + )(implicit user: LoggedUser): DB[Int] + + def findStickyNoteById(noteId: StickyNoteId)(implicit user: LoggedUser): DB[Option[StickyNoteEventEntityData]] + + def deleteStickyNote(noteId: StickyNoteId)(implicit user: LoggedUser): DB[Int] + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala index 7e76f370fb0..ec0da71149b 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala @@ -71,6 +71,7 @@ import pl.touk.nussknacker.ui.process.processingtype.provider.ReloadableProcessi import pl.touk.nussknacker.ui.process.repository._ import pl.touk.nussknacker.ui.process.repository.activities.{DbScenarioActivityRepository, ScenarioActivityRepository} import pl.touk.nussknacker.ui.process.scenarioactivity.FetchScenarioActivityService +import pl.touk.nussknacker.ui.process.repository.stickynotes.DbStickyNotesRepository import pl.touk.nussknacker.ui.process.test.{PreliminaryScenarioTestDataSerDe, ScenarioTestService} import pl.touk.nussknacker.ui.process.version.{ScenarioGraphVersionRepository, ScenarioGraphVersionService} import pl.touk.nussknacker.ui.processreport.ProcessCounter @@ -172,6 +173,7 @@ class AkkaHttpBasedRouteProvider( implicit val implicitDbioRunner: DBIOActionRunner = dbioRunner val scenarioActivityRepository = DbScenarioActivityRepository.create(dbRef, designerClock) val actionRepository = DbScenarioActionRepository.create(dbRef, modelBuildInfo) + val stickyNotesRepository = DbStickyNotesRepository.create(dbRef, designerClock) val scenarioLabelsRepository = new ScenarioLabelsRepository(dbRef) val processRepository = DBFetchingProcessRepository.create(dbRef, actionRepository, scenarioLabelsRepository) // TODO: get rid of Future based repositories - it is easier to use everywhere one implementation - DBIOAction based which allows transactions handling @@ -409,6 +411,15 @@ class AkkaHttpBasedRouteProvider( scenarioService = processService, ) + val stickyNotesApiHttpService = new StickyNotesApiHttpService( + authManager = authManager, + stickyNotesRepository = stickyNotesRepository, + scenarioService = processService, + scenarioAuthorizer = processAuthorizer, + dbioRunner, + stickyNotesSettings = featureTogglesConfig.stickyNotesSettings + ) + val scenarioActivityApiHttpService = new ScenarioActivityApiHttpService( authManager = authManager, fetchScenarioActivityService = fetchScenarioActivityService, @@ -601,6 +612,7 @@ class AkkaHttpBasedRouteProvider( scenarioActivityApiHttpService, scenarioLabelsApiHttpService, scenarioParametersHttpService, + stickyNotesApiHttpService, userApiHttpService, statisticsApiHttpService ) diff --git a/designer/server/src/test/resources/config/common-designer.conf b/designer/server/src/test/resources/config/common-designer.conf index 508a1300a82..ce04adefb40 100644 --- a/designer/server/src/test/resources/config/common-designer.conf +++ b/designer/server/src/test/resources/config/common-designer.conf @@ -54,6 +54,12 @@ testDataSettings: { resultsMaxBytes: 50000000 } +stickyNotesSettings: { + maxContentLength: 5000, + maxNotesCount: 5, + enabled: true +} + scenarioLabelSettings: { validationRules = [ { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithSimplifiedConfigScenarioHelper.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithSimplifiedConfigScenarioHelper.scala index 93beb1f8d1e..dd3f02b9294 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithSimplifiedConfigScenarioHelper.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/WithSimplifiedConfigScenarioHelper.scala @@ -6,6 +6,8 @@ import pl.touk.nussknacker.test.base.db.WithTestDb import pl.touk.nussknacker.test.config.WithSimplifiedDesignerConfig import pl.touk.nussknacker.test.config.WithSimplifiedDesignerConfig.TestCategory import pl.touk.nussknacker.test.utils.domain.ScenarioHelper +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{StickyNoteAddRequest, StickyNoteCorrelationId} +import pl.touk.nussknacker.ui.process.repository.ProcessRepository.ProcessUpdated import scala.concurrent.ExecutionContext.Implicits.global @@ -39,4 +41,12 @@ trait WithSimplifiedConfigScenarioHelper { rawScenarioHelper.createSavedScenario(scenario, usedCategory.stringify, isFragment = true) } + def updateScenario(scenarioName: ProcessName, newVersion: CanonicalProcess): ProcessUpdated = { + rawScenarioHelper.updateScenario(scenarioName, newVersion) + } + + def addStickyNote(scenarioName: ProcessName, request: StickyNoteAddRequest): StickyNoteCorrelationId = { + rawScenarioHelper.addStickyNote(scenarioName, request) + } + } diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala index 7f893afe583..91f3ed111fd 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala @@ -10,14 +10,20 @@ import pl.touk.nussknacker.engine.management.FlinkStreamingPropertiesConfig import pl.touk.nussknacker.test.PatientScalaFutures import pl.touk.nussknacker.test.config.WithSimplifiedDesignerConfig.TestProcessingType.Streaming import pl.touk.nussknacker.test.mock.TestAdditionalUIConfigProvider +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{StickyNoteAddRequest, StickyNoteCorrelationId} import pl.touk.nussknacker.ui.db.DbRef import pl.touk.nussknacker.ui.definition.ScenarioPropertiesConfigFinalizer import pl.touk.nussknacker.ui.process.NewProcessPreparer import pl.touk.nussknacker.ui.process.processingtype.ValueWithRestriction import pl.touk.nussknacker.ui.process.processingtype.provider.ProcessingTypeDataProvider -import pl.touk.nussknacker.ui.process.repository.ProcessRepository.CreateProcessAction +import pl.touk.nussknacker.ui.process.repository.ProcessRepository.{ + CreateProcessAction, + ProcessUpdated, + UpdateProcessAction +} import pl.touk.nussknacker.ui.process.repository._ import pl.touk.nussknacker.ui.process.repository.activities.DbScenarioActivityRepository +import pl.touk.nussknacker.ui.process.repository.stickynotes.{DbStickyNotesRepository, StickyNotesRepository} import pl.touk.nussknacker.ui.security.api.{LoggedUser, RealLoggedUser} import slick.dbio.DBIOAction @@ -39,6 +45,7 @@ private[test] class ScenarioHelper(dbRef: DbRef, clock: Clock, designerConfig: C ) private val scenarioLabelsRepository: ScenarioLabelsRepository = new ScenarioLabelsRepository(dbRef) + private val stickyNotesRepository: StickyNotesRepository = DbStickyNotesRepository.create(dbRef, clock) private val writeScenarioRepository: DBProcessRepository = new DBProcessRepository( dbRef, @@ -75,6 +82,20 @@ private[test] class ScenarioHelper(dbRef: DbRef, clock: Clock, designerConfig: C saveAndGetId(scenario, category, isFragment).futureValue } + def updateScenario( + scenarioName: ProcessName, + newScenario: CanonicalProcess + ): ProcessUpdated = { + updateAndGetScenarioVersions(scenarioName, newScenario).futureValue + } + + def addStickyNote( + scenarioName: ProcessName, + request: StickyNoteAddRequest + ): StickyNoteCorrelationId = { + addStickyNoteForScenario(scenarioName, request).futureValue + } + def createDeployedExampleScenario(scenarioName: ProcessName, category: String, isFragment: Boolean): ProcessId = { (for { id <- prepareValidScenario(scenarioName, category, isFragment) @@ -189,6 +210,44 @@ private[test] class ScenarioHelper(dbRef: DbRef, clock: Clock, designerConfig: C } yield id } + private def updateAndGetScenarioVersions( + scenarioName: ProcessName, + newScenario: CanonicalProcess + ): Future[ProcessUpdated] = { + for { + scenarioId <- futureFetchingScenarioRepository.fetchProcessId(scenarioName).map(_.get) + action = UpdateProcessAction( + scenarioId, + newScenario, + comment = None, + labels = List.empty, + increaseVersionWhenJsonNotChanged = true, + forwardedUserName = None + ) + processUpdated <- dbioRunner.runInTransaction(writeScenarioRepository.updateProcess(action)) + } yield processUpdated + } + + private def addStickyNoteForScenario( + scenarioName: ProcessName, + request: StickyNoteAddRequest + ): Future[StickyNoteCorrelationId] = { + for { + scenarioId <- futureFetchingScenarioRepository.fetchProcessId(scenarioName).map(_.get) + noteCorrelationId <- dbioRunner.runInTransaction( + stickyNotesRepository.addStickyNote( + request.content, + request.layoutData, + request.color, + request.dimensions, + request.targetEdge, + scenarioId, + request.scenarioVersionId + ) + ) + } yield noteCorrelationId + } + private def mapProcessingTypeDataProvider[T](value: T) = { ProcessingTypeDataProvider.withEmptyCombinedData( processingTypeWithCategories.map { case (processingType, _) => diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpServiceBusinessSpec.scala new file mode 100644 index 00000000000..fcaeffd1ae3 --- /dev/null +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/StickyNotesApiHttpServiceBusinessSpec.scala @@ -0,0 +1,165 @@ +package pl.touk.nussknacker.ui.api + +import io.restassured.RestAssured.`given` +import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse +import org.scalatest.freespec.AnyFreeSpecLike +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.process.{ProcessName, VersionId} +import pl.touk.nussknacker.engine.build.ScenarioBuilder +import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} +import pl.touk.nussknacker.test.config.{ + WithBusinessCaseRestAssuredUsersExtensions, + WithMockableDeploymentManager, + WithSimplifiedDesignerConfig +} +import pl.touk.nussknacker.test.{ + NuRestAssureExtensions, + NuRestAssureMatchers, + RestAssuredVerboseLoggingIfValidationFails +} +import pl.touk.nussknacker.ui.api.description.stickynotes.Dtos.{Dimensions, StickyNoteAddRequest} + +import java.util.UUID + +class StickyNotesApiHttpServiceBusinessSpec + extends AnyFreeSpecLike + with NuItTest + with WithSimplifiedDesignerConfig + with WithSimplifiedConfigScenarioHelper + with WithMockableDeploymentManager + with WithBusinessCaseRestAssuredUsersExtensions + with NuRestAssureExtensions + with NuRestAssureMatchers + with RestAssuredVerboseLoggingIfValidationFails { + + private val exampleScenarioName = UUID.randomUUID().toString + + private val exampleScenario = ScenarioBuilder + .requestResponse(exampleScenarioName) + .source("sourceId", "barSource") + .emptySink("sinkId", "barSink") + + private def stickyNoteToAdd(versionId: VersionId, content: String): StickyNoteAddRequest = + StickyNoteAddRequest(versionId, content, LayoutData(0, 1), "#aabbcc", Dimensions(300, 200), None) + + "The GET stickyNotes for scenario" - { + "return no notes if nothing was created" in { + given() + .applicationState { + createSavedScenario(exampleScenario) + } + .when() + .basicAuthAllPermUser() + .get(s"$nuDesignerHttpAddress/api/processes/$exampleScenarioName/stickyNotes?scenarioVersionId=0") + .Then() + .statusCode(200) + .equalsJsonBody("[]") + } + + "return 404 if no scenario with given name exists" in { + given() + .when() + .basicAuthAllPermUser() + .get(s"$nuDesignerHttpAddress/api/processes/$exampleScenarioName/stickyNotes?scenarioVersionId=0") + .Then() + .statusCode(404) + .equalsPlainBody(s"No scenario $exampleScenarioName found") + } + + "return zero notes for scenarioVersion=1 if notes were added in scenarioVersion=2" in { + given() + .applicationState { + createSavedScenario(exampleScenario) + val updatedProcess = updateScenario(ProcessName(exampleScenarioName), exampleScenario) + addStickyNote(ProcessName(exampleScenarioName), stickyNoteToAdd(updatedProcess.newVersion.get, "")) + } + .when() + .basicAuthAllPermUser() + .get(s"$nuDesignerHttpAddress/api/processes/$exampleScenarioName/stickyNotes?scenarioVersionId=1") + .Then() + .statusCode(200) + .equalsJsonBody("[]") + } + + "return sticky notes for scenarioVersion=2" in { + given() + .applicationState { + createSavedScenario(exampleScenario) + val updatedProcess = updateScenario(ProcessName(exampleScenarioName), exampleScenario) + addStickyNote(ProcessName(exampleScenarioName), stickyNoteToAdd(updatedProcess.newVersion.get, "title1")) + } + .when() + .basicAuthAllPermUser() + .get(s"$nuDesignerHttpAddress/api/processes/$exampleScenarioName/stickyNotes?scenarioVersionId=2") + .Then() + .statusCode(200) + .body( + matchJsonWithRegexValues( + s"""[ + { + "noteId": "${regexes.digitsRegex}", + "content": "title1", + "layoutData": { + "x": 0, + "y": 1 + }, + "color": "#aabbcc", + "dimensions": { + "width": 300, + "height": 200 + }, + "targetEdge": null, + "editedBy": "admin", + "editedAt": "${regexes.zuluDateRegex}" + } + ]""".stripMargin + ) + ) + + } + + "return sticky notes for scenarioVersion=2 even if more for scenarioVersion=3 were added" in { + given() + .applicationState { + createSavedScenario(exampleScenario) + val updatedProcess = updateScenario(ProcessName(exampleScenarioName), exampleScenario) + addStickyNote(ProcessName(exampleScenarioName), stickyNoteToAdd(updatedProcess.newVersion.get, "sticky 1")) + val updatedProcessOnceMore = updateScenario(ProcessName(exampleScenarioName), exampleScenario) + addStickyNote( + ProcessName(exampleScenarioName), + stickyNoteToAdd(updatedProcessOnceMore.newVersion.get, "sticky 2") + ) + } + .when() + .basicAuthAllPermUser() + .get(s"$nuDesignerHttpAddress/api/processes/$exampleScenarioName/stickyNotes?scenarioVersionId=2") + .Then() + .statusCode(200) + .body( + matchJsonWithRegexValues( + s"""[ + { + "noteId": "${regexes.digitsRegex}", + "content": "sticky 1", + "layoutData": { + "x": 0, + "y": 1 + }, + "color": "#aabbcc", + "dimensions": { + "width": 300, + "height": 200 + }, + "targetEdge": null, + "editedBy": "admin", + "editedAt": "${regexes.zuluDateRegex}" + } + ]""".stripMargin + ) + ) + + } + + } + +} diff --git a/docs-internal/api/nu-designer-openapi.yaml b/docs-internal/api/nu-designer-openapi.yaml index b20a4e94ff9..849a6c93f88 100644 --- a/docs-internal/api/nu-designer-openapi.yaml +++ b/docs-internal/api/nu-designer-openapi.yaml @@ -2452,6 +2452,406 @@ paths: security: - {} - httpAuth: [] + /api/processes/{scenarioName}/stickyNotes: + get: + tags: + - StickyNotes + summary: Returns sticky nodes for given scenario with version + operationId: getApiProcessesScenarionameStickynotes + parameters: + - name: Nu-Impersonate-User-Identity + in: header + required: false + schema: + type: + - string + - 'null' + - name: scenarioName + in: path + required: true + schema: + type: string + - name: scenarioVersionId + in: query + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/StickyNote' + examples: + Example: + summary: List of valid sticky notes returned for scenario + value: + - noteId: 1 + content: "##Title \nNote1" + layoutData: + x: 20 + y: 30 + color: '#99aa20' + dimensions: + width: 300 + height: 200 + editedBy: Marco + editedAt: '1970-01-21T00:35:36.602Z' + - noteId: 2 + content: "##Title \nNote1" + layoutData: + x: 20 + y: 30 + color: '#99aa20' + dimensions: + width: 300 + height: 200 + editedBy: Marco + editedAt: '1970-01-21T00:35:36.602Z' + '400': + description: 'Invalid value for: header Nu-Impersonate-User-Identity, Invalid + value for: query parameter scenarioVersionId' + content: + text/plain: + schema: + type: string + '401': + description: '' + content: + text/plain: + schema: + type: string + examples: + CannotAuthenticateUser: + value: The supplied authentication is invalid + ImpersonatedUserNotExistsError: + value: No impersonated user data found for provided identity + '403': + description: '' + content: + text/plain: + schema: + type: string + examples: + InsufficientPermission: + value: The supplied authentication is not authorized to access this + resource + ImpersonationMissingPermission: + value: The supplied authentication is not authorized to impersonate + '404': + description: '' + content: + text/plain: + schema: + type: string + examples: + Example: + summary: No scenario {scenarioName} found + value: No scenario s1 found + '501': + description: Impersonation is not supported for defined authentication mechanism + content: + text/plain: + schema: + type: string + examples: + Example: + summary: Cannot authenticate impersonated user as impersonation + is not supported by the authentication mechanism + value: Provided authentication method does not support impersonation + security: + - {} + - httpAuth: [] + put: + tags: + - StickyNotes + summary: Updates sticky note with new values + operationId: putApiProcessesScenarionameStickynotes + parameters: + - name: Nu-Impersonate-User-Identity + in: header + required: false + schema: + type: + - string + - 'null' + - name: scenarioName + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StickyNoteUpdateRequest' + example: + noteId: 1 + scenarioVersionId: 1 + content: '' + layoutData: + x: 12 + y: 33 + color: '#441022' + dimensions: + width: 300 + height: 200 + required: true + responses: + '200': + description: '' + '400': + description: '' + content: + text/plain: + schema: + type: string + examples: + Example: + summary: Provided note content is too long (5004 characters). Max + content length is 5000. + value: Provided note content is too long (5004 characters). Max + content length is 5000. + '401': + description: '' + content: + text/plain: + schema: + type: string + examples: + CannotAuthenticateUser: + value: The supplied authentication is invalid + ImpersonatedUserNotExistsError: + value: No impersonated user data found for provided identity + '403': + description: '' + content: + text/plain: + schema: + type: string + examples: + InsufficientPermission: + value: The supplied authentication is not authorized to access this + resource + ImpersonationMissingPermission: + value: The supplied authentication is not authorized to impersonate + '404': + description: '' + content: + text/plain: + schema: + anyOf: + - type: string + - type: string + examples: + Example: + summary: 'No sticky note with id: 3 was found' + value: 'No sticky note with id: StickyNoteId(3) was found' + '501': + description: Impersonation is not supported for defined authentication mechanism + content: + text/plain: + schema: + type: string + examples: + Example: + summary: Cannot authenticate impersonated user as impersonation + is not supported by the authentication mechanism + value: Provided authentication method does not support impersonation + security: + - {} + - httpAuth: [] + post: + tags: + - StickyNotes + summary: Creates new sticky note with given content + operationId: postApiProcessesScenarionameStickynotes + parameters: + - name: Nu-Impersonate-User-Identity + in: header + required: false + schema: + type: + - string + - 'null' + - name: scenarioName + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StickyNoteAddRequest' + example: + scenarioVersionId: 1 + content: '' + layoutData: + x: 12 + y: 33 + color: '#441022' + dimensions: + width: 300 + height: 200 + required: true + responses: + '200': + description: '' + content: + application/json: + schema: + type: string + format: uuid + '400': + description: '' + content: + text/plain: + schema: + anyOf: + - type: string + - type: string + examples: + Example: + summary: 'Cannot add another sticky note, since max number of sticky + notes was reached: 5 (see configuration).' + value: 'Cannot add another sticky note, since max number of sticky + notes was reached: 5 (see configuration).' + '401': + description: '' + content: + text/plain: + schema: + type: string + examples: + CannotAuthenticateUser: + value: The supplied authentication is invalid + ImpersonatedUserNotExistsError: + value: No impersonated user data found for provided identity + '403': + description: '' + content: + text/plain: + schema: + type: string + examples: + InsufficientPermission: + value: The supplied authentication is not authorized to access this + resource + ImpersonationMissingPermission: + value: The supplied authentication is not authorized to impersonate + '404': + description: '' + content: + text/plain: + schema: + type: string + examples: + Example: + summary: No scenario {scenarioName} found + value: No scenario s1 found + '501': + description: Impersonation is not supported for defined authentication mechanism + content: + text/plain: + schema: + type: string + examples: + Example: + summary: Cannot authenticate impersonated user as impersonation + is not supported by the authentication mechanism + value: Provided authentication method does not support impersonation + security: + - {} + - httpAuth: [] + /api/processes/{scenarioName}/stickyNotes/{noteId}: + delete: + tags: + - StickyNotes + summary: Deletes stickyNote by given noteId + operationId: deleteApiProcessesScenarionameStickynotesNoteid + parameters: + - name: Nu-Impersonate-User-Identity + in: header + required: false + schema: + type: + - string + - 'null' + - name: scenarioName + in: path + required: true + schema: + type: string + - name: noteId + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: '' + '400': + description: 'Invalid value for: header Nu-Impersonate-User-Identity, Invalid + value for: path parameter noteId' + content: + text/plain: + schema: + type: string + '401': + description: '' + content: + text/plain: + schema: + type: string + examples: + CannotAuthenticateUser: + value: The supplied authentication is invalid + ImpersonatedUserNotExistsError: + value: No impersonated user data found for provided identity + '403': + description: '' + content: + text/plain: + schema: + type: string + examples: + InsufficientPermission: + value: The supplied authentication is not authorized to access this + resource + ImpersonationMissingPermission: + value: The supplied authentication is not authorized to impersonate + '404': + description: '' + content: + text/plain: + schema: + anyOf: + - type: string + - type: string + examples: + Example: + summary: No scenario {scenarioName} found + value: No scenario s1 found + '501': + description: Impersonation is not supported for defined authentication mechanism + content: + text/plain: + schema: + type: string + examples: + Example: + summary: Cannot authenticate impersonated user as impersonation + is not supported by the authentication mechanism + value: Provided authentication method does not support impersonation + security: + - {} + - httpAuth: [] /api/scenarioTesting/{scenarioName}/adhoc/validate: post: tags: @@ -4987,6 +5387,19 @@ components: properties: dictId: type: string + Dimensions: + title: Dimensions + type: object + required: + - width + - height + properties: + width: + type: integer + format: int64 + height: + type: integer + format: int64 DisplayableUser: title: DisplayableUser type: object @@ -6664,6 +7077,92 @@ components: type: string enum: - ERROR + StickyNote: + title: StickyNote + type: object + required: + - noteId + - content + - layoutData + - color + - dimensions + - editedBy + - editedAt + properties: + noteId: + type: integer + format: int64 + content: + type: string + layoutData: + $ref: '#/components/schemas/LayoutData' + color: + type: string + dimensions: + $ref: '#/components/schemas/Dimensions' + targetEdge: + type: + - string + - 'null' + editedBy: + type: string + editedAt: + type: string + format: date-time + StickyNoteAddRequest: + title: StickyNoteAddRequest + type: object + required: + - scenarioVersionId + - content + - layoutData + - color + - dimensions + properties: + scenarioVersionId: + type: integer + format: int64 + content: + type: string + layoutData: + $ref: '#/components/schemas/LayoutData' + color: + type: string + dimensions: + $ref: '#/components/schemas/Dimensions' + targetEdge: + type: + - string + - 'null' + StickyNoteUpdateRequest: + title: StickyNoteUpdateRequest + type: object + required: + - noteId + - scenarioVersionId + - content + - layoutData + - color + - dimensions + properties: + noteId: + type: integer + format: int64 + scenarioVersionId: + type: integer + format: int64 + content: + type: string + layoutData: + $ref: '#/components/schemas/LayoutData' + color: + type: string + dimensions: + $ref: '#/components/schemas/Dimensions' + targetEdge: + type: + - string + - 'null' StringParameterEditor: title: StringParameterEditor type: object diff --git a/docs/Changelog.md b/docs/Changelog.md index 67927213ae4..b4f42020bd6 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -10,6 +10,10 @@ ### 1.19.0 (Not released yet) +* [#7181](https://github.com/TouK/nussknacker/pull/7181) StickyNotes feature + * sticky notes are designed to store information inside scenario/fragment, they are separate from graph nodes and do not take part in scenario logic + * new API available under `processes/{scenarioName}/stickyNotes` + * configuration `stickyNotesSettings` allowing to hide/show stickyNotes, set sticky notes max content length or its max number on a graph * [#7145](https://github.com/TouK/nussknacker/pull/7145) Lift TypingResult information for dictionaries * [#7116](https://github.com/TouK/nussknacker/pull/7116) Improve missing Flink Kafka Source / Sink TypeInformation * [#7123](https://github.com/TouK/nussknacker/pull/7123) Fix deployments for scenarios with dict editors after model reload diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index ac47ca0fbd2..fd2975f560e 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -4,6 +4,15 @@ To see the biggest differences please consult the [changelog](Changelog.md). ## In version 1.19.0 (Not released yet) + +### Configuration changes + +* [#7181](https://github.com/TouK/nussknacker/pull/7181) Added designer configuration: stickyNotesSettings + * maxContentLength - max length of a sticky notes content (characters) + * maxNotesCount - max count of sticky notes inside one scenario/fragment + * enabled - if set to false stickyNotes feature is disabled, stickyNotes cant be created, they are also not loaded to graph + + ### Other changes * [#7116](https://github.com/TouK/nussknacker/pull/7116) Improve missing Flink Kafka Source / Sink TypeInformation