I hope team Office give Delve a second chance

Photo by Jay Wennington on Unsplash

Office Delve was the first product to use the Office Graph – an attempt to surface the nodes and edges of documents and people to build some intelligence.

Office Graph became Microsoft Graph and found lots of developer love.

Delve had to choose how to continue to exist – I suspect the team looked at the usage of how people that actually used Delve and studied how they used Delve.

Disclaimer

Please note - I have no particular inside knowledge of what Team Office is working on regarding Delve. I just wanted to write this, in 2019, because a good friend asked me - hey do you use Delve, do you like it? I am also NOT implying team Office is cutting Delve - as far as I know, they are maintaining it as it currently is. In fact, if I had known what they are working on - I wouldn’t have been able to write this post.

I do know from SharePoint uservoice - that there seems to be some work happening on taxonomy and metadata. So I figure I should write this blog post now.

So how does Delve work?

So a few years ago, I suspect they settled on what they thought was the killer idea – Delve shows you “people around you” and “documents around you” 

They found that if they could show YOU the documents that PEOPLE AROUND YOU are working on – that must be documents you want to see.

Today, that’s pretty much the only scenario that Delve works really well for - if I am looking for the document that I know my colleague was working on last Friday but I don’t know where it was saved.  Delve will tell me.

There was a second feature of delve - Boards and favourites. But as the product evolved around how it is being used - the boards idea takes a dramatic backseat.

How I think Delve should have worked

it should have been the cross Office 365 #tags management system, powered by Search and Graph. Providing a one-stop definition for folksonomy and taxonomy as both a destination, but also the interface where a process librarian owns and curates their tag collection.

 

There was a secondary idea – Delve gave us “Boards” where people can add documents to a board – think Pinterest, but for documents, and people, and office objects. 

So now imagine #usergroup tag shows you emails, documents and people involved with running the monthly company usergroup.

That’s damn useful. Surface it in SPFx search webpart. Use it to show a most recently added documents list or appear in a mega menu. 

What I think what team Office should have built, is to improve and combine

 

  • Improve Delve Boards and combine

  • SP Document Metadata taxonomy tags

  • SP Folksonomy tags

  • Yammer hashtags

  • Teams hashtags

  • Exchange/Outlook hashtags and categories

  • Tags across Todo, Planner

  • Tags across Stream

 

This way – we can surface a collection of documents explicitly labelled with tags – across the entire company. 

Say, if you tag #induction and this article will automatically appear in an #induction board in Delve.  That would have been more magical. 

Or if an OHS #worksafety email was sent and tagged - it would appear in the Delve Board. 

Appoint specific users to own the boards – so they are the tag librarian making sure unsuitable links is hidden from that tag. They own the boards - so they are the curators. They can decide if objects are automatically included, how the objects are sorted, and which objects should be pinned to the top.

I think this isn’t a simple goal - this is the longer path – requiring Team Office to work on some sort of inter-office tag/hashtag/metadata system.   I don’t believe it is possible to implement this across every product and force everyone to use the same list - but I do believe it is possible to aggregate them at the Search results stage and control collections via Delve Boards.

I think it would give Delve a strong reason and a decent second chance at being a unique Office 365 go to destination.  And tie up a lot of loose ends at the same time.

It’s 2019, my hope with Delve Next

is that we will see Me, then Tags (then People) - and the tags are curated boards of content collected across all of Office 365 via every tag and hashtag system we have.

How to implement Sort with Microsoft Flow in 3 actions within a loop

Photo by Sophie Elvis on Unsplash

Photo by Sophie Elvis on Unsplash

For some reason, Flow (and LogicApps) doesn’t have a built in sort() method.

So after much nudging from Paul Culmsee, I brooded about this and sat down and built one. There’s three actions within a loop, packing quite a lot of tricks, this is how it all works.

Plan

How does it work conceptually?

Build your own Sort in Microsoft Flow

Simplify it to 3 actions within a loop

Extend it to sort objects and beyond.


Defining what we need


Let’s set up some input and outputs - we want to have our initial variable within the variable “initial-array” and we want to have our final sorted result within the variable “sorted-array”

We want to do a simple insertion sort - for the academic - this is an O(n^2) sort. Not the fastest, but simple enough to understand and express with logic expressions.

How does it work conceptually?

For each element in the initial-array, we will loop add it to a final ‘sorted-array’ in a sorted position.

In the first loop, the sorted array would be empty - but as we progress, for each new value we are considering - we would split the sorted-array into two sets of sub-arrays, ones that are smaller than the current value, and ones that are larger than the current value.

Finally, we combine everything back together in a union, set that to be the new “sorted-array” value, and proceed with the next element in the initial-array.

Build your own Sort in Microsoft Flow

