diff --git a/indicator/config.json b/indicator/config.json index b2ae923..b9c003a 100644 --- a/indicator/config.json +++ b/indicator/config.json @@ -20,6 +20,7 @@ "valuePrefix": "", "valueSuffix": "", "valueUnitPrefix": true, + "aggressiveRefresh": false, "refreshIntervalValue": 5, "refreshIntervalUnit": "minutes" } \ No newline at end of file diff --git a/indicator/package.json b/indicator/package.json index 0701aba..b0a2a2a 100644 --- a/indicator/package.json +++ b/indicator/package.json @@ -1,6 +1,6 @@ { "name": "exb-indicator", - "version": "1.0.2", + "version": "1.0.3", "description": "A simple indicator widget for Experience Builder, based on the indicator widget from ArcGIS Dashboards.", "author": "Lucius Creamer", "license": "MIT", diff --git a/indicator/src/config.ts b/indicator/src/config.ts index b70fa76..711ed53 100644 --- a/indicator/src/config.ts +++ b/indicator/src/config.ts @@ -73,6 +73,7 @@ export interface Config { showLastUpdateTime: boolean lastUpdateTimeTextColor: string + aggressiveRefresh: boolean refreshIntervalValue: number refreshIntervalUnit: "seconds" | "minutes" | "hours" diff --git a/indicator/src/runtime/widget.tsx b/indicator/src/runtime/widget.tsx index 2b461de..c75beca 100644 --- a/indicator/src/runtime/widget.tsx +++ b/indicator/src/runtime/widget.tsx @@ -259,6 +259,50 @@ interface FeaturePagerProps { onNext: () => void } +type ArcadeRefreshableDataSource = ArcGISQueriableDataSource & { + getMainDataSource?: () => ArcGISQueriableDataSource + getDataSourceJson?: () => { arcadeScript?: string } + setNeedRefresh?: (needRefresh: boolean) => void + ready?: () => Promise + arcadeClientLayer?: unknown + arcadeResult?: { layer?: unknown } +} + +async function aggressiveRefreshArcadeDataSource ( + ds: ArcGISQueriableDataSource | null | undefined +): Promise { + const target = ds as ArcadeRefreshableDataSource | null | undefined + if (!target) return + + const dsJson = target.getDataSourceJson?.() + if (!dsJson?.arcadeScript) return + + const main = (target.getMainDataSource?.() ?? target) as ArcadeRefreshableDataSource + const reset = (item: ArcadeRefreshableDataSource | null | undefined) => { + if (!item) return + item.arcadeClientLayer = null + item.arcadeResult = undefined + item.setNeedRefresh?.(true) + } + + reset(target) + if (main !== target) { + reset(main) + } + + try { + // Re-run DS ready lifecycle to force re-execution of the Arcade script. + await main.ready?.() + } catch (err) { + console.warn("Indicator: aggressive Arcade refresh failed", err) + } finally { + target.setNeedRefresh?.(false) + if (main !== target) { + main.setNeedRefresh?.(false) + } + } +} + const FeaturePager = ({ current, total, @@ -328,6 +372,7 @@ export default function Widget (props: AllWidgetProps) { const mainDsRef = React.useRef(null) const refDsRef = React.useRef(null) const prevMainValueRef = React.useRef(null) + const aggressiveRefreshInFlightRef = React.useRef(false) // ── Derived fields ───────────────────────────────────────────────────── @@ -426,11 +471,28 @@ export default function Widget (props: AllWidgetProps) { React.useEffect(() => { if (!isConfigured) return const id = setInterval(() => { - setMainTrigger((v) => v + 1) - setRefTrigger((v) => v + 1) + if (!(config.aggressiveRefresh ?? false)) { + setMainTrigger((v) => v + 1) + setRefTrigger((v) => v + 1) + return + } + + if (aggressiveRefreshInFlightRef.current) return + aggressiveRefreshInFlightRef.current = true + + const run = async () => { + await aggressiveRefreshArcadeDataSource(mainDsRef.current) + await aggressiveRefreshArcadeDataSource(refDsRef.current) + setMainTrigger((v) => v + 1) + setRefTrigger((v) => v + 1) + } + + void run().finally(() => { + aggressiveRefreshInFlightRef.current = false + }) }, refreshMs) return () => { clearInterval(id) } - }, [isConfigured, refreshMs]) + }, [isConfigured, refreshMs, config.aggressiveRefresh]) // ── Main query effect ────────────────────────────────────────────────── diff --git a/indicator/src/setting/setting.tsx b/indicator/src/setting/setting.tsx index 936a756..7d05aa3 100644 --- a/indicator/src/setting/setting.tsx +++ b/indicator/src/setting/setting.tsx @@ -21,6 +21,7 @@ import { Button, ButtonGroup, Select, + Tooltip, CollapsablePanel, Tabs, Tab, @@ -310,8 +311,8 @@ export default function Setting (props: AllWidgetSettingProps) { dataSource={ props.useDataSources?.[0] ? DataSourceManager.getInstance().getDataSource( - props.useDataSources[0].dataSourceId - ) + props.useDataSources[0].dataSourceId + ) : null } onChange={(expression: IMSqlExpression) => { @@ -441,13 +442,13 @@ export default function Setting (props: AllWidgetSettingProps) { label={ config.indType === "Statistic" ? props.intl.formatMessage({ - id: "selectField", - defaultMessage: "Field" - }) + id: "selectField", + defaultMessage: "Field" + }) : props.intl.formatMessage({ - id: "selectValueField", - defaultMessage: "Value Field" - }) + id: "selectValueField", + defaultMessage: "Value Field" + }) } flow={"wrap"} > @@ -478,13 +479,13 @@ export default function Setting (props: AllWidgetSettingProps) { types={ (config.indType === "Statistic" && config.mainStatisticType !== "count") || - config.indType === "Feature" + config.indType === "Feature" ? Immutable.from([ - "NUMBER" as JimuFieldType, - "DATE" as JimuFieldType, - "DATE_ONLY" as JimuFieldType, - "TIME_ONLY" as JimuFieldType - ]) + "NUMBER" as JimuFieldType, + "DATE" as JimuFieldType, + "DATE_ONLY" as JimuFieldType, + "TIME_ONLY" as JimuFieldType + ]) : undefined } /> @@ -599,8 +600,8 @@ export default function Setting (props: AllWidgetSettingProps) { dataSource={ props.useDataSources?.[1] ? DataSourceManager.getInstance().getDataSource( - props.useDataSources[1].dataSourceId - ) + props.useDataSources[1].dataSourceId + ) : null } onChange={(expression: IMSqlExpression) => { @@ -717,11 +718,11 @@ export default function Setting (props: AllWidgetSettingProps) { config.refStatisticType === "count" ? undefined : Immutable.from([ - "NUMBER" as JimuFieldType, - "DATE" as JimuFieldType, - "DATE_ONLY" as JimuFieldType, - "TIME_ONLY" as JimuFieldType - ]) + "NUMBER" as JimuFieldType, + "DATE" as JimuFieldType, + "DATE_ONLY" as JimuFieldType, + "TIME_ONLY" as JimuFieldType + ]) } /> @@ -830,6 +831,64 @@ export default function Setting (props: AllWidgetSettingProps) { }} /> + + + + i + + + + {props.intl.formatMessage({ + id: "aggressiveRefresh", + defaultMessage: "Aggressive refresh" + })} + + + )} + flow={"no-wrap"} + > + { + onSettingChange({ + id, + config: { + ...config, + aggressiveRefresh: !(config.aggressiveRefresh ?? false) + } + }) + }} + /> +