Multiple Chat Windows in Teams (Workaround)

My company recently made the full switch from Skype for Business to Microsoft Teams. For calls, it’s fine and mostly feels the same if not better since I prefer the aesthetic of Teams. The one thing that drives me insane about Teams is that I can’t have more than one chat window.

This is particularly irksome when I’m on a call where somebody’s screensharing, and I want to have a side-chat or ask/answer a question from somebody outside the call. As soon as you open another chat, the screenshare is reduced to an unusably-small size, and switching back makes the other chat go away. There’s a similar problem with managing multiple conversations or team chats at the same time.

Here’s an easy workaround: use the Teams web client. Just login at teams.microsoft.com, and you can have as many windows as you want. This works pretty well for side-chats on a conference call, for example if you want to have a parallel internal conversation while speaking to a client or customer. If you have frequent contacts, you can even create bookmarks to specific conversations.

Use bookmarks for conversations or contacts you access frequently

Featured image photo by 🇨🇭 Claudio Schwarz | @purzlbaum on Unsplash

Advertisement

E2E Angular Drag & Drop App in 5 Steps

Image by sachin_21 from Pixabay

Drag & drop is one of the best enhancements you can implement to give your app a modern feel and provide a great user experience, and Angular’s Component Dev Kit (CDK) makes it really easy to do. In this article, we’ll build a new Angular app from scratch using the Angular CLI and add a drag & drop todo list component.

Prereqs: Make sure you have the Angular CLI installed. You can test by running the command ng --version. If you need to install it, run the command npm install -g @angular/cli. (If you have problems installing on Linux, maybe this article can help!)

Step 1: Create a new app

Create a new app using the Angular CLI. We’ll also install the Angular CDK, and make sure it all works:

$ ng new my-app --defaults
$ cd my-app
$ npm install @angular/cdk
$ ng serve --open

Step 2: Import DragDropModule

In order to make use of Angular CDK’s drag & drop module, we need to import it. Open app.module.ts and import DragDropModule by making the following two edits:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { DragDropModule } from '@angular/cdk/drag-drop';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    DragDropModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 3: Add a new component

Now we’ll use the Angular CLI again to add a new component:

$ ng generate component todo-list

Next we need to modify the component to handle the drop event. We’ll also add some hard-coded test data to work with. Open todo-list.component.ts and make the following changes:

import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
  tasks = [
    'Cleaning',
    'Gardening',
    'Shopping'
  ];

  constructor() { }

  ngOnInit(): void {
  }

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.tasks, event.previousIndex, event.currentIndex);
  }
}

We also need to modify the component HTML. Update the contents of todo-list.component.html to be the following:

<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    <div class="example-box" *ngFor="let task of tasks" cdkDrag>{{task}}</div>
</div>

Step 4: Use the new component

At this point, we’re actually done adding the basic drag & drop functionality, so let’s use our new component. Replace the entire contents of app.component.html with this:

<app-todo-list></app-todo-list>

Now, head back out to your terminal, re-launch the app, and–voila!–you’ve got drag & drop!

$ ng serve --open

(Optional) Step 5: Make it pretty

Sure it’s functional, but it doesn’t look very good! Spruce it up by adding the following CSS to todo-list.component.css:

.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

Ah, that feels a little better!

Tech For Kids: Mighty Music Player

Source

I don’t remember how old I was when I got my first Walkman, but I remember everything about it. I got it along with three cassette tapes: Beach Boys – Endless Summer, Heavy D & The Boyz – Now That We Found Love (single), and C+C Music Factory – Things That Make You Go Hmm (single). What a great collection.

My kids are getting old enough that I want to give them the same gift of music and control over what they listen to, but I wasn’t sure how to do it in the age of no-physical-media without also giving them access to all kinds of music that might not be appropriate for kids. (In other words, I didn’t want to given them access to all of Spotify.) However, it also feels silly to invest in something like a CD player and all the things needed to provide music on CDs.

The Mighty music player is exactly what I was looking for. It lets me select which playlists to sync to the device, and play all the songs offline. So I’m able to put music from Frozen 2, Tangled, The Descendants, and Weezer (my daughter’s favorite) all on the device, and she has all the freedom to play her own music. She uses it all the time.