So extending our Flow - add a loop over the initial-array.

Within the loop, we want to put the current item into a compose action. (going with the example above, this would be the element ‘4’ )

Add a compose at the end to see the final output of sorted-array.

Cutting sorted-array into lesser half, greater half

We do this in a parallel branch - these two don’t affect each other - add two Filter Array

Left, set the item() of the sorted-array to be <= the current value of the initial-array
Right, set the item() of the sorted-array to be > the current value of the initial-array

Union the arrays together. That is, the left-array, the right array, and in the middle, the current value, but put that inside an array of 1 element with createArray( <current-value> )

Before the apply to each ends, write the union result back into the “sorted-array” that concludes this loop, getting us ready for the next value.

See how it runs:

The result is a sorted-array.

See for example, loop 6, when the value is ‘4’

Clean up and simplify

We can remove some of the blocks to make the sort ‘smaller’

The final result

3 actions within one loop

Sorting complex (JSON) Objects

It is best to build an sort-array like this with numbers to understand how it works, before attempting to tweak it to sort complex object arrays.

Say for example - if we want to sort by the Date of a complex object array (our scenario was an RSS feed) - within the apply to each, we want to isolate the property we want to compare.

Because the current item’s property is used several times - we add the top compose back into the apply to each loop, we can consider the Compose is a kind of temporary variable used to hold the value that we are using to compare with.

We need to compare against the date in the two Filter-Arrays, but we don’t actually use it for the final Union - because we want to union the arrays together, not union the dates together.

And that’s Sort. Complex explanations, but very elegant with 3 actions inside a loop.

Hot-patching our dependencies with patch-package

lego-644x450.jpg

This is also a quick blog post.

Let me tell you the tale of patch-package. Not many people have heard the tale of patch-package. Many prefer to walk the path forked.

Well, the path of patch-package works like this.

About patch-package

Your favourite NPM library has a problem. You need to hot fix it. You send in a PR and it’s stuck in review hell. The poor maintainer is on stress leave and you really don’t want to trouble her/him - they are in Finland, don’t wear shoes, and can’t possibly scale any further.

This is the patch-package path https://www.npmjs.com/package/patch-package

  1. yarn / npm lock the dependency on that specific version so our entire team and the build pipeline gets the same version

  2. npm install patch-package --save-dev

  3. make changes in node_modules against that library, e.g. “package-banana”

  4. npx patch-package “package-banana” - this creates a folder
    /patches/package-banana-version.patch

  5. add “postinstall": "patch-package" to scripts section in package.json

  6. check in / commit everything. Package.json and /patches/package-banana-*.patch

Now when anyone get latest and run npm install, the build chain will automatically grab that version of the library package-banana and apply your patch.

Then it will build as normal. This works great on Azure DevOps (VSTS) too.

When the library package-banana is finally fixed - update the library version and the patch file won’t work against newer versions.

Bad JavaScript. Expected '(' in our webpack eval chunk

This is a quick blog post for resolving an issue this morning from webpack uglified/minified JavaScript. The error happens in IE and Edge, it simply says:

