posts: more work on rought draft

This commit is contained in:
Stevan Freeborn
2024-11-11 16:07:42 -06:00
parent 284c8d5d7a
commit e60f7c9afb
12 changed files with 215 additions and 12 deletions
+2 -1
View File
@@ -18,5 +18,6 @@
"default": true, "default": true,
"MD041": false, "MD041": false,
"MD048": false, "MD048": false,
} },
"editor.formatOnSave": true,
} }
+15 -2
View File
@@ -23,14 +23,27 @@
{ {
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() { dataLayer.push(arguments); }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'G-5JM0TZTRHF'); gtag('config', 'G-5JM0TZTRHF');
</script> </script>
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script> <script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript> <noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt=""
referrerpolicy="no-referrer-when-downgrade" /></noscript>
} }
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<script>
// NOTE: This is a workaround for the issue with FocusOnNavigate in Blazor
// not playing nicely with static site generation.
// read more here: https://github.com/dotnet/aspnetcore/issues/52412
window.Blazor.addEventListener("enhancedload", () => {
if (window.location.pathname === "/") {
return;
}
window.scroll({ top: 0, left: 0, behavior: 'instant' });
});
</script>
</body> </body>
</html> </html>
+3 -8
View File
@@ -17,12 +17,7 @@ else
<HeadContent> <HeadContent>
<meta name="description" content="@description"> <meta name="description" content="@description">
<OpenGraph <OpenGraph PageTitle="@title" Description="@description" Type="article" Image="@BlogPost.OpenGraphImage" />
PageTitle="@title"
Description="@description"
Type="article"
Image="@BlogPost.OpenGraphImage"
/>
<link rel="stylesheet" href="markdown.css"> <link rel="stylesheet" href="markdown.css">
<link rel="stylesheet" href="prism.css"> <link rel="stylesheet" href="prism.css">
</HeadContent> </HeadContent>
@@ -40,9 +35,9 @@ else
<PageScript Src="./Components/Pages/Post.razor.js" /> <PageScript Src="./Components/Pages/Post.razor.js" />
} }
@code @code
{ {
[Parameter] [Parameter]
public string Slug { get; set; } = string.Empty; public string Slug { get; set; } = string.Empty;
private PostWithContent? BlogPost; private PostWithContent? BlogPost;
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

@@ -0,0 +1,82 @@
```json meta
{
"title": "CountIfs and SumIfs in Onspring",
"lead": "How to use multiple criteria to count and sum records in Onspring",
"isPublished": true,
"publishedAt": "2023-10-21",
"openGraphImage": "posts/countifs-sumifs-in-onspring/og-image.png",
}
```
| [![thumbnail](posts/countifs-sumifs-in-onspring/thumbnail.png)](https://www.youtube.com/watch?v=CfpsO7_Emd0) |
|:--:|
|*Click above to watch a video demonstration*|
If you have spent a lot of time working in spreadsheets there is likely little chance that you haven't heard of the [countifs](https://support.microsoft.com/en-us/office/countifs-function-dda3dc6e-f74e-4aee-88bc-aa8c2a866842) or [sumifs](https://support.microsoft.com/en-us/office/sumifs-function-c9e748f5-7ea7-455d-9406-611cebce642b) function and a far more likely chance you've actually used both.
And for good reason too.
These functions are super simple, but really powerful because you can use them to quickly filter your data and calculate some basic metrics that allow you to better understand a large data set.
And you might have already guessed, but the same holds true for these sorts of calculations in [Onspring](https://onspring.com/) as well.
For example let's say that I have a super simple projects app and each project in my app can have many related task records. Something like this:
![projects-app-example](posts/countifs-sumifs-in-onspring/projects-app-example.png)
When the number of tasks related to the project is small it is pretty easy to just pop open the content record and get an idea of what is going on with the project, but as the number of related records grow it would probably be helpful to have some metrics that quickly summarize the state of things.
Perhaps some simple counts that tell us...
- How many total tasks there are
- How many of those tasks are complete
- How many of those tasks are in progress
- How many of those tasks are of a particular type
Something like this:
![basic-formula-example](posts/countifs-sumifs-in-onspring/basic-formula-examples.png)
Which is pretty straightforward to do because each of those counts are based upon no more than a single criteria and [Onspring](https://onspring.com/) already gives us a really handy CountIf function to work with. Below is an example of the CountIf function at work for counting the number of complete tasks.
```javascript
CountIf({:Tasks::Status}==[:Complete], {:item::Record Id});
```
But what if I want to continue building these task counts and subdiving them not just by their status or type, but by both. Meaning I want to create my counts using multiple criteria. This is when you'd most likely default to reaching for something like [countifs](https://support.microsoft.com/en-us/office/countifs-function-dda3dc6e-f74e-4aee-88bc-aa8c2a866842) or [sumifs](https://support.microsoft.com/en-us/office/sumifs-function-c9e748f5-7ea7-455d-9406-611cebce642b).
However in Onspring there are not built-in countifs and sumifs functions and if you look at the documentation for the CountIf or SumIf functions you'll see they only accept a single criteria parameter. For example:
> [!NOTE]
> The CountIf function can evaluate only one criterion at a time.
This might be where an admin isn't sure what to do next and it's where I'd like to help. Turns out this problem is completely solvable and the answer really isn't all that complex. It just requires looking at things from a different perspective.
Let's start by introducing a formula field at our task records that will allow each individual task record to identify itself as having met the criteria or not to be included in a particular count at the project record.
Take for example that I want to have a count at the project record of all the incidental tasks that are in progress. So let's add a formula field at the task record which indicates whether the task is exactly that - incidental and in progress.
Here is an example of that field and it's formula syntax:
![helper-formula-field-example](posts/countifs-sumifs-in-onspring/helper-formula-field-example.gif)
```javascript
if ({:Type}==[:Incidental] && {:Status}==[:In Progress]) return [:True];
return [:False];
```
With that in place at our task records our project records will be able to use that field to identify which related task records have met a particular criteria and therefore should be included as part of a particular count.
![task-records-from-project-record-example](posts/countifs-sumifs-in-onspring/task-records-from-project-record-example.png)
And here is an example of implementing a count of all in progress incidental tasks at the project record.
![multi-criteria-count-example](posts/countifs-sumifs-in-onspring/multi-criteria-count-example.png)
```javascript
CountIf({:Tasks::IsIncidentalAndIsInProgress}==[:True], {:item::Record Id});
```
This type of pattern can then be repeated as many times as you need to get at all the different sums and counts you need. You can also use as complex a criteria as you want to identify the records to include or exclude from your calculations.
I hope this is helpful and makes your job as an admin just a little easier.
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

@@ -3,7 +3,7 @@
"title": "Onspring's RefToArray Formula Function Explained: The What, Why, and How", "title": "Onspring's RefToArray Formula Function Explained: The What, Why, and How",
"lead": "Learn how to use the RefToArray function in the Onspring formula engine to transform and aggregate data from related records. This guide breaks down how RefToArray works, offers practical use cases, and explains when to leverage it for custom data evaluation. Whether you're handling risk scores, survey responses, or task deadlines, get tips on harnessing RefToArray for data insight and control without giving yourself a headache", "lead": "Learn how to use the RefToArray function in the Onspring formula engine to transform and aggregate data from related records. This guide breaks down how RefToArray works, offers practical use cases, and explains when to leverage it for custom data evaluation. Whether you're handling risk scores, survey responses, or task deadlines, get tips on harnessing RefToArray for data insight and control without giving yourself a headache",
"isPublished": true, "isPublished": true,
"publishedAt": "2024-11-08", "publishedAt": "2024-11-11",
"openGraphImage": "posts/onspring-reftoarray-explained/og-image.png", "openGraphImage": "posts/onspring-reftoarray-explained/og-image.png",
} }
``` ```
@@ -113,3 +113,115 @@ return JSON.stringify(names);
Again, there is a lot more to learn about arrays, but I hope this gives you a basic understanding of what they are and how they work. If you're interested in learning more, I recommend checking out the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections). This should though give you enough of an understanding to follow how they are used in the context of `RefToArray`. Again, there is a lot more to learn about arrays, but I hope this gives you a basic understanding of what they are and how they work. If you're interested in learning more, I recommend checking out the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections). This should though give you enough of an understanding to follow how they are used in the context of `RefToArray`.
## Putting It All Together ## Putting It All Together
Now that we have a basic understanding of reference fields, objects, and arrays, we can start to make sense of `RefToArray`. At its core, `RefToArray` is a function that allows you to transform and aggregate data from related records. It takes two arguments: the first argument is the reference field that links the records together, and the second argument is an object that defines the fields you want to include in the resulting array. Here's an example of how you might use `RefToArray` to get a list of tasks that are linked to a project:
```javascript
var tasks = RefToArray({:Tasks}, { status: {:item::Status}, dueDate: {:item::Due Date} });
// returns an array of objects like this:
// [
// { status: "In Progress", dueDate: "2024-11-08" },
// { status: "Not Started", dueDate: "2024-11-10" },
// ]
return JSON.stringify(tasks);
```
> [!NOTE]
> The second argument to `RefToArray` is often the most confusing part for folks. I've found the easiest way to conceptualize it is as a template for the objects you want to create from each of the related records in the reference field. The keys in the object are the names of the fields you want to have in the resulting objects and the values are fields from the related records whose values you want to include in the resulting objects.
This makes `RefToArray` primarily a utility function that allows you to cross that boundary between records and data in Onspring and data structures in JavaScript that allows you to manipulate that data in a way you couldn't otherwise.
And remember now that your record data is in an array of objects you can use them in all the ways you would expect to be able to use an array of objects. You can filter them, sort them, map them, reduce them, etc. Which is what is being done in the example at the beginning of this post.
## Cursed with Knowledge
Okay, so now you know what `RefToArray` is and how it works. This is great, but it also can lead you down a dark and dangerous path. You see, now that you know how to use `RefToArray` you might be tempted to use it everywhere. Kind of one of those "when all you have is a hammer, everything looks like a nail" situations. But I'm here to tell you that you should resist that temptation. `RefToArray` is a powerful tool, but it is not always the right tool for the job. In a lot of cases you can get by with simpler formulas that don't require the complexity of `RefToArray`. So let's talk about when you would and wouldn't want to use it.
### Wait, Don't Do That
The most common mistake with `RefToArray` is using it when you don't need to. For example:
- If you only need to access a single fields value from related records
- If you only need to get the count, average, max, min, or sum of a field from related records
- If you only need to conditionally count or sum a field from related records
All of these can be done with simpler formulas or built-in functions. For example, if you only need to get the count of related records you can use the `Count` function. If you only need to get the sum of a field from related records you can use the `Sum` function. If you only need to conditionally count or sum a field from related records you can use the `CountIf` or `SumIf` functions. These functions are simpler and more efficient than using `RefToArray` in these cases.
```javascript
CountIf({:Tasks::Status}!=[:Complete],{:item::Record Id})
```
> [!NOTE]
> A lot of time I see folks using `RefToArray` to do conditional aggregations when they need to consider more than one field in the related records. You don't need to though. The trick is to move your conditional logic into a formula field in the related record itself and then use the `SumIf` or `CountIf` functions to aggregate the results. This is a much more efficient way to do it. I've wrote about this approach in more detail [here](/countifs-sumifs-in-onspring).
The other big red-flag usage of `RefToArray` is when you are working with a large number of related records. `RefToArray` can be slow when working with a large number of records because it has to fetch all of the related records, load them into memory, and transform them into an array of objects so that you can work with them in your formula. Then the formula engine has to actually run your formula to perform the operations you want to do which may or may not involve things that are computationally expensive. Therefore I think it is best to avoid using `RefToArray` in these cases and if you absolutely have to you should give some thought to how you can optimize your formula to make it efficient - which is a whole other post in itself.
## A Perfect Fit
You are excited to use `RefToArray` now, right? Especially after I've provided such a wonderful cautionary tale. 😅
Jokes aside though there are most definitely times at which you can't avoid it or it is actually the best tool for the job. I doubt I'll be able to cover all the concrete use cases where `RefToArray` is the right choice, but I can give you a pattern you can look for that might indicate that you've wondered into `RefToArray` territory. This is a pattern that I've seen in my own work and in the work of others that I think is a good indicator that you probably will need to use `RefToArray`.
### Siblings Need To Know About Each Other
Most commonly I've found that `RefToArray` comes up when the answer to the question you need your formula field to answer requires that each of your related records know about each other. This is a bit of a weird concept to wrap your head around at first. Intuitively you might think "what do you mean they need to know about each other? They're related to the same record, of course they know about each other." But that isn't the case. Each of the related records isn't aware of the other unless they are evaluated in the context of their parent record in the same formula field.
For example, let's say you have a Tasks app and a Projects app. Each task is linked to a project and from the project perspective you want to know the next upcoming due date of all the tasks. This requires that the tasks know about each other so that we can compare their due dates, filter out any task that are not after the current date, and then sort them to find the next upcoming due date. Something like this:
```javascript
// create an array of tasks
// with their date values.
// i.e.
// [
// { date: 6/23/2023 },
// { date: 8/3/2023 },
// { date: 12/15/2023 },
// { date: 1/8/2024 },
// { date: 3/23/2024 }
// ]
var tasks = RefToArray(
{:Tasks},
{ date: {:item::Due Date} }
);
// filter out any tasks whose
// dates are before or on today
// i.e. will return:
// [
// { date: 1/8/2024 },
// { date: 3/23/2024 }
// ]
var filtered = tasks.filter(function (p) {
return IsAfterToday(new Date(p.date));
});
// if there are no tasks
// for us to consider return null.
if (filtered.length == 0) {
return null;
}
// sort dates in ascending order
// according to their value in milliseconds
// i.e. will return:
// [
// { date: 1/8/2024 },
// { date: 3/23/2024 }
// ]
var sorted = filtered
.slice()
.sort(function (a,b) {
return new Date(a.date).getTime() - new Date(b.date).getTime();
});
// return date of first task in sorted
// array
// i.e. if today's date is 12/22/2023 it will return
// 1/8/2023
return Object(sorted[0]).date;
```
Again that is definitely a "for instance" example and not an exhaustive list of all the times you might need to use `RefToArray`. But it illustrates the point that if you find yourself in a situation where you are needing to perform some sort of aggregation or manipulation that requires related record A to consider and know about related record B then you are probably going to have to become familiar with `RefToArray`.
## Conclusion
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB