Resolutions to software engineering problems that I couldn't find anywhere else on the Internet. This is mostly here so I can find all my past solutions in one place.
Friday, June 18, 2021
Font Awesome for Angular with Data-Driven Icons
Monday, March 22, 2021
Searching all Fields in all Tables in a Single Database
Way back in 2015 I provided the SQL to search for a specific string in any field in any table in any database on a server. I don't remember why I needed that, but I guess I did. What I've come to use more frequently, however, is a search of every field in every table in a single database. So here's that query:
DECLARE @SearchText NVARCHAR(1000) = 'Some value' DECLARE @SchemaName NVARCHAR(256), @TableName NVARCHAR(256), @ColumnName NVARCHAR(256) DECLARE TableCursor CURSOR FOR SELECT sch.[Name] AS SchemaName, st.[Name] AS TableName, sc.[Name] AS ColumnName FROM sys.tables st WITH (NOLOCK) INNER JOIN sys.columns sc WITH (NOLOCK) ON st.object_id = sc.object_id INNER JOIN sys.schemas sch WITH (NOLOCK) ON st.schema_id = sch.schema_id OPEN TableCursor FETCH NEXT FROM TableCursor INTO @SchemaName, @TableName, @ColumnName WHILE @@FETCH_STATUS = 0 BEGIN DECLARE @InternalSQL NVARCHAR(MAX) = 'SELECT @CountParam = COUNT(*) FROM [' + @SchemaName + '].[' + @TableName + '] WHERE [' + @ColumnName + '] LIKE ''%' + @SearchText + '%''' DECLARE @Count INT EXEC SP_EXECUTESQL @InternalSQL, N'@CountParam INT OUT', @Count OUT IF (@Count > 0) BEGIN PRINT @SchemaName + '.' + @TableName + '.' + @ColumnName END FETCH NEXT FROM TableCursor INTO @SchemaName, @TableName, @ColumnName END CLOSE TableCursor DEALLOCATE TableCursor
Friday, February 12, 2021
Scrum Branching Strategy
I've been using git for a few years now and I don't see how I could ever go back to TFVC or *shudder* Visual Source Safe. Git makes managing branches and work significantly easier than other source control technologies I've used in the past, but there seems to be some concern or confusion regarding a good branching strategy to use along with Scrum.
I was pointed to this article by a colleague and while I agreed with some of it, some of it just seemed flat out wrong to me. We've worked really hard through several iterations to land on our branching strategy in my current role and it works really well. I figured I'd share it and our reasons for going this route.
Before I start, it's important to point out that we use Azure DevOps for our CI/CD pipelines so we use their Pull Request tool to merge between some branches. If you're using a different tool then you may follow slightly different processes that make sense for that tool. I've done my best to lay out the way we do it using git terminology, but there are definitely parts that are specific to Azure DevOps.
The Basic Premise
The Branches
- main - this branch matches our production environment nearly all the time
- staging - this branch is created only when necessary
- release/<release number> - this branch is our iteration branch and contains all changes that are expected to be available as a result of our iteration
- <task branches> - these branches are transient, abundant, and created as necessary to complete individual pieces of the iteration (i.e. stories or bugs), but could also be created as sub-task branches to work on tasks within stories or bugs
The Strategy
- Create a branch from main called release/<release number> where <release number> represents the next version number
- If 1.0 is in production you'd create a branch called release/1.1
- When a new story is started in the sprint, create a branch for it, named something meaningful to the story (I'll refer to this as the story branch the rest of the way)
- If the story is about accepting online orders you might name the branch online-orders
- Every task in the story could have a separate task branch named whatever you want (I'll refer to these as task branches the rest of the way)
- If the story to accept online orders has separate pieces to accept credit card payments and invoices you might create a branch called accept-credit-cards when you start working on that task and another developer might create a branch called accept-invoices when they start working on the other task
- As the work is completed on each task, the task branch is merged into the story branch
- Depending on team dynamics, you could use a pull request to do this, but on our team we just review our own changes and make sure there are no merge conflicts
- It doesn't matter what merge type you use (fast-forward, rebase, etc.) because these commits will be squashed in the next step
- When a story is complete, the story branch can be deployed to the dev environment to validate everything is working together as expected
- When the story is ready to be tested (this varies by team and project, but on our team a story is ready to be tested when the developers feel confident that it could safely go live right at that moment; in other words, we don't "throw it over the wall" and wait to see what the tester finds), we merge the story branch into the release/<release number> branch and deploy that branch to the QA environment
- We do this using a pull request in Azure DevOps
- The important part here is to do a squash commit and provide a useful commit message for the work that was done on the story branch (e.g. "Made changes to allow online orders to be placed via credit card and invoice")
- If the tester finds something, create a new branch based on the release/<release number> branch, fix the bug, and repeat step 6
- At the end of the sprint, only completed stories are in the release/<release number> branch (that's important)
- Create a staging branch from main
- Perform a squash commit from release/<release number> into staging and make the commit message the release number
- We do this via pull request in Azure DevOps and it allows us to use the release number as the title of the pull request and then we fill in the individual commit message from the story branches as the description of the pull request; that way we have the detailed information from each story as well as the release number in the commit log
- This is also the time to associate work items with the commit if your team does that
- Once the staging branch has been updated, the release/<release number> branch can safely be deleted and the staging branch can be deployed to the staging environment
- When the next sprint starts, create a new release/<release number> branch based on the next release (so if our previous release was 1.1 this branch would be named release/1.2) from the staging branch (that's important)
- When it's time to deploy the code to production, rebase the changes from the staging branch onto the main branch
- Rebasing one branch onto another applies each commit from the source onto the destination, but there's only one commit to staging that gets rebased onto master at this point
- Since we create the new release/<release number> branch from the staging branch we already have the same exact commit (with the same SHA1) in the release/<release number> branch that is now in main
- Delete the staging branch and repeat this process starting at step 2 for every sprint until the project is complete
Conclusion
Tuesday, February 2, 2021
Flexbox Row/Column
I've been working more towards using Flexbox and away from Bootstrap's column layout as much as possible. I love the column layout, but it still feels janky to me sometimes and Flexbox seems a lot smoother. I was recently updating a page that uses columns to display 3 items on a row and each item (with margins and padding) takes up 33% of the row. When there are 4 items, there are 2 rows with 3 items on the first row and 1 item on the second row, but the single item on the second row still only takes up 33% of the second row.
I was able to accomplish this using the following markup and CSS.
<div class="d-flex flex-wrap"> <div class="item"> ...single item contents... </div> <div class="item"> ...single item contents... </div> <div class="item"> ...single item contents... </div> <div class="item"> ...single item contents... </div> </div>
.item { flex: 0 0 33%; }
At one point I had flex: 1 0 33% and that was really close, except the one item on the second line took up the entire line. This works beautifully and I'm happy because I get to use flexbox.
Tuesday, November 17, 2020
Git Stash Message
I've often struggled with git stash because I was under the impression you couldn't tag, label, or otherwise identify what you were doing when you stashed something. I've still used it plenty, but it was always a bit of a struggle to figure out which stash was which down the road.
Yesterday I learned you actually can label the stashes, by using save.
git stash save [message]
Good times.
Monday, February 24, 2020
Array Intersection in JavaScript
For example, let's say our application has role-based permissions that get populated as an array. That array might look like this:const roles = ['employee', 'manager']; We protect something in our app by applying restrictions to it, maybe like this:const restrictions = ['president', 'accountant']; What we need to know is does the user have the right role to lift the restriction. In other words, we want to know if the two arrays intersect.
Since there is no Array.intersect (yet) we have to come up with something. This is just what I use when I have two simple arrays (in this case two arrays of string values). We could add it to the Array prototype if we want, but I'm not going to show that here.
const roles = ['employee', 'manager'];
const restrictions = ['president', 'accountant'];
const matches = roles.filter(r => restrictions.includes(r));
Once this runs, matches contains all of the values that were in both arrays (so it's an empty array in this case). We can do evaluations on it like:
if (!!matches && !!matches.length) {
// do some stuff
}
BONUS: We can do the same thing with complex objects by applying the map function to this process. So if our array of roles looked like this:const roles = [{name: 'employee'}, {name: 'manager'}]; we could do the intersect like this:const matches = roles.map(r => r.name).filter(role => restrictions.includes(role));
I got this one and a couple of other goodies from this post on Medium. Happy coding!
Tuesday, February 11, 2020
Masked Input Length Validation in Angular
1: import { Injectable } from '@angular/core';
2: import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
3:
4: @Injectable({
5: providedIn: 'root'
6: })
7: export class MaskedLengthValidator {
8: static minLength(minLength: number, replace?: Array<string>): ValidatorFn {
9: return (control: AbstractControl): ValidationErrors | null => {
10: if (this.isEmptyInputValue(control.value)) {
11: return null;
12: }
13:
14: let value = control.value;
15:
16: if (!!replace && !!replace.length) {
17: replace.forEach(c => {
18: var regEx = new RegExp(c, 'g');
19: value = value.replace(regEx, "");
20: });
21: } else {
22: value = control.value.replace(/_/g, "");
23: }
24:
25: const length: number = value ? value.length : 0;
26: return length < minLength ?
27: {'minlength': {'requiredLength': minLength, 'actualLength': length}} :
28: null;
29: };
30: }
31:
32: static maxLength(maxLength: number, replace?: Array<string>): ValidatorFn {
33: return (control: AbstractControl): ValidationErrors | null => {
34: let value = control.value;
35:
36: if (!!replace && !!replace.length) {
37: replace.forEach(c => {
38: var regEx = new RegExp(c, 'g');
39: value = value.replace(regEx, "");
40: });
41: } else {
42: value = !!value ? control.value.replace(/_/g, "") : value;
43: }
44:
45: const length: number = value ? value.length : 0;
46: return length > maxLength ?
47: {'maxLength': {'requiredLength': maxLength, 'actualLength': length}} :
48: null;
49: };
50: }
51:
52: static isEmptyInputValue(value: any): boolean {
53: // we don't check for string here so it also works with arrays
54: return value == null || value.length === 0;
55: }
56: }
57:
So that's the validator. The simple way to use it is like this:
1: ngOnInit() {
2: this.someForm = this.formBuilder.group({
3: phoneNumber: [null, [Validators.required, MaskedLengthValidator.minLength(10), MaskedLengthValidator.maxLength(10)]]
4: });
5: }
And if you have placeholders in your mask other than underscores you can use it like this:
1: ngOnInit() {
2: this.someForm = this.formBuilder.group({
3: phoneNumber: [null, [Validators.required, MaskedLengthValidator.minLength(10, ['-']), MaskedLengthValidator.maxLength(10, ['-'])]]
4: });
5: }
This is pretty straightforward once you think about it. Now you don't have to think about.