Expected ‘(‘

This was happening in the middle of a webpack minified chunk so the entire block was wrapped inside eval(““)

Steps

  1. Navigate to the end of the webpack chunk, which will tell us the source of this chunk. Scroll past the sourcemap section to the end of the chunk (not the end of the file).

  2. Find the original file that the chunk was created from - now run ESLint over it.
    I didn’t have this handy in the build chain, so I ran it online https://eslint.org/demo/

  3. The error was spotted as there was a catch{} statement - in plain JavaScript - catch has to collect an argument. So the first syntax is incorrect. It must be the second syntax.

try {
    // do something
}
catch {
    // incorrect  
}
  
try {
    // do something
}
catch(e) {
    // correct, even if we don't use e  
}


Modern browsers are more lenient so this error did not appear on all browsers.

Migrate Angular SPA from ADALJS to MSAL because it is awesome

jon-tyson-420512-unsplash-resized.jpg

This is a public service announcement for all office devs.

If you are using ADALJS - you need to upgrade your project to MSAL. It is awesome. It has everything you want, and it worked the way we expected it to, right out of the box. Plus a bunch more new features.

It is 2018, we can finally put away ADALJS. Do it ASAP.


Plan

  • High level ideas - replace A with B

  • New things we can now do

  • Code diff snippets (if you want to compare notes with your own)

High Level

ADALJS was never released stand-alone. It was released as part of an Adal-AngularJS library. In the many years after several community produced wrappers were created to wrap ADALJS into various frameworks. The one I was using is ng2-adal. But there are others.

  • AdalService -> MsalService

  • this.adalService.userInfo -> this.msalService.getUser()

  • replace auth route guards with MsalGuard

  • move adalService.config(adalConfig) to MsalModule imports dependency injection

  • add msalInterceptor to HTTP_INTERCEPTORS which automatically attach the correct bearer token

  • switch http (from httpModule) to httpClient (from httpClientModule) which listens to HTTP_INTERCEPTORS

  • a handy tip to detect if SPA is running inside an adalFrame and disable route-outlets (this disables sub-components from loading inside the iframe - this is a great tip for adaljs as well)

MSAL provides three libraries with examples - make sure you switch to the relevant library instead.

MSAL is Awesome!

  • Listen to BroadcastService observables to be notified when tokens are received successfully or expired, or if consent level has changed.

  • MSAL supports incremental consent.

  • Can use acquireTokenSilent to obtain tokens silently, listen to event subscription to catch if grant isn’t available - then call acquireTokenRedirect or acquireTokenPopUp.
    Generally listen to “consent_required” or “interaction_required”.

  • App should be registered with the set of permissions that an admin can grant for the whole organization.

  • But MSAL can request additional consent separately. Users will have to grant those.

  • MSAL can issue both v1 and v2 tokens so it has no problems talking to APIs that still need v1 tokens.

  • Switch library has no visible effect to your end users - they will not see any new consent and everything will happen silently in the back.

The conversion took me two evenings to wrap up. I’m now in the process of adding new incremental consent.

Code Diff Snippets

While the whole thing is still fresh in my mind, I want to write this blog post.

Here’s what I start with

  • Angular 7

  • ng2-adal

  • adal-angular (includes adal.js)

I end up with

  • @azure/msal-angular

npm

npm install @azure/msal-angular --save

npm uninstall ng2-adal adal-angular --save-dev

app.module.ts

Change imports to msal-angular, remove my own auth.guard and use MsalGuard instead.

Use MsalIntercepter to automatically add bearer token on HttpClient calls (so replacing HttpModule’s Http with the new HttpClientModule’s HttpClient).

Initialize MsalModule with config (this is traditionally the adalConfig.

// remove
import { AdalService } from 'ng2-adal/dist/core';
import { AuthGuard } from "./my/auth.guard";
// replace
import { MsalModule } from "@azure/msal-angular";
import { MsalInterceptor } from "@azure/msal-angular";
import { LogLevel } from 'msal';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';

export function loggerCallback(logLevel, message, piiEnabled) {
    console.log("client logging" + message);
}
export const protectedResourceMap: [string, string[]][] = [
    ['https://graph.microsoft.com/v1.0/me', ['user.read']],
    // ... other scopes
];

// imports
imports: [
    ...
    // HttpModule // remove
    HttpClientModule, // replace
    MsalModule.forRoot({
        clientID: '00000000-0000-0000-0000-000000000000',
        authority: "https://login.microsoftonline.com/common/",
        validateAuthority: true,
        redirectUri: window.location.origin,
        cacheLocation: 'localStorage',
        postLogoutRedirectUri: window.location.origin,
        navigateToLoginRequestUrl: false,
        popUp: false,
        unprotectedResources: ["https://www.microsoft.com/en-us/"],
        protectedResourceMap: protectedResourceMap,
        logger: loggerCallback,
        correlationId: "1000",
        level: LogLevel.Info,
        piiLoggingEnabled: true
    })
    ....
],
providers: [
    // AdalService,  // remove
    // AuthGuard,    // remove
    {provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true}   // add
    ],

app.component.ts

constructor(
  ...
  //private adalService: AdalService,
  private broadcastService: BroadcastService,
  private authService: MsalService
) {
  this.isIframe = window !== window.parent && !window.opener;
  //this.adalService.init(this.secretService.adalConfig);
  //this is moved to app.module.ts
}

ngOnInit(): void {
  this.subscription = this.broadcastService.subscribe("msal:loginSuccess", 
    (payload) => {
      console.log("login success " + JSON.stringify(payload));    
      this.loggedIn = true;
      this.user = this.msalService.getUser();
    });  
}

ngOnDestroy() {
  // disconnect from broadcast service on component destroy
  this.broadcastService.getMSALSubject().next(1);
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
}

app.component.html

<div>
  ...
  <router-outlet style="text-align:center;" *ngIf="!isIframe"></router-outlet>
</div>

app.routing-module.ts

...
import { MsalGuard } from '@azure/msal-angular';

const routes: Routes = [
    { path: '', 
        component: DemoComponent,
        canActivate: [MsalGuard],
        children: [
          ...
        ]
    },
];

Summary

TLDR - the first two paragraphs outline the high level changes we need to apply.

Sorry for the big massive screens and screens of text - these code are from my Flow Studio - and I have to cut out parts of the code to show specific changes that we need to make.