Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions indicator/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"valuePrefix": "",
"valueSuffix": "",
"valueUnitPrefix": true,
"aggressiveRefresh": false,
"refreshIntervalValue": 5,
"refreshIntervalUnit": "minutes"
}
2 changes: 1 addition & 1 deletion indicator/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions indicator/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface Config {

showLastUpdateTime: boolean
lastUpdateTimeTextColor: string
aggressiveRefresh: boolean

refreshIntervalValue: number
refreshIntervalUnit: "seconds" | "minutes" | "hours"
Expand Down
68 changes: 65 additions & 3 deletions indicator/src/runtime/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,50 @@ interface FeaturePagerProps {
onNext: () => void
}

type ArcadeRefreshableDataSource = ArcGISQueriableDataSource & {
getMainDataSource?: () => ArcGISQueriableDataSource
getDataSourceJson?: () => { arcadeScript?: string }
setNeedRefresh?: (needRefresh: boolean) => void
ready?: () => Promise<unknown>
arcadeClientLayer?: unknown
arcadeResult?: { layer?: unknown }
}

async function aggressiveRefreshArcadeDataSource (
ds: ArcGISQueriableDataSource | null | undefined
): Promise<void> {
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,
Expand Down Expand Up @@ -328,6 +372,7 @@ export default function Widget (props: AllWidgetProps<IMConfig>) {
const mainDsRef = React.useRef<ArcGISQueriableDataSource | null>(null)
const refDsRef = React.useRef<ArcGISQueriableDataSource | null>(null)
const prevMainValueRef = React.useRef<number | null>(null)
const aggressiveRefreshInFlightRef = React.useRef(false)

// ── Derived fields ─────────────────────────────────────────────────────

Expand Down Expand Up @@ -426,11 +471,28 @@ export default function Widget (props: AllWidgetProps<IMConfig>) {
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 ──────────────────────────────────────────────────

Expand Down
101 changes: 80 additions & 21 deletions indicator/src/setting/setting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
Button,
ButtonGroup,
Select,
Tooltip,
CollapsablePanel,
Tabs,
Tab,
Expand Down Expand Up @@ -310,8 +311,8 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
dataSource={
props.useDataSources?.[0]
? DataSourceManager.getInstance().getDataSource(
props.useDataSources[0].dataSourceId
)
props.useDataSources[0].dataSourceId
)
: null
}
onChange={(expression: IMSqlExpression) => {
Expand Down Expand Up @@ -441,13 +442,13 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
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"}
>
Expand Down Expand Up @@ -478,13 +479,13 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
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
}
/>
Expand Down Expand Up @@ -599,8 +600,8 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
dataSource={
props.useDataSources?.[1]
? DataSourceManager.getInstance().getDataSource(
props.useDataSources[1].dataSourceId
)
props.useDataSources[1].dataSourceId
)
: null
}
onChange={(expression: IMSqlExpression) => {
Expand Down Expand Up @@ -717,11 +718,11 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
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
])
}
/>
</SettingRow>
Expand Down Expand Up @@ -830,6 +831,64 @@ export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
}}
/>
</SettingRow>
<SettingRow
label={(
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: 6
}}
>
<Tooltip
title={props.intl.formatMessage({
id: "aggressiveRefreshHelpTooltip",
defaultMessage:
"Primarily for Arcade data sources. This mode is more expensive and may increase refresh workload."
})}
>
<span
style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: 16,
height: 16,
borderRadius: "50%",
border: "1px solid currentColor",
fontSize: 11,
fontWeight: 700,
lineHeight: 1,
cursor: "help",
userSelect: "none"
}}
>
i
</span>
</Tooltip>
<span>
{props.intl.formatMessage({
id: "aggressiveRefresh",
defaultMessage: "Aggressive refresh"
})}
</span>
</div>
)}
flow={"no-wrap"}
>
<Switch
checked={config.aggressiveRefresh ?? false}
onChange={() => {
onSettingChange({
id,
config: {
...config,
aggressiveRefresh: !(config.aggressiveRefresh ?? false)
}
})
}}
/>
</SettingRow>
<SettingRow
label={props.intl.formatMessage({
id: "refreshInterval",
Expand Down
Loading