-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrss.xml
2706 lines (2541 loc) · 348 KB
/
rss.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Engineering at MindLink]]></title><description><![CDATA[Insights and musings from the MindLink software engineers]]></description><link>https://engineering.mindlinksoft.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 25 Jun 2019 13:08:50 GMT</lastBuildDate><item><title><![CDATA[Rebuilding a performant emoji picker in React with hooks]]></title><description><![CDATA[Before I start, we didn’t really want to have to do this. There are a couple of really good projects out there that offer an emoji picker…]]></description><link>https://engineering.mindlinksoft.comrebuilding-a-performant-emoji-picker-in-react-with-hooks</link><guid isPermaLink="false">https://engineering.mindlinksoft.comrebuilding-a-performant-emoji-picker-in-react-with-hooks</guid><pubDate>Thu, 23 May 2019 00:00:00 GMT</pubDate><content:encoded><p>Before I start, we didn’t really want to have to do this. There are a couple of really good projects out there that offer an emoji picker, they just didn’t <em>quite</em> work how we needed them to.</p>
<h2>TL;DR</h2>
<p>It is surprisingly easy to build out a complex component with React and hooks. However, you have to be careful about accessibility and profile the performance routinely!</p>
<p>In a couple of days we managed to put together a performant and feature-rich emoji picker UX that supports down to IE 10 using <a href="https://reactjs.org/">React</a>, <a href="https://reactjs.org/docs/hooks-intro.html">hooks</a>, <a href="https://github.com/bvaughn/react-window">react-window</a> and an emoji data source!</p>
<h2>Background</h2>
<p>As you perhaps already know one of MindLink’s core products is a single page application for accessing chat systems.</p>
<p>We support legacy chat systems from a time where emojis were not a mainstream expectation from users. We bring up to date emoji support to those chat systems via our server-side abstraction layer, which allows us to build modern features on top of existing Enterprise chat systems.</p>
<p>To bring emoji support to our clients we originally we went with <a href="https://github.com/missive/emoji-mart">emoji-mart</a> by missive. It’s sleek and stylish and has a lot of customisation options. However, we encountered some issues with performance:</p>
<ul>
<li>On IE10/11 the showing of the picker is slooww as it struggles to render all 2000+ emojis into the DOM</li>
<li>Even in modern browsers there’s a noticeable delay</li>
<li>The use of a large sprite sheet results in browsers having to reset their graphics context so switching back to a tab with the picker open results in a brief white screen</li>
</ul>
<p>Additionally, we really wanted to customise it in other ways:</p>
<ul>
<li>Inject the search term as a prop to make it work with our message input</li>
<li>To forward on keyboard events to move the selected emojis/trigger selection</li>
</ul>
<p>What we didn’t need:</p>
<ul>
<li>Customisable source (we opted for Twitter, we don’t need the overhead of supporting other emoji icons)</li>
<li>Custom emojis (at least not yet)</li>
</ul>
<h2>Moving to a virtualized list</h2>
<p>We knew from performance profiling that the biggest issue is the rendering of the emoji list. 2000+ emojis on screen at once, not a good idea. We need a better way - a virtualised list.</p>
<p>Simply put, a virtualised list only renders what is visible into the DOM on demand and tracks scroll position to trigger loading different parts of the list. This means the complete list is virtual and we render only a window.</p>
<p>A well-established solution in the React world is <a href="https://github.com/bvaughn/react-window">react-window</a>. It is a great project that handles most of the pain of managing a virtualised list or grid. There is also the possibility to get some vanilla JS to do this using an <code class="language-text">IntersectionObserver</code> to trigger rendering “pages”.</p>
<p>We are also fortunate that right now it’s pretty easy to make lightweight components using React hooks.</p>
<p>Combining both react-window and hooks we sought to recreate the minimal feature set we wanted from emoji-mart:</p>
<ul>
<li>Category icon buttons allow you to jump to categories and also highlight what category you’re currently viewing</li>
<li>Inline category headers separating groups of emojis</li>
<li>A larger preview of a highlighted emoji showing the shortcode, description and ASCII art equivalents</li>
<li>A skin variation picker to select a skin tone</li>
<li>Search to filter the emojis</li>
</ul>
<h2>A look at the finished picker</h2>
<p>A first draft of this we threw together in about half a day - leveraging the emoji-mart data source so that we didn’t have to worry about compiling that. That is the power of leveraging hooks and react-window.</p>
<p>We took that first draft, liked it and built it out over another couple of days to get it to where we wanted it for production.</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/picker-tone-32f2c7748747621aabad37edd831bff1-0904b.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 398px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 103.26633165829145%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAENElEQVQ4y4WVSY/bRhCF9bvzC3I0kGtOOeTmg2EgcOxk7Alsy/Es2kWJEhdxFTeRlLhImhl/qaY8QW4RVOrWY3d19auqx15WZJjpmvJQEvgBYRiy3+9xXZfD4YDv+wRB0GGe53VYFEWkaUqe5ziO02Fqjdrbi/KQvn9NXu1Yr9aYlklZlpim2S3UNA1d19ntdtzd3XWYYRjdgQobj8f/BrDZbOilSUomp1VVJXboxsNh321UdsGry1xhsrl6nv/H6rruxl4cR2y8iMcnePp2Mfl2o/zKXB5cEJ4/j9+Nb90inp4uz47HI71DfabWf6WO52z134lDuc7kNZn3hUk/wZpHLG5cTK1Af/cby6s/ift3RF/vcKKYhdCUpBnWxu2i7u2rlpP3mbYIaLMZReZTpTOavU1i5pRhTugdSJIWb6rjzA0ay6fZBGRVI85K8lpG8ZPEMb1a+HGjnHxfMdPFiRCtjX22dsK15TEMEw7LAamxpo4+Uod98uCW3Llm59wSbd7TjO6p/3hJdTrRU4Rv072UTY0TZuzEsWNnZOGOftgwy87k2pR4ZVEmC4poKaNBGWlsYxcnWAldDoU1pG6Fw0rCLZYvZcGS2n5DE41pgz7HwqQwr2R+S+n3qZI7sqWUz3rJzjJIlxp716bUNSLXwh5+oT2dFYcNp1j+7IW76J4m31B4A9qdjb9dc8hMqngiuE5h6JS2TmYsSLQRhWOy02fkkU+0GtOoCNu2ZTyaEMUp809/sXU26Ldf8dc6q8VCCtbDGA1w9CXGdIg+uOnqNS9yEmfNdj1lKx2y1ma0qmwacWjNBmRJhD+9Ee5c3PEtsb0msFakoUe0log8C0cc2uN7iiQk8zdSWhaxOScJZI9E3BwlKXXT4K6EbMnuZiXXylJsUyeWErD0OYG0lKbfEsr19NVIsAllXkgpOZRFwVZ6uEwTtovJ9yuLw9FyRZTljCZTwihhNR8SBh7L4T2uYWJbU7Kty2o6YTPRiCUiazkn8j3W86nQFaAth5crV01LKBHuZFGR7+TUnCzaSqQJbpLhxXJ6LNF7IcGuRJfSCqX/B4bHLo3xbJNMbmfMJULx1Tsez9z8fY0t2YusdcelJyWhuNPWEp3I00LUZikJ0tyAe93EkDZ7P5jjSzJmkwlBkvDhU//Sy4VoWitkVqIWyg5KWaQ2Dy1UzbGb18cHmtOj1NmDiMg3zmJKFlRTqKQqlVH8Kdnr7b/L0bM8VVKXVRHTuq9pko2Y9PhWI7dnxPqQ2Zf3TD9/YPDhbefkWeqUn06+FNBIYi5Wy6IzbenxOP+Bh2AkZvBoX7EbvsX7+IqrX17w5ucXvPrpR86ieUoHn/eriKVTTtRCZl1X3cOLyTWb8wVv1PzY9amKSLXX8XzmdH7onDzvUQ3SOWySOW114VGB/28NRxkVX+q9ojCVDPU6UNg/5CUZ8YDCaO8AAAAASUVORK5CYII='); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="Emoji picker with skin tone"
title=""
src="/static/picker-tone-32f2c7748747621aabad37edd831bff1-0904b.png"
srcset="/static/picker-tone-32f2c7748747621aabad37edd831bff1-7c835.png 148w,
/static/picker-tone-32f2c7748747621aabad37edd831bff1-11b18.png 295w,
/static/picker-tone-32f2c7748747621aabad37edd831bff1-0904b.png 398w"
sizes="(max-width: 398px) 100vw, 398px"
/>
</span>
</span>
</a>
</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/picker-preview-fa3fdceab79f58afba33fe9edcf15e3f-5d328.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 399px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 102.75689223057644%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAACXBIWXMAAA7DAAAOwwHHb6hkAAADU0lEQVQ4y41US2/cNhDWv+n/6z/oucf20EsuRR8w0iRFnLZ2W7tx4sSOY/ix2fVm1ytKlLR6kxTJGVI24nVnrboweuoHaDRPzlDkpwAQKl0AWill27aISBIAuq4jxTlHfnKSqZT6N0pOMoPOqjf5rsA2jvhsNqPsMAyNMYyx0WhEecfHx1SZZRl5qPj8/JzM5XJJaQGtkVXpsDCBFCEESfIPCrUaokO3pmmGNEKgpEp5enNzc3t7u7rD7f8DlQTWWq01zUMDD3JQCHAHvMdDnXLWnWkMfociL6IoChlL02wdRux73/f9ffYDieido5YBfRtrDW0D6EXoJJjOU/Dqum7qsiyonkw/PJ6Wo9g/VuCubuX0W5u/79gLzF6n4V4W7efsYHK2e7j9zV+Pv6jCPb34CapTNvlZFx/q0SMn5mr0te+vAxpBiwR0Y2QBpq6kaJSsW9mJ+vLj+Xj0zqjaiAyM6NoUjFRt7LADxah1AP0KJhuYndj4RZ/u8/FWNN+5ersFe89dc2Anz1x9jOyJa0Yu2sByasffY8P02SPvrwOgzbfM6Rpkgm0OKkGVGbk0gju6eQVztnIqdrZBFXotsWLOKFfOnesD8CvLNqA+AfYblod28qudbGF+gMlLWxwq9gtUJxA/9+0Yomcophj9iF1qyeOpGHsUMzSlLy/6loGIreBecq9S1EsnQ9QFyoUztZdzb5tezhwoLy/Xe0a/MovH9DFV9LvJ3zX8VZe+hvyNTXexOu74n7Y40WwT24kIN7vyopn8QLuIR985f0Wdr1w7djqzzUeQcdVyKyOnIhCX2GVdG7oug2bqdN5UM6tKlZ95aKE6czQ2+k+GbUI9svE+5KcuO7LFRKdHlh9gfWrSl7YaW75d06LZjpcLOhSvU4yfrs/Z4CdbvAcV6+gM8rnNLmwxN8up5WNQl7b+gGIBJSUkXXZgRazTV1Zmmu9YwIAujTz8XITbSaEWLGY8jZOMZ3kYpzFfLsKEJ8uI53Gc0Ix0rvfyhngSmOSPt199tjz6Mkry6cW4yJeCGNw2oiXK1FVZEJHpTdz2bk2MgRUOYX23iQ9dPtKq4kmaJBzXZOr9PQa9v4MbCPEAgQXor1fo+oGuD0n7HxDz6WcyJBD9SPkbkJuAO7JG2eEAAAAASUVORK5CYII='); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="Emoji picker with preview"
title=""
src="/static/picker-preview-fa3fdceab79f58afba33fe9edcf15e3f-5d328.png"
srcset="/static/picker-preview-fa3fdceab79f58afba33fe9edcf15e3f-be17d.png 148w,
/static/picker-preview-fa3fdceab79f58afba33fe9edcf15e3f-7298c.png 295w,
/static/picker-preview-fa3fdceab79f58afba33fe9edcf15e3f-5d328.png 399w"
sizes="(max-width: 399px) 100vw, 399px"
/>
</span>
</span>
</a>
</p>
<p>The results are impressive considering the short time frame. The picker loads considerable faster and supports all the same use cases we had. We now have the full benefits of owning that:</p>
<ul>
<li>We can style it totally how we like</li>
<li>We can add extra features specific to our use cases</li>
<li>We can keep it as up to date as we need it (hello Unicode 12)</li>
</ul>
<p>We want to take this open-source, but before we do we need to figure out a first-class data source and stop piggy-backing off of emoji-mart so we can control the Unicode support.</p>
<p>I’ll take a deep-dive into the implementation in a few follow-up posts.</p></content:encoded></item><item><title><![CDATA[Automate your memory profiling now]]></title><description><![CDATA[MindLink’s core product is designed to be a long running and stable server brokering between clients and chat systems. Stability is…]]></description><link>https://engineering.mindlinksoft.comautomate-your-memory-profiling-now</link><guid isPermaLink="false">https://engineering.mindlinksoft.comautomate-your-memory-profiling-now</guid><pubDate>Wed, 17 Apr 2019 00:00:00 GMT</pubDate><content:encoded><p>MindLink’s core product is designed to be a long running and stable server brokering between clients and chat systems. Stability is paramount to ensure users have an optimal experience. We have a large number of automated tests, exploratory and planned manual QA processes and we dogfood our products with nightly builds.</p>
<p>Combined, these strategies have ensured that our products continue to be scalable, reliable and high quality. But we can do better!</p>
<p>Recently we discovered a memory leak late into the QA and release process and this made it out into customer hands. Not ideal, but these things happen. The leak itself has implications when lots of users log on and log out over time and will eventually cause the process to run out of memory.</p>
<h2>TL;DR</h2>
<p>By leveraging a memory profiler that provides programmatic access to snapshots you can automate memory performance testing to give you high confidence that you haven’t introduced any memory leaks into your code under reproducible scenarios.</p>
<p>One such profiler for .Net code that’s free (as in beer) is <a href="https://www.jetbrains.com/dotmemory/unit/">dotMemory Unit</a>.</p>
<h2>The story</h2>
<p>The cause of the leak could only effectively be discovered through memory profiling and it was the result of several issues combining to cause the leak, if any one of those issues were alleviated the leak would not have occurred.</p>
<p>If memory profiling is what it takes to notice these, or at least some manual observation, then there is always the chance that nobody actually checks (as what happened here).</p>
<p>Can we do better than relying on somebody to remember to check, for every release? And to check all necessary scenarios manually? Not really…</p>
<h2>The search for a tool</h2>
<p>What we really want is a way to automate that profiling during the release pipeline and to flag memory leaks automatically.</p>
<p>We could crudely invoke a process snapshot before and after an operation and then compare the memory, but that isn’t going to be reliable and how do we ensure that each scenario is actually running?</p>
<p>Ideally we want programmatic access to a profiler so that we can:</p>
<ol>
<li>start profiling</li>
<li>Execute the scenario</li>
<li>Stop profiling</li>
<li>Make assertions on the profiling session</li>
</ol>
<p>As luck would have it, such magic exists! We leverage the .net framework and we are therefore fortunate to be able to benefit from the brilliant <a href="https://www.jetbrains.com/dotmemory/unit/">dotMemory Unit</a> project by JetBrains (the makers of <a href="https://www.jetbrains.com/resharper/">ReSharper</a> and <a href="https://kotlinlang.org/">Kotlin</a>).</p>
<p>Unlike its GUI profiling counterpart dotMemory, dotMemory Unit is free! And it lets you programmatically profile and make assertions on the profiling session.</p>
<p>There are some caveats:</p>
<ol>
<li>Tests have to be synchronous</li>
<li>The test runner has to be called via the dotMemory Unit executable.</li>
</ol>
<p>That seems reasonable considering the power that it gives us. With dotMemory Unit we can write automated and isolated tests that ensure we don’t have memory leaks.</p>
<h2>Writing a memory unit test</h2>
<p>For our issue that snuck under the manual testing radar we knew the specific scenario that showed the issue, all we needed to do was log a user onto a chat system and then log them off again, basically the simplest scenario we could write - so that’s nice!</p>
<p>Here’s an example <a href="https://nunit.org/">NUnit</a> test:</p>
<div class="gatsby-highlight">
<pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span>Test<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">LoggingOffCleansUpSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">LoggingOffCleansUpSessionAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
dotMemory<span class="token punctuation">.</span><span class="token function">Check</span><span class="token punctuation">(</span>memory <span class="token operator">=</span><span class="token operator">></span> Assert<span class="token punctuation">.</span><span class="token function">That</span><span class="token punctuation">(</span>memory<span class="token punctuation">.</span><span class="token generic-method function">GetObjectsOfType<span class="token punctuation">&lt;</span>ISession<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>ObjectCount<span class="token punctuation">,</span> Is<span class="token punctuation">.</span><span class="token function">EqualTo</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">private</span> <span class="token keyword">async</span> Task <span class="token function">LoggingOffCleansUpSessionAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> session <span class="token operator">=</span> <span class="token function">CreateSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">LogOnAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">LogOffAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
</div>
<p>What we are doing here is creating a session, logging it on and then logging it off. Since the session is asynchronous and we want a clean test case we use an async method to perform the test steps and wait for that in the actual test to satisfy dotMemory unit’s synchronous constraint.</p>
<p>Our first use of dotMemory Unit was to create a test over the entire session stack, since the memory leak we encountered was the combination of internal behaviour and how the session was wired together. This high level test will also flag any memory leaks specific to individual units.</p>
<p>We are aiming to create memory tests for more isolated areas of our code so that we can have better coverage over different scenarios. It’s a work-in-progress, but certainly an improvement on relying on manual testing and something I think everybody should consider when stability and performance is important.</p>
<p>In a future post I will discuss how we integrated dotMemory Unit tests into our Azure DevOps release pipeline.</p></content:encoded></item><item><title><![CDATA[Hybrid Electron application deployment in the enterprise]]></title><description><![CDATA[Enterprises love control, that usually means they’re only going to install your software if there’s an MSI package that they can deploy…]]></description><link>https://engineering.mindlinksoft.comhybrid-electron-application-deployment-in-the-enterprise</link><guid isPermaLink="false">https://engineering.mindlinksoft.comhybrid-electron-application-deployment-in-the-enterprise</guid><pubDate>Thu, 14 Mar 2019 00:00:00 GMT</pubDate><content:encoded><p>Enterprises love control, that usually means they’re only going to install your software if there’s an MSI package that they can deploy through group policy.</p>
<p>This means that if you want a lovely auto-updating client so that you don’t have to maintain backwards compatible server code you’re <em>stuck</em>. Except there is a way forward!</p>
<h2>TL;DR</h2>
<p>By treating your Electron application as a browser shell and taking control of downloading the latest non-native assets from a target server (the “web app”), you can automatically sync the client version with the server version and do that securely by signing your non-native assets.</p>
<h2>Background</h2>
<p>It is inescapable that some form of forwards or backwards compatibility will be necessary, for MindLink we are forced to have a client that is backwards compatible with older servers since our mobile apps are app-store distributed and hence on a faster deployment cadence than the on-premise servers our customers deploy. If there’s a customer that is slow to deploy a new version, we don’t want all our other customers not getting the latest and greatest client.</p>
<p>This means that we don’t need a backwards compatible server, and that’s brilliant because that keeps code lean and up to date.</p>
<p>However, when we looked to package up our web app as a desktop application, suddenly we found a backwards compatible client is not enough. In an enterprise deployment often its the other way around, servers are updated first and then clients.</p>
<p>That means we either need to also maintain a backwards compatible server, or find a way to keep clients in-sync.</p>
<p>We know first hand the pain of keeping legacy code around for backwards compatibility - the choices available aren’t great when you’re talking about behavioural differences. Thats why we <em>really</em> want to avoid it in the server.</p>
<h2>The modern deployment approach</h2>
<p>Modern client deployment techniques follow an auto-update process. If you’re trying to use a really old WhatsApp then it’ll force you to install the latest update. Mobile apps and associated app stores have instilled this approach into consumers, and the same trend is happening on desktops with most services now in the cloud. Maintaining a cloud service that has to be compatible with many different client versions is an engineering overhead best avoided.</p>
<p>The classic auto-update approach is to replace the native installation. That’s great if you’re installing into user-space, but in an Enterprise world the app is natively installed and sysadmins don’t want an automatically installed native package that they haven’t got deployment control over.</p>
<p>You could implement a native bootloader instead, so you pull down a native payload in the bootloader and then launch that. There are security concerns around this approach and it’s possible that the native code execution is blocked by some security policy.</p>
<p>However there is a middle ground - do exactly what a browser does. After all, Enterprises don’t stop browsers from loading web sites or apps (provided they’re not blacklisted)!</p>
<h2>The idea</h2>
<p>In the Electron world you’re basically a browser renderer and engine without the native interop built-in (you implement those bits yourself). So here’s the idea:</p>
<ol>
<li>Have a versioned native interface</li>
<li>Load a default application payload into Chromium in an isolated context</li>
<li>Fetch the right application payload from the server, stick it in local user app data and load that payload into the isolated context instead</li>
<li>We can remember which payload we last used to skip always loading the default payload</li>
</ol>
<p>This is basically what a browser does with a cache, only we are busting the cache manually and storing the payload manually.</p>
<p>To ensure that we aren’t getting some nefarious payload from the server we will sign the payload and verify the signature using an embedded public key in the native payload.</p>
<p>In normal circumstances, where the server and client are the same version the default payload is used and everything is awesome.</p>
<p>When the server is upgraded the client sees there’s a newer payload available from the server, downloads it and uses that payload instead.</p>
<p>The same is true of a client that accesses an older server - just download the older package (although a backwards-compatible client would just work).</p>
<p>Wait, what about the native interop?! I hear you say.</p>
<p>Well, we can’t overwrite that native interop with the payload without opening ourselves up to other attack vectors. Instead we version the native interop and ensure that:</p>
<ol>
<li>Native interop maintains backwards compatibility</li>
<li>Client maintains backwards compatibility with native interop</li>
</ol>
<p>Perhaps this sounds like more work than maintaining a backwards compatible server? We expect the native interop to change far less often than our server stack, and in addition all our backwards compatibility code remains client side instead of being spread between client and server.</p>
<h2>Wrap-up</h2>
<p>We didn’t come to this conclusion lightly, we brainstormed the approaches and overhead of maintaining a backwards compatible server and unanimously agreed that we really didn’t want to be doing that if we could avoid it. It would slow the release of new features and mean we’d have to maintain both a backwards compatible server and backwards compatible client - much nicer not to have to worry about that!</p>
<p>This proposal is still being finalised and isn’t live yet. However, this approach essentially amounts to what a browser does, the only difference is we’re exposing some extra APIs for our “web app” to leverage.</p></content:encoded></item><item><title><![CDATA[The Symphony London Hackathon 2018]]></title><description><![CDATA[MindLink entered it’s first hackathon and placed second with the Town Hall bot. This post is a brief review of how it went! TL;DR MindLink…]]></description><link>https://engineering.mindlinksoft.comthe-symphony-london-hackathon-2018</link><guid isPermaLink="false">https://engineering.mindlinksoft.comthe-symphony-london-hackathon-2018</guid><pubDate>Mon, 01 Oct 2018 00:00:00 GMT</pubDate><content:encoded><p>MindLink entered it’s first hackathon and placed second with the Town Hall bot. This post is a brief review of how it went!</p>
<h2>TL;DR</h2>
<ul>
<li>MindLink entered the Symphony London Hackathon September 2018 and we placed second with Town Hall Bot!</li>
<li>We had fun, learned a lot about Symphony and made a complex bot to improve transparency and company-wide relationships</li>
</ul>
<h2>The idea</h2>
<p>This is the first hackathon MindLink has entered, after our success hopefully it won’t be our last!</p>
<p>If you’re not familiar with Symphony as a platform, I suggest you go and check it out at <a href="https://www.symphony.com">https://www.symphony.com</a>, in the most basic description it’s a secure real-time messaging platform that supports 1-1, multiparty and chat room messaging. There’s much more to it than that, but it sets the scene!</p>
<p>Symphony has many APIs available, but we wanted to focus on user experience and that boils down to two main APIs:</p>
<ul>
<li>Bot => a back-end application that can send and receive messages in 1-1, multiparty and chat room conversations,</li>
<li>Client extension => a website that is embedded within the Symphony UX and can directly communicate with parts of the UX</li>
</ul>
<p>We wanted to incorporate both of these APIs in some form and tie them together for a unique and seamless user experience. We will cover the specifics of how we managed to tie them together in another post.</p>
<p>MindLink has a lot of ideas from a long history in the collaboration and messaging space, but we kept coming back to the idea of a “Town Hall Bot”.</p>
<p>Town Hall meetings are fundamentally about transparency, whether that’s within a company or with its shareholders. The larger a company gets the more silos form, the harder it is for everybody to move in the same direction. Town Halls are all about breaking down those barriers to keep everybody on the same page, that’s broadly applicable to every part of a company and so has a huge potential for positive business impact.</p>
<h2>The hackathon</h2>
<p>The hackathon itself was held at the BNP Paribas offices in Harewood Avenue, London. As can be expected from a banking group the offices are modern, professional and big! A large open atrium space was assigned for the hackathon and there were about 9 teams and 50 attendees including the Symphony cohort. Everbody from Symphony was welcoming, approachable and encouraging - exactly what you need for an event like this!</p>
<p>Having never been to a hackathon before we were apprehensive, we knew we had the ideas and the expertise, but could we deliver in the time?!</p>
<p>For the most part the day was spent with the MindLink team (myself, Dimitri Fadda and Jamie Matthews) diligently working away, making sure we kept regularly in sync. Our approach was simple - get a minimum viable product and then build upon its feature set. We didn’t break for lunch, but enjoyed the <strong>really good</strong> sandwiches put on for the event while continuing to develop the bot. </p>
<p>We were in a good place for most of the event, until the last hour and a half - that’s when everything is supposed to come together and work beautifully and it didn’t!</p>
<p>Unfortunately we had an issue with a set of features that relied on sending IMs to users to notify them when their Town Hall questions are answered, we frantically tried to figure out the problem but ended up having to remove the feature on the day, which was disappointing.</p>
<h2>The presentation</h2>
<p>Having successfully created the bot and client extension it was up to me to present the idea to the room and make a compelling story. As we were down to the wire for implementation we didn’t spend a lot of time preparing a slide deck or rehearsing so I wasn’t feeling all the confident having seen other polished presentations.</p>
<p>Fortunately the concept is relatable to almost anybody, so to tell a story about the impact of the idea isn’t the hard part, however the technical presentation wasn’t flawless. I predicted that PowerPoint would try to extend the display of the laptop and thus make it harder to then do a live demo so I turned off that “automatically extend my display” setting, only to find that didn’t work and the display was extended anyway :( So I had to flail around moving windows to the extended display so everyone could see => that’s not slick and despite my best effort to continue talking, not a great experience!</p>
<p>All in all the presentation went OK, everything worked that should have worked and the audience didn’t look bewildered or bored afterward!</p>
<h2>Grand finale</h2>
<p>MindLink entered the hackathon to get some more exposure to the Symphony APIs and strengthen our relationship with Symphony, but I am also competitive so there was also a part of me vying to win one of the categories ;)</p>
<p>We were up against some strong competition, everybody produced something that addressed a real need in a business area - whether that’s trading, dev-ops or client engagement. However, we had a couple of unique takes on the hackathon:</p>
<ol>
<li>
<p>We built for the whole business, not a specific set of users - anybody in any part of the business can be involved in a town hall meeting, there’s no specific expertise required and it’s fundamentally about getting everybody in the company getting their questions answered. I strongly believe that it has a huge business impact and one that is not easily quantified since it involves emotions and relationships and complex community systems.</p>
</li>
<li>
<p>We leveraged two independent APIs and tied them together for a single user experience, using the Symphony identity and authentication model to have a mechanism that didn’t require external dependencies to authenticate a user.</p>
</li>
</ol>
<p>We are very proud that our efforts were rewarded with second place for the <em>Most Cutting-Edge Technical Development</em> category!</p>
<p>Thank you to my fellow engineers - Dimitri and Jamie - and to Symphony and BNP Paribas for hosting the event!</p></content:encoded></item><item><title><![CDATA[Visualising Skype for Business Persistent chat data]]></title><description><![CDATA[Persistent Chat allows the Skype For Business users to create topic-based discussion rooms that persist over time. This feature allows users…]]></description><link>https://engineering.mindlinksoft.comvisualising-skype-for-business-persistent-chat-data</link><guid isPermaLink="false">https://engineering.mindlinksoft.comvisualising-skype-for-business-persistent-chat-data</guid><pubDate>Thu, 09 Aug 2018 17:52:00 GMT</pubDate><content:encoded><p>Persistent Chat allows the Skype For Business users to create topic-based discussion rooms that persist over time. This feature allows users within an organization to discuss either casual or business related subjects in chatrooms dedicated for a desired purpose. Logically, a lot of business relevant data for an organization is generated in those chatrooms so it would be great if we could extract that information and visualise it in some modern and cool charts, wouldn’t it? </p>
<h1>Proof of concept</h1>
<p>In this post we are going to show how easy it is to use Power Bi tool and to visualise some interesting statistics about the Persistent Chat usage in one of our dev environments. The goal with this exercise is to be able to display the 5 top users for any desired chatroom in a classic bar chart. </p>
<h1>Enter Microsoft Power BI</h1>
<p>Microsoft Power BI is a business intelligence cloud service that provides business users with tools for aggregating, analysing and visualising data. It did not take too long to get used to its simple interface as it is quite similar to other widely used Microsoft products.</p>
<p>One great thing about this service is that it is possible to get data from multiple sources. For instance: </p>
<ul>
<li>File based data such as JSON or XML</li>
<li>Datatabase data like SQL Server, Oracle, MySQL and many more systems.</li>
<li>Azure services such as SQL Data Warehouse, Azure HDInsight or Azure Data Lake Store.</li>
<li>and many other online services like Facebook, SalesForce or MailChimp.</li>
</ul>
<p>Another very good aspect about Power Bi is that the user has a very wide range of clean, neat and customisable charts to choose from. </p>
<p>Bar, Column, Stacked, Clustered, Waterfall, Donut, Filled Map, Gauge, HeatMap, Word Cloud, amongst many others, can be easilly selected to represent the data that you want to visualise. Not surprisingly, many more type of charts can be downloaded from the Marketplace.</p>
<p>There are two different versions: professional and free. The professional version has a paid subscription attached to it and it offers more storage, more frequent data refresh cycles and much more streaming data consuption. </p>
<h1>Persistent Chat Backend</h1>
<p>Going back to Persistent Chat for Skype For Business, chat room and user data is persisted in a SQL Server database. Let’s look at some important tables for Lync Group Chat 2010, one of the predecessors of Skype For Business Persistent Chat, which has a very similar structure to Skype For Business 2015 Persistent Chat.</p>
<ul>
<li><em>tblChat</em> - contains all the messages sent in chatrooms.</li>
<li><em>tblNode</em> - contains all the chatrooms and their respective categories that the chatrooms might be associated with. </li>
<li><em>tblPrincipal</em> - contains the information about all the users that are enabled for Persistent Chat and the AD groups that they potentially are associated with.</li>
<li>few more dozens of tables but they are out of scope for our exercise.</li>
</ul>
<p>Shall we start doing our little proof of concept? </p>
<h1>Preparing the DataSet and Relationships</h1>
<p>Let’s start by opening the PowerBi Desktop application, which you can easilly download for free from the Microsoft website. First thing to do is to authenticate against it with an Office365 account.</p>
<p>After authenticating the user is guided to the start page which has a form to create a new project.</p>
<p>As expected the user is prompted with a menu that allows it to choose where to get its data from. In this case we want to get the data from the Persistent Chat database in our Skype For Business 2015 dev environment. After selecting <em>SQL Server database</em> option the application ask the user to fill in the server details and the database name. In addition it is required to specify which Data Connectivity mode we prefer:</p>
<ul>
<li>
<p>Import - extracts the data from your database and pulls it into the Power BI Desktop :</p>
<ul>
<li>Gives you the full suite of transformation and data manipulation in the Desktop.</li>
<li>Consumes and pushes the data into the Power BI Azure backend</li>
<li>Can only be refreshed up to 8x a day by setting up a scheduled refresh (In the Service)</li>
</ul>
</li>
<li>
<p>Direct Query - leaves the data in the database and sends queries to pull the information:</p>
<ul>
<li>Limits your ability to manipulate data in the Desktop (removes the Data section).</li>
<li>Leaves the data in the SQL database.</li>
<li>Is <em>live</em>, no need to schedule a refresh.</li>
</ul>
</li>
</ul>
<p>In this example we suggest to select the Import option as we want to be able to do transformation and data manipulation.</p>
<p>Ok so now it’s time to specify the credentials to access the database and then press <em>Connect</em>.</p>
<p>Now that we have tested that we can connect to the Persistent Chat database we are prompted with a navigator that allows the user to select which tables they want to import. We will select the three tables that were mentioned in the previous section: <em>tblNode</em>, <em>tblPrincipal</em> and <em>tblChat</em>. Since <em>tblChat</em> has thousands and thousands of rows it will take a while to load all the rows.</p>
<p>Great! Once all the tables are loaded we will be taken to the project’s main page and on the left hand side there will be 3 icons:</p>
<ul>
<li>Reports icon - where we place the actual charts and buttons for our report.</li>
<li>Data icon - where the data is defined - queries, lookups and so on.</li>
<li>Relationships icon - where the relationships between our entities are outlined.</li>
</ul>
<p>Before doing anything else, maybe now it’s a good time to save your project. </p>
<p>If you click on the Data icon you’ll be able to see imported tables and their respective data.</p>
<p>But first we need to define the relationships between the tables! So click on the Relationships icon.</p>
<p>We can see the three tables that we’ve loaded from our database connection. The goal is to clearly specify that: </p>
<ul>
<li>
<p><em>tblChat</em>’s userId tells us which user sent the respective message so the <em>userId</em> in <em>tblChat</em> will correspond to the <em>prinId</em> in <em>tblPrincipal</em>. In order to establish that relationship just drag the <em>prinId</em> field in <em>tblPrincipal</em> to userId field in <em>tblChat</em>. Now for a given <em>userId</em> in <em>tblChat</em> we will be able to know more about that user - more specifically - the prinName which corresponds to the user name that will be displayed in the chart.</p>
</li>
<li>
<p><em>tblChat</em>’s <em>channelId</em> is referring to what group was the message sent in so in order to get the information about that group we need to drag the <em>tblNode</em>’s nodeId to <em>tblChat</em>’s <em>channelId</em> field. </p>
</li>
</ul>
<p>It should look like this:</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-1a87f.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 63.78482228626321%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACRUlEQVQoz32S7U/aUBTG+wejKKVluriwD2YOZEtwQshgIAhzyzL3luE+LBMMI/GTkgwWILy0lta+3ra0IBrcU/zkh3nSNLf3nt+5z3l6KI7jbdsyNMUgRDVMjVj2eEwsh5hja+zaNr4cLBx3ggN77JimNZ1MNN1wHIe6VDSDmLIk8PyFKErCSFI1TVI0UVIuZU1WVEVVsdZ1YzjkkDwaiYauXwgialGZ14l3bzMfPxTeH2Rf7eykUqnMm0wul9vby2Wz2Xw+n8tld3d3k8nkTjyeTqe9hEwGZ3hTXw5LvV5XFIU/jfrKyvLSkm9jY+P09PTs7Ly9iGarFQ6HfT6f3++vVCqNRqPT6fT7/UQiQVV+lS3LRifcoMmywdUAHYlGiWHIsjy7vr65uXFdNxqN0jQdDAZ5nocH2JzP58VikTqpHMESQkx++DfE0o/Xll++iBg6YGU6hTUTeBaLxRiGAd/r9RR4oKqoWCgUqOpx2XEmjuMO+831NXr90crzSAQYjlH+9vZ2Npttb2/fwVArLwJF9/f3PXgM1x2XG7SePlkFvLn5TJJEZEiShLcgCFtbW8wi+t7NCjYJIfASPX83LQvK+UGLYQIsy4ZCobX7cbcJuNvtgsREQJonGzAGAQ8MY4I00pAavB/sIiD7DiameXV15cHHP78ZBsGEdNvn6Iv9fwDutNsiJkmS0LMnu1b9MZ3ONE33bn4QDgQCHMfhLyIZRpZKJSqViH39VPp8WDwopdDVAzD0l8vlWu13tXpSr9fj8fg/ccI0KMOvYv8AAAAASUVORK5CYII='); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="alt text"
title=""
src="/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-fb8a0.png"
srcset="/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-1a291.png 148w,
/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-2bc4a.png 295w,
/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-fb8a0.png 590w,
/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-526de.png 885w,
/static/EntityRelationships-7eba4701c8b856839e8260901dd098e4-1a87f.png 1041w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<h1>Creating the report</h1>
<p>Now that the data is there we can start creating the report which will include a chart and a dropdown to select the chatroom that we wish to see the top 5 users that have sent more messages. First thing to do is to rename the the page for that report from <em>Page 1</em> to <em>Top 5 Users in Chatroom</em>.</p>
<h2>Adding the slicer</h2>
<p>By pressing the <em>Reports</em> icon we are taken to the area where we can drop the slicer that will contain all the chatrooms in our Persistent Chat environment. The slicer can be formatted to look like a dropdown.</p>
<p>The <em>Visualizations</em> navigator has a list of predefined objects so we need to press the slicer icon.</p>
<p>There are several properties that need to be filled so that the slicer displays all the chatrooms in a dropdown.</p>
<p>First the filters:</p>
<ul>
<li>Field: Drag the <em>nodeName</em> field from the <em>tblNode</em> table.</li>
<li>
<p>Filters: </p>
<ul>
<li>
<p>Page level filters: This affects which rows will be shown in the dropdown.</p>
<ul>
<li>drag the <em>disabled</em> field from <em>tblNode</em> here and tick the <em>False</em> box as we only want to show chatrooms that are enabled.</li>
<li>drag the <em>nodeType</em> field from <em>tblNode</em> here and tick the <em>False</em> box as we only want to filter out chatroom categories from the eligible chatrooms.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>And then the Formatting:</p>
<ul>
<li>First, click on the paint roller icon!</li>
<li>
<p>Here you can define the visual aspects of the slicer.</p>
<ul>
<li>You can edit the title by switching the <em>Title</em> on.</li>
<li>Or edit edit the <em>Header</em>.</li>
<li>and change other properties as you please :)</li>
</ul>
</li>
</ul>
<p>To behave like a dropdown instead of a list hover the slicer’s header and there is the possibility to choose between <em>list</em> or <em>dropdown</em>.</p>
<p>Now you should be able to see the dropdown with all the valid chatrooms!</p>
<h2>Adding the Clustered Column chart and linking it with the dropdown</h2>
<p>It’s time to add the chart that will show us the top 5 users for a chatroom!</p>
<p>The Clustered column chart will do just fine so in order to add it to our report simply press the report page and then click on the corresponding icon and place it next to the dropdown. </p>
<p>Ok so now it is time to configure the chart! </p>
<p>Our first goal is to link the chart with the dropdown so that every time we choose a different chatroom the chart component displays the top 5 users for the selected chatroom.</p>
<p>In order to achieve this we need to :</p>
<ul>
<li>
<p>Highlight the slicer component.</p>
</li>
<li>
<p>Select the Format tab (in between Help and Data/Drill tabs). </p>
</li>
<li>
<p>Switch on Edit Interactions.</p>
</li>
<li>
<p>Filter controls are represented by the funnel icon on top of each component. </p>
<ul>
<li>Initially all the Filter icons are selected.</li>
</ul>
</li>
<li>
<p>Highlight both components and ensure the filter controls are selected for both.</p>
</li>
<li>
<p>Now we need to configure the chart accordingly:</p>
<ul>
<li>
<p>Highlight the chart component and check the fields section on the right hand side.</p>
</li>
<li>
<p>Filters :</p>
<ul>
<li>
<p>Legend: </p>
<ul>
<li>This will determine what will be shown in the X axis. Since we want to display a bar for each of the top 5 users in the chatroom this has to show the user name. Don’t forget that we have established the relationship between <em>tblPrincipal</em> and <em>tblChat</em>. So for each <em>tblChat</em> row there is a userId associated with a message sent and that is linked with the respective user in <em>tblPrincipal</em>. To show the sender’s user name we simply need to drag the prinName from <em>tblPrincipal</em> to the Legend data field. We can rename the what is shown in the chart by clicking the arrow next to prinName and by clicking on <em>Rename</em>.</li>
</ul>
</li>
<li>
<p>Value:</p>
<ul>
<li>This will reflect what should be displayed in the Y axis. In our case we want a count of each occurences of a given <em>channelId</em> in the <em>tblChat</em>. This will give us a count of all messages in that chatroom. In order to achieve this we just need to drag the <em>channelId</em> field in <em>tblChat</em> and drop it in the <em>Value</em> data field. Once that is done if you click the arrow next to it some options will appear and the one that we want is <em>Count</em>.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>At the moment you should be able to change the chatroom and see a new graph being drawn every time you select a new chatroom. It is showing the number of messages sent by each user in the given chatroom. Now we want to limit it to only show 5 users and improve the looks of that chart! </p>
<ul>
<li>
<p>Visual level filters:</p>
<ul>
<li>
<p>User (all) </p>
<ul>
<li>Click on <em>Filter type</em> and select <em>Top N</em>.</li>
<li>Next to <em>Top</em> type <em>5</em>.</li>
<li>Now drag <em>chatId</em> from <em>tblChat</em> to <em>By value</em> section.</li>
<li>Click on <em>Apply filter</em> and it should only display the top 5 users in that chatroom!</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>Format :</p>
<ul>
<li>
<p>Here it is where you can change the layout of the chart. For instance you can move the legend to be at the bottom instead of being at the top or you can add a title to any of the axis.</p>
</li>
<li>
<p>More visual aspects can be changed such as the colour of the bars or add some nice labels that indicate the absolute number of messages sent to them. </p>
</li>
</ul>
</li>
</ul>
<h2>Publish the report!</h2>
<p>Now that we have done what we wanted we need to publish the report that we have just done to the cloud! </p>
<p>In order to achieve that go to <em>File</em> and click on the <em>Publish</em> button! </p>
<p>After the <em>success</em> dialog click on the link provided and you will be redirected to the PowerBi portal so sign in using the same credentials as before and you should be able to see this report in your workspace.</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-ea655.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 33.0488750969744%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAIAAACHqfpvAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA4ElEQVQY032QSwrCMBCGvZwX8AIewJUnEF2oZ/AGIi5UqKArFbGK4ELBlYirolRIqEnMNA+nqe3Gx0+YTIb55pGCzaS1AZASIAaw1uRxpRTnXDqhwxh7ZioY885DR2mDF31wPGkkhQmhCHEulJN2QieHEwvsgna8PfUWB4fptAQhJIoi+6EMNkmeDLzLzu8MV935Hp+xg1GUUiHELxiNa3KdtKrNUqU9Wh9zGFcNwxCH/AJnC7smN69eaRTLtf7ykMNYGABwyS+wlEKKJ07FRCzJyZ9tBlP/HNyT/zfG/tULkqWQMQfVfkIAAAAASUVORK5CYII='); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="alt text"
title=""
src="/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-fb8a0.png"
srcset="/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-1a291.png 148w,
/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-2bc4a.png 295w,
/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-fb8a0.png 590w,
/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-526de.png 885w,
/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-fa2eb.png 1180w,
/static/Top5UsersChatroom-22c9b968eaa3647bea9669fcd5201502-ea655.png 1289w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<p>If you want to publish it to the Internet or to embed in a SharePoint or to a Powerpoint simply go to File and those options will be available!
</p>
<h2>Conclusions</h2>
<p>This was a very basic example on how we can combine a powerful visual tool like the Power BI with the Skype For Business Persistent Chat database to show us interesting information about a chatroom usage.</p>
<p>Another good example that I was also working on was to create a word cloud <em>chart</em> that is built of the most used words in a chatroom but unfortunately that is still work in progress as it requires a bit more advanced algorithms to gather the meaningful words present in messages that are sent in a chatroom. </p>
<p>My experience with Power Bi was very positive as I found the tool quite powerful and easy to integrate with existing systems. It allows the user to get data from a long list of data sources and it offers a wide range of different charts that display the information the users need. With the R Script Editor,which I did not cover on this example, the more technical user will feel more empowered to use their scripting abilities to create and use their own Power Bi visuals. </p>
<p>As a quick and last note I am definitely not an expert on Power Bi so if you found something that was wrong or could be explained better please leave a friendly comment :)</p>
<p>Hopefully this article has sparked some curiosity about this tool! </p></content:encoded></item><item><title><![CDATA[Getting started with Python and the MindLink API.]]></title><description><![CDATA[Today, we’ll have a look at how I started coding in Python to work with the MindLink API. Although Python is an extremely popular language…]]></description><link>https://engineering.mindlinksoft.comgetting-started-with-python-and-the-mindlink-api</link><guid isPermaLink="false">https://engineering.mindlinksoft.comgetting-started-with-python-and-the-mindlink-api</guid><pubDate>Mon, 23 Jul 2018 17:10:00 GMT</pubDate><content:encoded><p>Today, we’ll have a look at how I started coding in Python to work with the MindLink API.</p>
<p>Although Python is an extremely popular language, we, or at least I, haven’t used too much of it. When we started investigating the capabilities of the <a href="https://rasa.com/">Rasa Core platform</a> as part of our <a href="../leveraging-rasa-to-build-on-premise-intelligent-chat-bots">larger work around intelligent chat bots</a>, we found out that they have a neat open source API, in Python. The next natural step for us was wanting to leverage the capabilities of the MindLink API (MLAPI for briefness) to provide the bot with voice and ears; while there are ways to use Rasa-Core in a language-agnostic way, we took the chance to dive into a bit of Python programming, beginners level.
This post will document how I went from virtually no Python knowledge to writing a simple bot harness that connects to the MLAPI and sends/receives messages, covering the following topics: </p>
<ul>
<li>How to setup a basic Python dev environment on a Windows machine.</li>
<li>How to create an application representing a bot that connects to the API and exposes methods to authenticate, retrieve and send messages.</li>
</ul>
<p>What we will not cover here is:</p>
<ul>
<li>Setup of your MindLink API infrastructure.</li>
<li>Provisioning of user, agents and channels.</li>
</ul>
<p>You probably have noticed by now that the assumption is that you have some degree of familiarity with the fundamental concepts of the API - for more information please do visit our <a href="https://wiki.mindlinksoft.com/tiki-index.php?page=MindLink+API#API_Developers_reference">comprehensive developer reference wiki</a>, and of some fundamental programming concepts. </p>
<p>Also, in case it wasn’t clear enough from the blob above, <strong>I am no python expert</strong> - so if you notice anything wrong or that could be done better please do feel free to leave a comment!</p>
<p>By the way, you can find the code we’re writing here and more <a href="https://github.com/mindlink/api-samples/tree/master/python-sample">at our github</a>.</p>
<p>Now, without any further ado:</p>
<h2>1 Set up your dev environment</h2>
<p>For briefness, the assumption is that we will configure our environment on a Windows machine. Using Linux or OSX will not be too dissimilar.</p>
<h3>1.1 Install Python</h3>
<p>First thing is to have a local installation of Python. Go to <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a>, and download the latest stable version (3.6.5 at the time of writing). Run the installer, and make sure you tick the “add to PATH” option. This will ensure that you can run the Python interpreter, pip, etc. from any folder.</p>
<h3>1.2 Install your IDE of choice</h3>
<p>While IDEs are like pizza toppings, everyone has their favourites and they are all great (except for pineapple), I have chosen to use <a href="https://code.visualstudio.com/">Visual Studio Code</a>. It’s what we use for our client side TS/React development, it’s a great editor and a fantastic project. Simply download and run the installer.</p>
<h4>1.2.1 Install the Python extension for VS Code</h4>
<p>This will allow us to run the debugger from VS Code, set breakpoints, leverage autocompletion and more. Simply launch VS Code, go to the extensions tab (Ctrl-Shift-X) and search for Python, find the ms-python.python extension and install it. Restart VS Code to make the extension load.</p>
<h3>1.3 Install Python Requests library</h3>
<p>As I am sure you know, MLAPI is a REST api, meaning requests are made using HTTP. While Python has standard libraries that allow for HTTP handling, <a href="http://docs.python-requests.org/en/master/">requests</a> is easy to use, widespread, comes highly recommended and is, according to them,“is the only Non-GMO HTTP library for Python, safe for human consumption.”, whatever that means.</p>
<p>To install requests, simply run “pip install requests” in a cmd/powershell or VSCode terminal window.</p>
<p>We’re now ready to code!</p>
<h2>2 Get your details!</h2>
<p>Before we start writing code we need to know few details about the MindLink API deployment that we’re going to use.</p>
<ul>
<li>The API location URL, i.e. <a href="http://mlapi.company.com:X">http://mlapi.company.com:X</a>.</li>
<li>The agent ID for the agent that we will use.</li>
<li>The username and password for the user backing the agent.</li>
<li>The ID of a channel(s) that the agent is provisioned upon.</li>
</ul>
<p>Again, if you are unsure of what any of those terms mean please do refer to our wiki.</p>
<p>We can finally start writing some code! Find some place where you’ll write your files to, open your IDE, create a new file and off we go!</p>
<h2>3 Getting a token</h2>
<p>First thing we have to do is to get a token. This is done by performing a post request onto the authentication service, specifically on the Authentication/V1/Tokens resource, providing agent ID, username and password as the request’s payload. In our file, let’s define a function “authenticate” that takes in the URL of the service, agent ID, username, password and returns us the token.
The function will:</p>
<ul>
<li>Build the URL of the service.</li>
<li>Post the request to the service endpoint, with the necessary payload.
As a final note, to make the result easier to handle, we will specify via headers that we want the MLAPI server to return us JSON. This will make data very easy to deserialize, as the requests library has in-built support for JSON.</li>
</ul>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> requests
<span class="token keyword">def</span> <span class="token function">authenticate</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> host<span class="token punctuation">,</span> username<span class="token punctuation">,</span> password<span class="token punctuation">,</span> agent<span class="token punctuation">)</span><span class="token punctuation">:</span>
request_url <span class="token operator">=</span> <span class="token string">'{}/Authentication/V1/Tokens'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>host<span class="token punctuation">)</span>
response <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>request_url<span class="token punctuation">,</span> json <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'Username'</span><span class="token punctuation">:</span> username<span class="token punctuation">,</span>
<span class="token string">'Password'</span><span class="token punctuation">:</span> password<span class="token punctuation">,</span>
<span class="token string">'AgentId'</span><span class="token punctuation">:</span> agent
<span class="token punctuation">}</span><span class="token punctuation">,</span> headers <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'Accept'</span><span class="token punctuation">:</span> <span class="token string">'application/json'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre>
</div>
<p>Let’s analyse what is going on in this code block.
The first line imports the requests library that we’ve previously installed on our environment, allowing us to use it in the scope of this file.</p>
<p>We then define a function that takes in the aforementioned parameters.
The function begins by building the full URL of our request by appending the static part of the request address (the “/Authentication/V1/Tokens” bit) to the dynamic part (the provided protocol/server/port). </p>
<p>After this, we create and forward a post request. The first parameter is the URL we just created, and then we provide the request payload which is automatically serialized in JSON by the requests library - all we have to do is to match the attributes to the parameters.</p>
<p>The next parameter for our requests is the headers. We specify that we want to accept JSON, which will cause the API server to JSON encode the response data.</p>
<p>Do note that the request is immediately fired once the .post() method is called, hence why we assign its result to the “response” parameter.</p>
<p>Provided that the request was completed succesfully, our response will contain a JSON-encoded API token. To “extract” it, we simply use the .json() method onto the response object.</p>
<p>OK, how do we actually use what we just wrote though? Below the previous code block, let’s add:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python">token <span class="token operator">=</span> authenticate<span class="token punctuation">(</span><span class="token string">'http://apiserver.domain.com:8081'</span><span class="token punctuation">,</span> <span class="token string">'userdomain\\username'</span><span class="token punctuation">,</span> <span class="token string">'password'</span><span class="token punctuation">,</span> <span class="token string">'agentId'</span><span class="token punctuation">)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'Here`s my token!'</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span></code></pre>
</div>
<p>We can now run our program. 2 ways of doing this, either </p>
<ul>
<li>open a cmd/powershell/terminal instance in the folder where our file resides and run python my_file.py</li>
</ul>
<p>
<a
class="gatsby-resp-image-link"
href="/static/cmd_run_py-d4ae5f47947f8767601cce6b7a7e15e4-87d31.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 59.241706161137444%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAA7DAAAOwwHHb6hkAAABCElEQVQoz+2STU7DMBCFHTt2FMEmBFdqkoKRIA03QJSGLsslOQASG35aICSBtlFaLvWwvYEFiAq6ZPFp3nispyfNkMura9xOJ7iZ3uOxLDBvGyxWLWa6Gt28LTFftlpXaFa11WZevtYoXioUdYmn6hl3DxMUsxrkMEsRdyMopbDX60HtK/TTvu0PNFl2jPQoRRJLRN0dxFGidYKOlAjDEEEQQO5KcJdjfDEGEVs+GGNgrgtKKTjnEELYat5t7wkQQn7kdHgGQoW71meD4zhfYsKYeT46B2EeX9vwOyijtg5HuU7ouf+GfzbcxFLYJ0OxgYQO/Tgbvu2Dcn3UOqkx/w3c96zhST7AO2mBLFiZfgKrAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="Running Python via PS"
title=""
src="/static/cmd_run_py-d4ae5f47947f8767601cce6b7a7e15e4-fb8a0.png"
srcset="/static/cmd_run_py-d4ae5f47947f8767601cce6b7a7e15e4-1a291.png 148w,
/static/cmd_run_py-d4ae5f47947f8767601cce6b7a7e15e4-2bc4a.png 295w,
/static/cmd_run_py-d4ae5f47947f8767601cce6b7a7e15e4-fb8a0.png 590w,
/static/cmd_run_py-d4ae5f47947f8767601cce6b7a7e15e4-87d31.png 844w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<ul>
<li>or by leveraging the Python running and debugging capabilities of our IDE. In VSCode simply go to the debug menu and press “Start (Without) Debugging”. A dropdown menu to choose the running environment will open, and you can safely choose “Python”. In the terminal tab you’ll see the command VSCode actually runs, and hopefully our output.</li>
</ul>
<p>
<a
class="gatsby-resp-image-link"
href="/static/vscode_run-d1de836a4ba530d11766017a22e6c694-ef9ea.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 54.270833333333336%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABZUlEQVQoz52RaW7CQAyFcwIQEoVss2WZLISGXf1X0t6g96FQEUpv/eoMYpWq0v745IznPXvsWOvtJ9brDVard2w2H6h3O2y3taGu76PR7si333/BkqFGr9cHZwzc8+C7LrrdLlqt1p9ot9vodDqwZvMFlJIo8xSz8hFaazqrf2PleQYhJOI4hpISgjOCX8EJRhP8RqOjgjnCMERKL0viCFGgEFDhS6QQRnwPVpZl8H3f7JAxDv+n7hewm29G/qPOFGwSx/GkoPH4GdNZ3GBywtxL2ltAE8pmXYRVPJbQ5QT5dIFi/oTBeAKVDYgCKh9CJNQwicF0ciBNwMPmTJPFKaKiRDqaGo/UKe2wGCKINXSWGyIyKeqogtBEIRW9RBygn3eIZ3yzJn7SWGkcwus/wHf68O0eGEXu2td4DmFf4FzknBOMtFZWvUFMniHnr5CzFzjjCv3REvYdNDp3vISYVeDTyvi/AcmcRkOwkYvvAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="VSCode debugger"
title=""
src="/static/vscode_run-d1de836a4ba530d11766017a22e6c694-fb8a0.png"
srcset="/static/vscode_run-d1de836a4ba530d11766017a22e6c694-1a291.png 148w,
/static/vscode_run-d1de836a4ba530d11766017a22e6c694-2bc4a.png 295w,
/static/vscode_run-d1de836a4ba530d11766017a22e6c694-fb8a0.png 590w,
/static/vscode_run-d1de836a4ba530d11766017a22e6c694-526de.png 885w,
/static/vscode_run-d1de836a4ba530d11766017a22e6c694-fa2eb.png 1180w,
/static/vscode_run-d1de836a4ba530d11766017a22e6c694-08f6a.png 1770w,
/static/vscode_run-d1de836a4ba530d11766017a22e6c694-ef9ea.png 1920w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<h3>3.1 Let’s handle some errors</h3>
<p>One of the problems with the code we just wrote, is that it deals poorly with errors. It always tries to deserialize the response, which can fail horribly if the request was not successful. So let’s add some error handling/reporting.</p>
<p>Instead of just returning the response.json(), we can add a conditional statement that will try to deserialize and return the token <strong>only</strong> if the server has given us a 200 response code (OK). We can also take this a step further by making the function print to console the status code and reason of an erroneous response. The second part of our function will now look like:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"> <span class="token keyword">if</span> response<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">:</span>
<span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">print</span> <span class="token punctuation">(</span><span class="token string">'Something went wrong!'</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>status_code<span class="token punctuation">,</span> response<span class="token punctuation">.</span>reason<span class="token punctuation">)</span></code></pre>
</div>
<p>If we now run our program, the output will look a lot less scary and more useful, even when something goes wrong!</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-59922.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 54.06673618352451%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABbklEQVQoz5WRWU7DQBBE5wZEQnFMmNXr2IlDWBI4ABJwAk6SxPmAqxc1kxiExPrxVO5Rd/Vi0e/32PV7bDYbbLdb7BkH+r7/M7tdH2teX14hXFFhkqawRsNqBS0lkiTBaDTCycko6m8MeePxGOJmtYK1FovW42rRoSzLGDvn/kWoybIMYtY0MAy8b1AWBYwx0JpQldZQSkV0+Cb6G0JOqBUNDfMsR+trNFWBuixQHcnZObOG5zA/mg2GQWnYHsZ1AccuQxI7Uj/iI2pQFXMOG8j3TUTjPeT5OYsPBtZ8MYFhcXh/J5zEQlGty5DlPFUYhoOJbnmJ9nqN+eqO3MIvr6D8HKrpoNoFoc5njFvoqqLW0DxRQNIs5+2b7gJZVcPRWMy6BUrfRnI+2qKEsu4zZoBT6cDxhxHJNaUMK+uI8IXDNDmFSscRfZbATCf/Qk9TMoEior5/hlo/QN89RdKbByTXfyfkq9tHyPUjDOvfAGDsRPDvOeTQAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="With some error handling"
title=""
src="/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-fb8a0.png"
srcset="/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-1a291.png 148w,
/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-2bc4a.png 295w,
/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-fb8a0.png 590w,
/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-526de.png 885w,
/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-fa2eb.png 1180w,
/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-08f6a.png 1770w,
/static/auth_with_error_handling-aa594c2505e2e5f0214d436046820a10-59922.png 1918w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<p>It’s now obvious that the issue here is to do with wrong credentials.</p>
<h2>4 Getting messages</h2>
<p>Now that we have a token, we can use it to do actual collaboration!
Let’s write a function that will retrieve the last X messages from a channel.</p>
<p>In our existing file:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">get_messages</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> token<span class="token punctuation">,</span> channel_id<span class="token punctuation">,</span> count<span class="token punctuation">)</span><span class="token punctuation">:</span>
request_url <span class="token operator">=</span> <span class="token string">'{}/Collaboration/V1/Channels/{}/Messages'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> channel_id<span class="token punctuation">)</span>
response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>
request_url<span class="token punctuation">,</span>
params <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'take'</span><span class="token punctuation">:</span> count<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
headers <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'Accept'</span> <span class="token punctuation">:</span> <span class="token string">'application/json'</span><span class="token punctuation">,</span>
<span class="token string">'Authorization'</span><span class="token punctuation">:</span> <span class="token string">'FCF {}'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> response<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">:</span>
<span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">print</span> <span class="token punctuation">(</span><span class="token string">'Something went wrong!'</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>status_code<span class="token punctuation">,</span> response<span class="token punctuation">.</span>reason<span class="token punctuation">)</span></code></pre>
</div>
<p>As you can see, the method is quite similar to the “authenticate” one that we wrote previously; notable differences are:</p>
<ul>
<li>The request URL, we now want to access the <a href="https://wiki.mindlinksoft.com/tiki-index.php?page=MindLink+API#Collaboration_REST_API">Collaboration service</a>, and in it the <a href="https://wiki.mindlinksoft.com/tiki-index.php?page=MindLink+API#Channels_id_Messages_resource">Messages resource of a specific channel</a>, identified by its ID.</li>
<li>We are using a GET request, not a POST.</li>
<li>We have to specify a “take” parameter with the number of messages that we want to retrieve.</li>
<li>We have to provide the token as our authorization header.</li>
</ul>
<p>We can now update our code to use the new function. As we have to reuse the host, username, password and agent id values, we can store them in variables, i.e.:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python">host <span class="token operator">=</span> <span class="token string">'http://localhost:8081'</span>
user_name <span class="token operator">=</span> <span class="token string">'domain\\user'</span>
password <span class="token operator">=</span> <span class="token string">'super_secret_password'</span>
agent <span class="token operator">=</span> <span class="token string">'agent_1'</span>
token <span class="token operator">=</span> authenticate<span class="token punctuation">(</span>host<span class="token punctuation">,</span> user_name<span class="token punctuation">,</span> password<span class="token punctuation">,</span> agent<span class="token punctuation">)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'Here`s my token!'</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span>
messages <span class="token operator">=</span> get_messages<span class="token punctuation">(</span>host<span class="token punctuation">,</span> token<span class="token punctuation">,</span> <span class="token string">'chat-room:d343ec64-c867-4edf-9daf-84f25a3c8ed4'</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>
<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'here are my messages!'</span><span class="token punctuation">,</span> messages<span class="token punctuation">)</span></code></pre>
</div>
<p>If we now run the program, the output should look something like:</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-2c446.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 55.67542213883677%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABL0lEQVQoz42SWXLDIAyGuUfYMZsTUjtO0mnvf6+/Eo4zaaed+uFDSAihBWGtQy0Vp1oQhwHBe4QQYIyBUupftNZdeu/6XeFoaa2hpIhIgQIfkHTOPS/shZMQxlqMJSNSIO9sP5BSfnt9b5ZrQFpSTKg5UUDXjczmtDe7zVfElHBpR7QcweV7gsvtTjv4GVjwILjkQhkO3DvLw5DQ/OIOetlbiwgRhohEWZZCUybJPVWMYQwkPSAfsutkZ53PpTZwdD+PR1jnO+J8WXD7+Oxc7u9o1zvOt3ecSJZpRpknVJJ1Ic60nxeM8xWpTchvZJsWZLInCprqCDFQyZky4yxjjH0wK5bKX+k6/ddXvcuHrrV6DlPwF5GHw4o8rP3YUA/kL6hXH/X0FcoF7Mb+YXvhC80CMFSZfTQKAAAAAElFTkSuQmCC'); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="Get messages result"
title=""
src="/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-fb8a0.png"
srcset="/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-1a291.png 148w,
/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-2bc4a.png 295w,
/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-fb8a0.png 590w,
/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-526de.png 885w,
/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-fa2eb.png 1180w,
/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-08f6a.png 1770w,
/static/get_messages-8a8a4ce52af32aa104e553b1759e93bd-2c446.png 2132w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<p>This looks perhaps a bit confusing…time to look at those messages using the debugger. Set a breakpoint on the line where we print the messages, launch the application and inspect the “messages” object.</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/messages-59cfc6a26e7ba1545eef50766ecd0432-e19cf.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 590px; margin-left: auto; margin-right: auto;"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 67.17373899119295%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABiklEQVQ4y42T6W6jMBSFeY02eAMvGDBL2BKkGc38Gc37v9DptZNWUduk+fHpWl4OdzlkQgjYyiOEgLqq0NUera/QVA62LKALQhtCoygUGGN3yfMcmSTBkh6O44imrlGoyyNOCM4Rz6WUifjxdEb798jeL3Ztg8raJKIkiZAwvwrcEh89yvIqKGF1idpoeEPRalgSV0p9K/ZzhlJRHxu0bQvvPRT1LWYnbsp9RuxD0DmH2hl0TqOhDGMf4xB0qVM0xiTRpwQLyuY4HWm6VZpooS49FdcLcXJ5frgRYw/JYp8M2cISJmYVo7FwZCFrXRrOZUASh0O0Bn9IFr0VgkfXGIxdhdA6zNOK/fQb87zitJ2xbTvGYSAXkMU0Jz+yu2S6jIIafZAYgkJHcTrO+LP+xTKdMPYjpmFC35Pxa5bwnn2sP5NxLqiUPJUTeXk9INQD/u3/sa871mnGeVnJpz1enylZiPdG86s1crRNj335hW3ZsJ/PJLrQXgDjPw8m+/I73azZJ+IDxh7b5g2lJmuCfA67TwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
>
<img
class="gatsby-resp-image-image"
style="width: 100%; height: 100%; margin: 0; vertical-align: middle; position: absolute; top: 0; left: 0; box-shadow: inset 0px 0px 0px 400px white;"
alt="Messages"
title=""
src="/static/messages-59cfc6a26e7ba1545eef50766ecd0432-fb8a0.png"
srcset="/static/messages-59cfc6a26e7ba1545eef50766ecd0432-1a291.png 148w,
/static/messages-59cfc6a26e7ba1545eef50766ecd0432-2bc4a.png 295w,
/static/messages-59cfc6a26e7ba1545eef50766ecd0432-fb8a0.png 590w,
/static/messages-59cfc6a26e7ba1545eef50766ecd0432-526de.png 885w,
/static/messages-59cfc6a26e7ba1545eef50766ecd0432-fa2eb.png 1180w,
/static/messages-59cfc6a26e7ba1545eef50766ecd0432-e19cf.png 1249w"
sizes="(max-width: 590px) 100vw, 590px"
/>
</span>
</span>
</a>
</p>
<p>Now it makes a bit more sense. We have an array with 2 messages, and if we expand the first node we can see all the properties of each message object. We can use what we have just learned to prettify the output of our application. We’ll iterate through each message and print some properties. Simply replace the line printing messages for:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token keyword">for</span> message <span class="token keyword">in</span> messages<span class="token punctuation">:</span>
<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'message ID: '</span><span class="token punctuation">,</span> message<span class="token punctuation">[</span><span class="token string">'Id'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">' Alert? '</span><span class="token punctuation">,</span> message<span class="token punctuation">[</span><span class="token string">'IsAlert'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">' Sender '</span><span class="token punctuation">,</span> message<span class="token punctuation">[</span><span class="token string">'SenderId'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">' Text '</span><span class="token punctuation">,</span> message<span class="token punctuation">[</span><span class="token string">'Text'</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre>
</div>
<p>Notice how we’re displaying the text instead of using the message parts. Message parts are a very powerful modelling tool that remove the coupling between what a part of a message looks like and what it represents. They are a more advanced topic though, and we’ll come back to them in another episode.</p>
<h2>5 Sending messages</h2>
<p>We are now ready to send our first message! To do that:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">send_message</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> token<span class="token punctuation">,</span> channel_id<span class="token punctuation">,</span> content<span class="token punctuation">,</span> is_alert<span class="token punctuation">)</span><span class="token punctuation">:</span>
request_url <span class="token operator">=</span> <span class="token string">'{}/Collaboration/V1/Channels/{}/Messages'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> channel_id<span class="token punctuation">)</span>
response <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>
request_url<span class="token punctuation">,</span>
json <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'Text'</span><span class="token punctuation">:</span> content<span class="token punctuation">,</span>
<span class="token string">'IsAlert'</span><span class="token punctuation">:</span> is_alert
<span class="token punctuation">}</span><span class="token punctuation">,</span>
headers <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'Accept'</span> <span class="token punctuation">:</span> <span class="token string">'application/json'</span><span class="token punctuation">,</span>
<span class="token string">'Authorization'</span><span class="token punctuation">:</span> <span class="token string">'FCF {}'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> response<span class="token punctuation">.</span>status_code <span class="token operator">!=</span> <span class="token number">200</span><span class="token punctuation">:</span>
<span class="token keyword">print</span> <span class="token punctuation">(</span><span class="token string">'Something went wrong!'</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>status_code<span class="token punctuation">,</span> response<span class="token punctuation">.</span>reason<span class="token punctuation">)</span></code></pre>
</div>
<p>As you have probably noticed, the method doesn’t look too different from the previous ones; in fact you can probably see a pattern beginning to take shape! Anyway, the way it works is pretty self explanatory; do however note how we use a POST request!</p>
<p>Now, to call him from our application we’ll move the chatroom ID to a variable to the previous block to avoid duplication, and then wel’ll add a call to the method:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token comment"># snip</span>
agent <span class="token operator">=</span> <span class="token string">'agent_1'</span>
chat <span class="token operator">=</span> <span class="token string">'chat-room:d343ec64-c867-4edf-9daf-84f25a3c8ed4'</span>
<span class="token comment"># snip</span>
<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'sending a message'</span><span class="token punctuation">)</span>
send_message<span class="token punctuation">(</span>host<span class="token punctuation">,</span> token<span class="token punctuation">,</span> chat<span class="token punctuation">,</span> <span class="token string">"hello world"</span><span class="token punctuation">,</span> <span class="token boolean">True</span><span class="token punctuation">)</span> <span class="token comment">#True for alerts, False for regular messages</span></code></pre>
</div>
<p>That easy!</p>
<h2>6 Getting events</h2>
<p>The final piece of this initial puzzle is how to get events. Events are the mechanism that the MindLink API uses to relay in real time what is happening on channels. Again, for more information <a href="https://wiki.mindlinksoft.com/tiki-index.php?page=MindLink+API#Events_resource">check the dedicated section on the Wiki</a>.</p>
<p>The method to get message events looks something like:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">get_events</span><span class="token punctuation">(</span>host<span class="token punctuation">,</span> token<span class="token punctuation">,</span> channel_id<span class="token punctuation">,</span> last_event_id<span class="token punctuation">)</span><span class="token punctuation">:</span>
request_url <span class="token operator">=</span> <span class="token string">'{}/Collaboration/V1/Events'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>host<span class="token punctuation">)</span>
parameters <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'last-event'</span><span class="token punctuation">:</span> last_event_id<span class="token punctuation">,</span>
<span class="token string">'types'</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'message'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token string">'channels'</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>channel_id<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token string">'regex'</span><span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span>
<span class="token string">'origins'</span><span class="token punctuation">:</span> <span class="token string">'remote'</span>
<span class="token punctuation">}</span>
response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>
request_url<span class="token punctuation">,</span>
params <span class="token operator">=</span> parameters<span class="token punctuation">,</span>
headers <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token string">'Accept'</span> <span class="token punctuation">:</span> <span class="token string">'application/json'</span><span class="token punctuation">,</span>
<span class="token string">'Authorization'</span><span class="token punctuation">:</span> <span class="token string">'FCF {}'</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> response<span class="token punctuation">.</span>status_code <span class="token operator">!=</span> <span class="token number">200</span><span class="token punctuation">:</span>
<span class="token keyword">print</span> <span class="token punctuation">(</span><span class="token string">'Something went wrong while getting events!'</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>status_code<span class="token punctuation">,</span> response<span class="token punctuation">.</span>reason<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token keyword">return</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre>
</div>
<p>Note how we need to keep track of the last event ID - that can be extracted by simply iterating through the events and comparing their id; this could be done in the get_events function our outside of it. A function to do that could look like:</p>
<div class="gatsby-highlight">
<pre class="language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">get_latest_event_id</span><span class="token punctuation">(</span>events<span class="token punctuation">)</span><span class="token punctuation">:</span>
last_event_id <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span>
<span class="token keyword">for</span> event <span class="token keyword">in</span> events<span class="token punctuation">:</span>
event_id <span class="token operator">=</span> event<span class="token punctuation">[</span><span class="token string">'EventId'</span><span class="token punctuation">]</span>
<span class="token keyword">if</span> event_id <span class="token operator">></span> last_event_id<span class="token punctuation">:</span>
last_event_id <span class="token operator">=</span> event_id
<span class="token keyword">return</span> last_event_id</code></pre>
</div>
<p>While events can be retrieved individually, the streaming paradigm implies that what we probably want to do is to continuously ask for new requests, i.e. poll. To do that we would need few things:
- keep track of the last event ID, so we only ask for newer events
- introduce a loop with control
- run this event pump in a separate thread, or use asynchronous requests, so that it does not block our bot.
To see a sample of that, everything we discussed here and much much more, do check out our sample <a href="https://github.com/mindlink/api-samples/tree/master/python-sample/sample2">on our github</a>.
The sample introduces a second module that contains all the various API methods that we have seen here in an instantiable class, so that we reduce duplication (no need to provide host and token for each request) and our application logic is less coupled with the API.</p>
<h2>7 Wrapping up</h2>
<p>Hopefully if you have made it this far you’ll have realised how simple it is to begin writing collaborative tools using the MindLink API, even when starting anew with a programming language as we did here. Naturally we have just begun to scratch the surface of the capabilities of the platform, so stay tuned for more!</p></content:encoded></item><item><title><![CDATA[Parallelizing our CI tests]]></title><description><![CDATA[In this second “improving our CI tests” blog we’ll complete our journey by: Enabling parallel NUnit test execution at the text-fixture…]]></description><link>https://engineering.mindlinksoft.comparallelizing-our-ci-tests</link><guid isPermaLink="false">https://engineering.mindlinksoft.comparallelizing-our-ci-tests</guid><pubDate>Thu, 05 Jul 2018 13:10:00 GMT</pubDate><content:encoded><p>In this second “improving our CI tests” blog we’ll complete our journey by:</p>
<ul>
<li>Enabling parallel NUnit test execution at the text-fixture granularity</li>
<li>Fixing some issues in our CI tests stack that prevented parallelisation</li>
<li>Reducing our CI test run time by a further 10%</li>
</ul>
<h2>Background</h2>
<p>The MindLink .NET CI/test stack looks like:</p>
<ul>
<li>TFS 2018</li>
<li>NUnit</li>
<li>Moq</li>
</ul>
<p>See the previous <a href="../improving-our-ci-tests">blog post</a> about how we enabled test project-level parallelism and upgraded to NUnit 3.10.</p>
<h2>Parallelising Take 2</h2>
<p>NUnit 3 now supports running individual tests in parallel. This is at a different level of granularity to the project-level scope supported by the external test runners. We can choose which groups of tests are able to run in parallel by attaching <code class="language-text">[Parallelizable]</code> attributes <a href="https://github.com/nunit/docs/wiki/Parallelizable-Attribute">at the correct level and scope</a>.</p>
<p>This has some interesting implications for your tests. With project-level granularity, your tests still run in the same environment as they would do otherwise - basically just entire isolated environments running in parallel.</p>
<p>Now, with test-level granularity <em>your tests</em> need to make sure that they can run whilst other tests are running. This of course <em>should</em> be the case for correctly designed Unit and in-memory component tests (and correctly designed code under test).</p>
<p>In our previous post, project-level parallelism gave us a 2x speed-up (on a 2-processor build server). In theory, turning on test-level parallelisation isn’t going to give us any greater speed up - there are still 2 cores to perform the synchronously-running tests, regardless of who and how is scheduling them.</p>
<p>However, we decided to look into this anyway because:</p>
<ul>
<li>Unless the project-level distribution of the tests is <em>exactly equal</em> across test projects, there will always be the case where there’s one test project left running after all the others have completed. At this stage, we will benefit from test-level parallelisation.</li>
<li>Developers running the tests individually from Resharper/Visual Studio will benefit from the test-level parallelisation.</li>
<li>We <em>should</em> be able to turn on parallelisation - if our tests are written correctly - so it’s an interesting exercise to try.</li>
</ul>
<h2>Turning on Parrallelisation</h2>
<blockquote>
<p>At this point in the blog I have typed the word “parallel” so many times I’m forgetting how to spell it.</p>
</blockquote>
<p>We need to decide at what level we can enable parallelisation. We have a couple of options, based on where we apply the <code class="language-text">[Parallelizable]</code> attribute, and what scope we define.</p>
<ul>
<li>Per-test - all tests can run parallel with each other</li>
<li>Per-test-fixture - all test fixtures can run parallel with each other, but tests inside run sequentially</li>
<li>Per-namespace - tests in different namespaces can be run in parallel, but tests inside the same namespace run sequentially</li>
</ul>
<p>The answer to the above depends on how you’ve written your tests. I think we’re in the same boat as most NUnit teams in that our test fixtures have state that is reset in each <code class="language-text">[SetUp]</code> method (i.e. we have private fields in each test fixture class). On the up-side, each test depends on that state and that state alone.</p>
<div class="gatsby-highlight">
<pre class="language-c#"><code class="language-c#">[TestFixture]
public sealed class MyTestFixture
{
private Mock&lt;IMyDependentInterface&gt; mockMyDependentInterface;
private MyClass myClass;
[SetUp]
public void SetUp()
{
this.mockMyDependentInterface = new Mock&lt;IMyDependentInterface&gt;();
this.myClass = new MyClass(this.mockMyDependentInterface.Object);
}
[Test]
public void DoingSomethingWhenSomethingDoesSomething()
{
this.mockMyDependentInterface.Setup(...);
Assert.That(
this.myClass.DoSomething(),
Is.EqualTo(Expected));
}
}</code></pre>
</div>
<p>I should point out that the goal with this work is to enable parallelization:</p>
<ul>
<li>With the minimum amount of work/changes/risk possible.</li>
<li>Zero changes to the actual code (i.e. outside of the tests).</li>
</ul>
<p>If we can’t achieve parallelization without either of the above being true, then we’ll abandon the exercise.</p>
<p>This leads me to the conclusion that we can run all test-fixtures in parallel, but tests inside each fixture must be run in sequence. This isn’t to say we can’t change individual test fixtures - or write new fixtures - to support per-test parallelization in future.</p>
<p>So to do this, I apply the <code class="language-text">[Parallelizable]</code> to each of our test projects, at the assembly level. The Scope parameter is instructing that each test-fixture can be run in parallel, but no two tests inside each fixture can run at the same time.</p>
<p><code class="language-text">[assembly: Parallelizable(ParallelScope.Fixtures)]</code></p>
<p>And low-and-behold, when I run chunks of test fixtures in Resharper, I can actually see multiple fixtures running in parallel.</p>
<p>
<a
class="gatsby-resp-image-link"
href="/static/parallel-running-00849fbe03616d34da4f76653ea0a107-6eb6a.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; ; max-width: 547px; margin-left: auto; margin-right: auto;"
>
<span