Arcade expression return nearest future date from FeatureSet

766
8
06-13-2023 06:13 AM
Labels (2)
RingwayJacobs
New Contributor II

Hi

I am working on some code to bring across two date fields, 'lastinspdate' and 'nextinspdate' from another table.

I've adapted some really useful code (below) to get me started (thanks @JohannesLindner) but I'm struggling to do two things:

1/ limit the response to just show the two date fields

2/ to add in a filter that returns the response to the record with the 'nextinspdate' nearest in the future.

Many thanks in advance. Not a natural coder here so be kind 🙂

var ref_id = $feature["CENTRAL_AS"]

var fs = FeatureSetByName($map, "InspInspectionDates", ['central_asset_id', 'lastinspdate', 'nextinspdate'], false)

var filt = "central_asset_id = @ref_id"

var cou = First(Filter(fs, filt))

if(cou == null) { return "No inspection found" }

var attributes = Dictionary(Text(cou))["attributes"]

var popup_lines = []

for(var a in attributes) {

    var line = `${a}: ${attributes[a]}`

    Push(popup_lines, line)

}

return Concatenate(popup_lines, TextFormatting.NewLine)
Tags (3)
0 Kudos
8 Replies
jcarlson
MVP Esteemed Contributor

If you're building a popup, you really ought to consider using an Arcade element, rather than returning a string. You are essentially building a fields list, so why not doing it directly? Under add content in the popup menu, choose an Arcade element:

jcarlson_0-1686662978522.png

The output of that code block will be a bit different, but it's not too hard. For the null situation, we can return a simple line of text, but we can bring your two desired attributes into a field list otherwise.

What happens when there are more than one items in the inspection dates table? The default sort is by objectid, so you may want to apply an OrderBy function before grabbing the first record, to ensure that you're really getting the latest item. An in response to one of your questions, would you have a situation in which there are multiple future inspection dates, and one is further into the future? Without seeing the data, it's hard to know exactly how to approach the situation.

The first option would be to reverse the sort so that newer records are first:

fs = OrderBy(fs, 'lastinspdate DESC')

The other would be to filter out records where the "nextinspdate" field is in the past, and order by that field ascending, such that the first record will be the nearest future inspection date.

fs = Filter(OrderBy(fs, 'nextinspdate ASC'), 'nextinspdate > CURRENT_DATE()')

 But once we have that figured out, the rest isn't too hard. When we have a feature returned by the filter, we can populate the popup item with a simple loop. Calling a loop on a Feature directly will iterate over its attributes, so there's no need to convert it to a dictionary first.

for (var attr in cou) {
// do something per attribute
}

Anyway, we put this all together, and it might look like this:

// Get inspection date records in Wo
var fs = FeatureSetByName(
  $map,
  "InspInspectionDates", 
  ['central_asset_id', 'lastinspdate', 'nextinspdate'],
  false
)

// Get future inspections, order for nearest to present
fs = Filter(OrderBy(fs, 'nextinspdate ASC'), 'nextinspdate > CURRENT_DATE()')

// Get the first
var cou = First(Filter(
  fs,
  `central_asset_id = '${$feature['CENTRAL_AS']}'`
))

// Return simple text if no inspection
if(cou == null) {
  return {
    type: 'text',
    text: 'No inspection found'
   }
}

// empty objects for output
var fieldInfos = []
var attributes = {}

for (var att in cou) {
  // push field name
  Push(fieldInfos, {fieldName: att})

  // add attribute
  attributes[att] = cou[att]
}

return {
  type: 'fields',
  title: 'Inspection Dates',
  fieldInfos: fieldInfos,
  attributes : attributes
}

Now, if you wanted to go in and make those look a little nicer with field aliases, there's more you can do with the Schema function, but the script above ought to be enough to get you a nice looking fields list in your popup.

- Josh Carlson
Kendall County GIS
0 Kudos
RingwayJacobs
New Contributor II

Thanks for the super quick response Josh. I've taken your advice and used the Arcade block instead. I've used it before and its good, though I've found it leaves a messy blank space if you have a text bloc preceding it.

ESRI wasn't liking the Current_Date() part so I declared NOW() as a variable and replaced that bit.

Code now works but both yours and Johannes code are returning the if null text for all records so I'm not sure what is going on there...

0 Kudos
jcarlson
MVP Esteemed Contributor

ESRI wasn't liking the Current_Date()

You'd hope they would take it out of their own documentation, then... Glad you fixed that part.

As for the nulls, try using IsEmpty() instead? Or else, check for no-record situations before grabbing the cou object. Swap this in for lines 12-24 in my code snipped above.

 

var filt_fs = Filter(fs, `central_asset_id = '${$feature['CENTRAL_AS']}'`)

if (Count(filt_fs) < 1) {
  return {
    type: 'text',
    text: 'No inspection found'
   }
}

var cou = First(filt_fs)

 

 

- Josh Carlson
Kendall County GIS
0 Kudos
RingwayJacobs
New Contributor II

Hi Josh, thanks for coming back to me.

I've tried replacing line 19 with IsEmpty(cou) and IsEmpty('nextinspdate') and it doesn't like either. I just get "Test execution error: Invalid variable assignment.. Verify test data." as before

When replacing 12-24 with your code, I get 

"Test execution error: Execution error - Cannot access value using a key of this type. Verify test data."

Not sure if you had any further ideas?

 

0 Kudos
jcarlson
MVP Esteemed Contributor

Ah, shoot. I forgot the "$" in front of "feature" in the filter statement. Editing my post.

- Josh Carlson
Kendall County GIS
0 Kudos
RingwayJacobs
New Contributor II

Ah that works better. I've also used if (IsEmpty(filt_fs)) { instead and now get the 'Inspection Dates' title coming up. Just no actual date info. Is there somewhere I need to declare that?

0 Kudos
JohannesLindner
MVP Frequent Contributor

A general form could look like this:

// define which attributes to return
var return_attributes = ["lastinspdate", "nextinspdate"]

// load the featureset
var fs = FeatureSetByName($map, "InspInspectionDates", ['central_asset_id', 'lastinspdate', 'nextinspdate'], false)

// filter for id and dates in future
var ref_id = $feature["CENTRAL_AS"]
var dt = Now()
var filt = "central_asset_id = @ref_id AND nextinspdate > @DT"

// get the next future inspection date
var filtered_fs = OrderBy(Filter(fs, filt), "nextinspdate")
var cou = First(filtered_fs)
if(cou == null) { return "No future inspection found" }

var all_attributes = Dictionary(Text(cou))["attributes"]
var popup_lines = []
for(var a in return_attributes) {
    var att = return_attributes[a]
    var val = all_attributes[att]
    var line = `${att}: ${val}`
    Push(popup_lines, line)
}
return Concatenate(popup_lines, TextFormatting.NewLine)

 

This is useful if you want to keep the expression expandable to include more/less fields later. If you don't need that, you can just hardcode it:

// define which attributes to return
var return_attributes = ["lastinspdate", "nextinspdate"]

// load the featureset
var fs = FeatureSetByName($map, "InspInspectionDates", ['central_asset_id', 'lastinspdate', 'nextinspdate'], false)

// filter for id and dates in future
var ref_id = $feature["CENTRAL_AS"]
var dt = Now()
var filt = "central_asset_id = @ref_id AND nextinspdate > @DT"

// get the next future inspection date
var filtered_fs = OrderBy(Filter(fs, filt), "nextinspdate")
var cou = First(filtered_fs)
if(cou == null) { return "No future inspection found" }

var popup_lines = [
    `Last inspection: ${cou["lastinspdate"]}`,
    `Next inspection: ${cou["nextinspdate"]}`,
]
return Concatenate(popup_lines, TextFormatting.NewLine)

Have a great day!
Johannes
0 Kudos
RingwayJacobs
New Contributor II

Thanks for the really quick response Johannes. I like the idea of having the code expandable so the second option is definitely better. 

For some reason though, its is returning the if null text for all records. I've tried changing @DT to @dt to see is that made any difference but it didn't. Also tried using Today() instead of Now().

There are definitely dates in the future in the source so not sure why they are not being picked up...

0 Kudos