You use the Mighty app to sync to your Amazon Music or Spotify account (subscriptions required) and choose which playlists to transfer. The device is then connected to your phone via Bluetooth to transfer data. The experience of syncing music wasn’t great. It would hang or stall a lot, and I needed to restart it. But once I got it set up, it was exactly what I wanted.

Once synced, the songs are good for offline play for a month at which point you need to re-sync the device. I’ve only done this once so far, and the experience was similar to that first sync. It stalled once, and I needed to restart, but once it was done we were back to good.

Despite the sync problems and frustrations I’ve had with the app, the device itself is really awesome. It holds something like 1000 songs, and having all of Spotify’s library to choose from is pretty incredible. My daughter loves it!

6 Tips For Remote Team Success!

Source

I’ve been a full-time remote employee for more than five years now. There are lots of benefits to working from home, but it’s also easy to lose transparency or negatively impact the productivity of your team. Today I’m sharing my best tips for being successful as a remote team.

1. Be Visible

The most important thing you can do while working remotely is to be visible! Don’t make your team wonder if you’re showing up or question your contribution. In my opinion, the single best way to do this is to favor open channels of communication over private ones.

If you have a question, and you know who you want to ask, it’s easy to direct message them and have a private conversation. However, you could also ask that person in a team channel so that everybody can see. Having these conversations “in the open” is great for team engagement and knowledge-sharing. It can also lead to you getting your question answered sooner–for example, if the person you’re asking is away but there’s someone else who can answer. Before you direct message someone, think about what you’re asking. Is it something that needs to be private? If it’s not, consider asking in a more-public way that will benefit everyone!

Slack is my preferred “open channel” for these sorts of things, but you could just as easily use Teams or even Discord. It helps me keep track of what my team is doing, and it also gives me an opportunity to jump in on a conversation when I have additional context. It also helps with socialization so I don’t feel isolated, and the mix of work-related and non-work-related conversations help to build and strengthen relationships.

If your team doesn’t do formal daily standups, implementing “virtual standups”–as an individual or a team–can be another good way to keep your contributions visible. At the beginning or end of your day, send a summary of the things you accomplished, what you plan to tackle next, and highlight any roadblocks that are getting in your way.

2. Use a Webcam

Webcams are one of those things that everybody says is a good idea for working remotely but that nobody likes using. It’s easy to be self-conscious, and it can definitely be awkward to be on a call where you’re the only one on camera. I try really hard to embrace the cam, though, because the benefits are so important.

One great benefit is that it opens up a whole world of nonverbal communication. You can see when another participant is shaking their head, making a confused face, or raising their hand to get a word in. These cues make meetings more productive by providing feedback about the pace of content and helping to moderate the discussion. You can also see when somebody is talking while muted–because everybody stays muted when not speaking, right? (More on that in a minute.)

The thing I like most about webcams is that keeps me honest about paying attention. It’s hard not to get distracted by laptops and cell phones in regular, in-person meetings when everybody can see you, and it’s even harder when you’re sitting at your computer with all its alerts and notifications plus your phone’s right there and nobody can tell what you’re doing! Being on camera helps me resist the temptation to multi-task, and it turns out meetings are more productive when everybody’s engaged. Who knew?

3. Mute Your Mic

This one’s more about being courteous than productive, but as a remote team member you’ll likely find yourself on a lot of conference calls. Stay muted by default so people don’t have to listen to things like you munching on potato chips, your dog barking, or you yelling at your kids. I prefer to use a headset that has a hardware mute button on it so I can quickly mute & unmute when I have something to say. It’s a small thing, but it can be annoying to have “that person” on a call. Don’t wait to get shame-muted by someone else!

4. Keep Normal Hours

Keeping normal hours while working from home is an important anti-productivity preventative measure. Similar to the webcam, part of the benefit is just that it keeps you honest about your day and helps you resist all the temptations from things you’d rather be doing. More importantly, though, keeping sporadic hours poses a big threat to the team’s productivity, especially when there are multiple people doing it. It’s incredibly frustrating when there’s one person who has the answers you need, but nobody knows where they’re at or when they’ll re-surface, and it’s easy to lose a day when “the early person” needs something from “the late person” and the two don’t connect.

Be honest with yourself about how you should be spending your time, and hold yourself accountable. Have a predictable schedule so teammates know when you’ll be available, and say something if you do need to be a way for a bit. Be visible!

5. Virtual Office

The concept of a virtual office, or persistent team call, is something that’s becoming more prevalent at my work. One of our teams has a recurring all-day meeting, and people just join it throughout the day. Usually someone shares their screen regardless of whether others are actively working on the same thing or not. It’s great for socialization and relationship-building, and it also drives engagement through knowledge-sharing and collaboration.

Another team uses Slack to create open calls when they’re working on things. I love Slack calls for this because you can set a title or topic that’s visible in the channel allowing people to pop in & out. Slack calls are also great because they give the ability to draw on a screenshare. It’s fun for doodles but also great when you need to say, “Right HERE!”

6. Have Fun!

Fun’s important for in-office and remote workers, but it can be harder to find the fun when you’re on an island. You can’t walk around and look for a conversation, and you can only see what others choose to make visible. So, how can you keep it fun?

Slack is a great way to spread some fun with its emojis, reactions, and gifs (used sparingly!). It’s easy to share a joke or tell a funny story about your kid that’s melting down upstairs.

My team used to use Skype’s whiteboard feature to make the most ridiculous drawings before our daily standup. Somebody would make something, and everybody else would just keep adding. It was a good way to flex creativity and almost always made us laugh.

Webcams can also be good fun. We have a few people that use green screens to replace their backgrounds or apps like FaceRig to replace themselves with avatars. Even just holding up a hand-written note at the right time can be hilarious.

What are some things you do to keep it fun while working remotely? And what other tips do you have for being a successful remote worker? I’d love to hear your ideas and experiences!

Intro to Unit Testing In Angular With Karma & Jasmine

Ah, unit testing. One of my favorite subjects. I’ve been big on unit tests for what seems like more than a decade now, but I’ve never dipped my toes into the UI unit test pool! It’s been long enough, though. It’s time to learn!

So, I’m taking my first look at writing tests for an Angular application using Karma and Jasmine. I thought this tutorial at scotch.io was a great first read. It provides overviews of what Karma and Jasmine are, and walks you through the steps of creating an Anuglar application and adding tests from scratch using the Angular CLI, and that’s basically the foundation I’m working with today.

In this article, I’m going to write some tests as I extend behavior on “an existing product” and also write tests for some untested code. I’ll be using my favorite boilerplate code–the .NET Core CLI Angular template–as my existing product, as it provides a functional appliction with some existing tests for us to build on.

If you haven’t already, start by creating a new Angular application by running the following command:

dotnet new angular -o my-app

Verify that can run your new application by running dotnet run in the my-app directory. my-app/ClientApp contains the Angular application. Since we’re going to be adding to existing test suite, we should verify that the existing tests run and pass. Run the following from my-app/ClientApp:

npm install
ng test

You should see output like the following, indicating that all tests have passed.

Now that we’ve verified that existing tests work, we can start making changes. Let’s assume we want to modify the Counter page to have a decrement button in addition to its increment button, because sometimes we click too many times. Before we start making changes to the Counter component itself, we can describe the desired behavior in the existing counter.component.spec.ts file, which already contains similar logic for the increment functionality. Most of what needs to be written can be copied and adjusted from the Increment test.

Here’s the test I wrote:

it ('should decrease the current count by 1 when Decrement is clicked', async(() => {
  const countElement = fixture.nativeElement.querySelector('strong');
  expect(countElement.textContent).toEqual('0');

  const decrementButton = fixture.nativeElement.querySelector('#decrement-button');
  decrementButton.click();
  fixture.detectChanges();
  expect(countElement.textContent).toEqual('-1');
}));

I can run tests again, and guess what–it’ll fail. Cool. So let’s make it work, which is just two steps. First we need to add the button to counter.component.html:

<h1>Counter</h1>

<p>This is a simple example of an Angular component.</p>

<p aria-live="polite">Current count: <strong>{{ currentCount }}</strong></p>

<button class="btn btn-primary" (click)="incrementCounter()">Increment</button>

<button class="btn btn-secondary" (click)="decrementCounter()" id="decrement-button">Decrement</button>

And then we need to add the logic for when it’s clicked in counter.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-counter-component',
  templateUrl: './counter.component.html'
})
export class CounterComponent {
  public currentCount = 0;

  public incrementCounter() {
    this.currentCount++;
  }

  public decrementCounter() {
    this.currentCount--;
  }
}

That’s it. Now we can run ng test again and see that we now have 3 passing tests.

Good stuff. Now, let’s turn our attention to some untested code. FetchDataComponent is functional but has no tests for its display of data retrieved from the API. We’ll need to mock the API call to return some data and then assert that results are displayed as expected.

Here’s my fetch-data.component.spec.ts file with a test that mocks the API call and asserts that data is populated in the table on the UI:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

import { FetchDataComponent } from './fetch-data.component';

describe('FetchDataComponent', () => {
    let component: FetchDataComponent;
    let fixture: ComponentFixture<FetchDataComponent>;
    let httpMock: HttpTestingController;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            declarations: [FetchDataComponent],
            providers: [{ provide: 'BASE_URL', useValue: 'https://my.fake.url/' }]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(FetchDataComponent);
        component = fixture.componentInstance;
        httpMock = TestBed.get(HttpTestingController);
    });

    it('should retrieve weather forecasts', async(() => {
        const dummyForecasts = [
            {
                date: "date1",
                temperatureC: 0,
                temperatureF: 32,
                summary: "summary1"
            },
            {
                date: "date2",
                temperatureC: 100,
                temperatureF: 212,
                summary: "summary2"
            }
        ];

        const req = httpMock.expectOne('https://my.fake.url/weatherforecast');
        req.flush(dummyForecasts);

        expect(component.forecasts).toBe(dummyForecasts);
        fixture.detectChanges();
        const rowCount = fixture.nativeElement.querySelectorAll('table tr').length;
        expect(rowCount).toBe(3); // header plus two data rows
    }));
});

There’s a lot to unpack with this test, and I’m going to attempt to give a visual, summary-level explanation in the screenshot below.

Once again, we run tests using ng test and see that we now have passing tests for FetchDataComponent!

Installing Angular CLI on Ubuntu 18.04.4 LTS

I was all geared up to explore unit testing in Angular this morning–something I’ve been putting off learning for quite some time–and quickly hit an problem. I started by installing Angular CLI using apt install ng-common, but trying to use the Angular CLI’s ng command was launching Emacs!

It took me a minute to figure this out, and here’s what it took. First, I uninstalled:

sudo apt remove ng-common

Next, I installed using npm:

sudo npm install -g @angular/cli

This was common how-to-fix-it advice, but this didn’t resolve the problem for me. At this point ng was no longer launching an editor, but instead it was an unrecognized command. Luckily, I found the solution here.

I noticed in the install output that the bin location was listed. I was able to verify where the ng binary was located and add an alias. After that, ng --version worked successfully.

Here’s the alias I added to ~/.bashrc:

alias ng='/usr/local/lib/node_modules/@angular/cli/bin/ng'

Permission Bits in Base64 String

We’ve been kicking around some ideas of how to revamp user permissions in our application, and I was reflecting on how I’ve done it in the past. Previously, I worked on a client/server application where each user had a permission string that tracked all the rights they’d been granted, and global enumerations were used to identify which permissions and rights. I was explaining the approach to colleagues, and there was enough interest that I decided to recreate it.

Here’s the summary:

  • Each user has a Base64-encoded permission string
  • There’s an enumeration for the different modules; this identifies which byte in the permission string contains the rights
  • There’s an enumeration for the different rights which can be granted for a given module; these are used in bitwise operations to check or modify a user’s rights to a module

Enumerations

public enum Modules
{
    ModuleOne = 0,
    ModuleTwo = 1,
    ModuleThree = 2
}

