Skip to content

Commit c0c8191

Browse files
Merge pull request #21 from SunshineLuke90/indicator-aggressive-refresh
Added "aggressive refresh" for arcade datasources
2 parents cc81e4e + ca6face commit c0c8191

5 files changed

Lines changed: 148 additions & 25 deletions

File tree

indicator/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"valuePrefix": "",
2121
"valueSuffix": "",
2222
"valueUnitPrefix": true,
23+
"aggressiveRefresh": false,
2324
"refreshIntervalValue": 5,
2425
"refreshIntervalUnit": "minutes"
2526
}

indicator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "exb-indicator",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "A simple indicator widget for Experience Builder, based on the indicator widget from ArcGIS Dashboards.",
55
"author": "Lucius Creamer",
66
"license": "MIT",

indicator/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface Config {
7373

7474
showLastUpdateTime: boolean
7575
lastUpdateTimeTextColor: string
76+
aggressiveRefresh: boolean
7677

7778
refreshIntervalValue: number
7879
refreshIntervalUnit: "seconds" | "minutes" | "hours"

indicator/src/runtime/widget.tsx

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,50 @@ interface FeaturePagerProps {
259259
onNext: () => void
260260
}
261261

262+
type ArcadeRefreshableDataSource = ArcGISQueriableDataSource & {
263+
getMainDataSource?: () => ArcGISQueriableDataSource
264+
getDataSourceJson?: () => { arcadeScript?: string }
265+
setNeedRefresh?: (needRefresh: boolean) => void
266+
ready?: () => Promise<unknown>
267+
arcadeClientLayer?: unknown
268+
arcadeResult?: { layer?: unknown }
269+
}
270+
271+
async function aggressiveRefreshArcadeDataSource (
272+
ds: ArcGISQueriableDataSource | null | undefined
273+
): Promise<void> {
274+
const target = ds as ArcadeRefreshableDataSource | null | undefined
275+
if (!target) return
276+
277+
const dsJson = target.getDataSourceJson?.()
278+
if (!dsJson?.arcadeScript) return
279+
280+
const main = (target.getMainDataSource?.() ?? target) as ArcadeRefreshableDataSource
281+
const reset = (item: ArcadeRefreshableDataSource | null | undefined) => {
282+
if (!item) return
283+
item.arcadeClientLayer = null
284+
item.arcadeResult = undefined
285+
item.setNeedRefresh?.(true)
286+
}
287+
288+
reset(target)
289+
if (main !== target) {
290+
reset(main)
291+
}
292+
293+
try {
294+
// Re-run DS ready lifecycle to force re-execution of the Arcade script.
295+
await main.ready?.()
296+
} catch (err) {
297+
console.warn("Indicator: aggressive Arcade refresh failed", err)
298+
} finally {
299+
target.setNeedRefresh?.(false)
300+
if (main !== target) {
301+
main.setNeedRefresh?.(false)
302+
}
303+
}
304+
}
305+
262306
const FeaturePager = ({
263307
current,
264308
total,
@@ -328,6 +372,7 @@ export default function Widget (props: AllWidgetProps<IMConfig>) {
328372
const mainDsRef = React.useRef<ArcGISQueriableDataSource | null>(null)
329373
const refDsRef = React.useRef<ArcGISQueriableDataSource | null>(null)
330374
const prevMainValueRef = React.useRef<number | null>(null)
375+
const aggressiveRefreshInFlightRef = React.useRef(false)
331376

332377
// ── Derived fields ─────────────────────────────────────────────────────
333378

@@ -426,11 +471,28 @@ export default function Widget (props: AllWidgetProps<IMConfig>) {
426471
React.useEffect(() => {
427472
if (!isConfigured) return
428473
const id = setInterval(() => {
429-
setMainTrigger((v) => v + 1)
430-
setRefTrigger((v) => v + 1)
474+
if (!(config.aggressiveRefresh ?? false)) {
475+
setMainTrigger((v) => v + 1)
476+
setRefTrigger((v) => v + 1)
477+
return
478+
}
479+
480+
if (aggressiveRefreshInFlightRef.current) return
481+
aggressiveRefreshInFlightRef.current = true
482+
483+
const run = async () => {
484+
await aggressiveRefreshArcadeDataSource(mainDsRef.current)
485+
await aggressiveRefreshArcadeDataSource(refDsRef.current)
486+
setMainTrigger((v) => v + 1)
487+
setRefTrigger((v) => v + 1)
488+
}
489+
490+
void run().finally(() => {
491+
aggressiveRefreshInFlightRef.current = false
492+
})
431493
}, refreshMs)
432494
return () => { clearInterval(id) }
433-
}, [isConfigured, refreshMs])
495+
}, [isConfigured, refreshMs, config.aggressiveRefresh])
434496

435497
// ── Main query effect ──────────────────────────────────────────────────
436498

indicator/src/setting/setting.tsx

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
Button,
2222
ButtonGroup,
2323
Select,
24+
Tooltip,
2425
CollapsablePanel,
2526
Tabs,
2627
Tab,
@@ -310,8 +311,8 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
310311
dataSource={
311312
props.useDataSources?.[0]
312313
? DataSourceManager.getInstance().getDataSource(
313-
props.useDataSources[0].dataSourceId
314-
)
314+
props.useDataSources[0].dataSourceId
315+
)
315316
: null
316317
}
317318
onChange={(expression: IMSqlExpression) => {
@@ -441,13 +442,13 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
441442
label={
442443
config.indType === "Statistic"
443444
? props.intl.formatMessage({
444-
id: "selectField",
445-
defaultMessage: "Field"
446-
})
445+
id: "selectField",
446+
defaultMessage: "Field"
447+
})
447448
: props.intl.formatMessage({
448-
id: "selectValueField",
449-
defaultMessage: "Value Field"
450-
})
449+
id: "selectValueField",
450+
defaultMessage: "Value Field"
451+
})
451452
}
452453
flow={"wrap"}
453454
>
@@ -478,13 +479,13 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
478479
types={
479480
(config.indType === "Statistic" &&
480481
config.mainStatisticType !== "count") ||
481-
config.indType === "Feature"
482+
config.indType === "Feature"
482483
? Immutable.from([
483-
"NUMBER" as JimuFieldType,
484-
"DATE" as JimuFieldType,
485-
"DATE_ONLY" as JimuFieldType,
486-
"TIME_ONLY" as JimuFieldType
487-
])
484+
"NUMBER" as JimuFieldType,
485+
"DATE" as JimuFieldType,
486+
"DATE_ONLY" as JimuFieldType,
487+
"TIME_ONLY" as JimuFieldType
488+
])
488489
: undefined
489490
}
490491
/>
@@ -599,8 +600,8 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
599600
dataSource={
600601
props.useDataSources?.[1]
601602
? DataSourceManager.getInstance().getDataSource(
602-
props.useDataSources[1].dataSourceId
603-
)
603+
props.useDataSources[1].dataSourceId
604+
)
604605
: null
605606
}
606607
onChange={(expression: IMSqlExpression) => {
@@ -717,11 +718,11 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
717718
config.refStatisticType === "count"
718719
? undefined
719720
: Immutable.from([
720-
"NUMBER" as JimuFieldType,
721-
"DATE" as JimuFieldType,
722-
"DATE_ONLY" as JimuFieldType,
723-
"TIME_ONLY" as JimuFieldType
724-
])
721+
"NUMBER" as JimuFieldType,
722+
"DATE" as JimuFieldType,
723+
"DATE_ONLY" as JimuFieldType,
724+
"TIME_ONLY" as JimuFieldType
725+
])
725726
}
726727
/>
727728
</SettingRow>
@@ -830,6 +831,64 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
830831
}}
831832
/>
832833
</SettingRow>
834+
<SettingRow
835+
label={(
836+
<div
837+
style={{
838+
display: "inline-flex",
839+
alignItems: "center",
840+
gap: 6
841+
}}
842+
>
843+
<Tooltip
844+
title={props.intl.formatMessage({
845+
id: "aggressiveRefreshHelpTooltip",
846+
defaultMessage:
847+
"Primarily for Arcade data sources. This mode is more expensive and may increase refresh workload."
848+
})}
849+
>
850+
<span
851+
style={{
852+
display: "inline-flex",
853+
alignItems: "center",
854+
justifyContent: "center",
855+
width: 16,
856+
height: 16,
857+
borderRadius: "50%",
858+
border: "1px solid currentColor",
859+
fontSize: 11,
860+
fontWeight: 700,
861+
lineHeight: 1,
862+
cursor: "help",
863+
userSelect: "none"
864+
}}
865+
>
866+
i
867+
</span>
868+
</Tooltip>
869+
<span>
870+
{props.intl.formatMessage({
871+
id: "aggressiveRefresh",
872+
defaultMessage: "Aggressive refresh"
873+
})}
874+
</span>
875+
</div>
876+
)}
877+
flow={"no-wrap"}
878+
>
879+
<Switch
880+
checked={config.aggressiveRefresh ?? false}
881+
onChange={() => {
882+
onSettingChange({
883+
id,
884+
config: {
885+
...config,
886+
aggressiveRefresh: !(config.aggressiveRefresh ?? false)
887+
}
888+
})
889+
}}
890+
/>
891+
</SettingRow>
833892
<SettingRow
834893
label={props.intl.formatMessage({
835894
id: "refreshInterval",

0 commit comments

Comments
 (0)