public enum Permissions
{ 
    Create = 1,     // 0000 0001
    Read = 2,       // 0000 0010
    Update = 4,     // 0000 0100
    Delete = 8,     // 0000 1000
    Execute = 16    // 0001 0000
}

Granting Permission

Granting a permission is a simple as specifying the module and permission to grant. The module is used to identify the correct byte, and then a bitwise OR is performed to enable the corresponding permission bit.

string GrantPermission(string permissionString, Modules module, Permissions permission)
{
    if (permissionString == null)
    {
        permissionString = string.Empty;
    }

    var bytes = Convert.FromBase64String(permissionString);
    
    if (bytes.Length <= (int)module)
    {
        Array.Resize(ref bytes, (int)module + 1);
    }

    bytes[(int)module] |= (byte)permission;

    return Convert.ToBase64String(bytes);
}

Checking Permission

Checking a permission is very similar to granting except that we AND the permission mask. If the result isn’t zero, that means the user has the specified permission.

bool HasPermission(string permissionString, Modules module, Permissions permission)
{
    if (string.IsNullOrWhiteSpace(permissionString))
    {
        return false;
    }

    var bytes = Convert.FromBase64String(permissionString);

    if (permissionString.Length < (int)module)
    {
        return false;
    }

    var b = bytes[(int)module];

    return (b & (byte)permission) != 0;
}

Revoking Permission

Revoking a permission is similar as well. This time, we’ll invert the bits on the permission mask and AND it to the module permission byte so that all non-zero bits except the specified permission will remain the same.

string RevokePermission(string permissionString, Modules module, Permissions permission)
{
    if (permissionString == null)
    {
        permissionString = string.Empty;
    }

    var bytes = Convert.FromBase64String(permissionString);

    if (bytes.Length <= (int)module)
    {
        return permissionString;
    }

    bytes[(int)module] &= (byte)~permission;

    return Convert.ToBase64String(bytes);
}

Conclusion

This was just a quick & dirty re-implementation of something from the past. I haven’t put this code through the wringer, so there may be some bugs, and you could probably make it more developer-friendly by extracting it to its own class or moving this logic into some nice extension methods. It’s an efficient way to store and check permissions for a collection of modules. Depending on your needs, it might be a good fit. I’m also wondering if this could be used in a hybrid system–for example, perhaps it would make sense for different applications or scopes to have their own permission string within a larger system.

Pull Image From Private Registry

In order to deploy an image into Kubernetes, the image must be available in a registry. I’m very much in the learning & experimenting phase of my Kubernetes journey, and I find myself using Docker Hub private registries for a lot of things. After using docker login I can docker push & docker pull images from a private registry just by naming them correctly with my Docker ID e.g. <my-docker-id>/some-image. However, kubectl doesn’t automatically inherit this knowledge and access, so what must be done to enable deployment of a locally-developed image into a locally-runing cluster? Let’s take a look!

The first thing we need to do is authenticate to the registry, which we can do using docker login. This will prompt for credentials and store an authorization token in ~/.docker/config.json. The lines below demonstrate how to do this and view the result.

docker login
cat ~/.docker/config.json

In order to make the authentication token accessible to the Kubernetes cluster, we can use Secrets. The following commands can be used to copy the credential from the Docker config.json into a Secret named regcred and inspect the result.

kubectl create secret generic regcred \
    --from-file=.dockerconfigjson=<path/to/.docker/config.json> \
    --type=kubernetes.io/dockerconfigjson
kubectl get secret regcred --output=yaml

Next, we’ll create a Deployment that includes our Secret. Create a new file my-app-deployment.yaml with the following contents:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: <my-docker-id>/my-app:latest
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: regcred

And we’ll need to expose the deployment with a Service, so create another file my-app-service.yaml with this for its contents:

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30080
  selector:
    app: my-app

Now we use these files to create the Deployment and Service in Kubernetes:

kubectl create -f my-app-deployment.yaml
kubectl create -f my-app-service.yaml

That should do the trick. You can inspect the Deployment and Service using kubectl get pods and kubectl describe service my-app-service respectively. Assuming everything deployed correctly, you should be able to access your app at http://cluster-host-ip:30080.

%d bloggers like this: