Skip to content

Commit f601ab6

Browse files
authored
Subsetter testing improvements and fixes (#30)
* bugfix: spatial constraints not ready * 0.0.152 * bugfix: add debugging * 0.0.153 * bugfix: more debugging * 0.0.154 * remove debugging * 0.0.155 * remove watch * 0.0.156 * resolve issues * 0.0.157 * Filter out non-configured giovanni variables * 0.0.158 * handle groups in giovanni variable names * 0.0.159 * drop help icons and update text * 0.0.160 * feature: datepicker sidebar slots * feature(badge): add info variant * feature(dialog): borders * 0.0.161 * include composed-offset-position * 0.0.162 * feature(date-picker): switch to 24 hour time, clean up date handling and add tests * feature(date-picker): preserve time * 0.0.163 * fix warning about datepicker using inefficient updates * 0.0.164 * feature(date-picker): attempt to fix additional day * chore: formatting * 0.0.165 * feature: add clear functionality * 0.0.166 * feature(subsetter): add long name and units to variable list * feature(subsetter): handle no granules and fix dialog sizing * handle loading and error states for collection * feature(date-picker): allow end of day to be customized * 0.0.167 * multiple variable plot support (#31)
1 parent 0c2de01 commit f601ab6

29 files changed

Lines changed: 592 additions & 202 deletions

docs/pages/components/data-subsetter.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,19 @@ The `<terra-data-subsetter>` component provides a complete UI for subsetting and
4545
### Basic Usage
4646

4747
```html:preview
48-
<terra-data-subsetter short-name="OMAERO" version="003"></terra-data-subsetter>
48+
<terra-data-subsetter short-name="M2I3NPASM" version="5.12.4"></terra-data-subsetter>
49+
```
50+
51+
### Giovanni services for CSV and TIFF outputs
52+
53+
```html:preview
54+
<terra-data-subsetter short-name="FLDAS_NOAHMP001_G_CA_D" version="001"></terra-data-subsetter>
55+
```
56+
57+
### Error handling for collections with no granules
58+
59+
```html:preview
60+
<terra-data-subsetter short-name="XAERDT_L2_MODIS_Aqua" version="1"></terra-data-subsetter>
4961
```
5062

5163
### No collection (enables Collection search)

docs/pages/components/time-series.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ By default, the time series component automatically fetches new data when users
6464
</terra-login>
6565
```
6666

67+
## Plot Multiple Variables
68+
69+
Use `variable-entry-ids` to request and render multiple variables in one chart. Each variable is fetched and cached independently, and each response is plotted as its own line.
70+
71+
```html:preview
72+
<terra-login style="width: 100%">
73+
<span slot="loading">Loading...please wait</span>
74+
75+
<terra-time-series
76+
slot="logged-in"
77+
variable-entry-ids='["GPM_3IMERGDF_07_precipitation", "GPM_3IMERGDE_07_precipitation"]'
78+
start-date="01/01/2019"
79+
end-date="03/01/2019"
80+
location="62,5,95,40"
81+
></terra-time-series>
82+
83+
<p slot="logged-out">Please login to view this plot</p>
84+
</terra-login>
85+
```
86+
6787
```jsx:react
6888
import TerraTimeSeries from '@nasa-terra/components/dist/react/time-series'
6989

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@nasa-terra/components",
33
"description": "A collection of NASA Earthdata components",
4-
"version": "0.0.166",
4+
"version": "0.0.167",
55
"homepage": "https://github.com/nasa/terra-ui-components",
66
"author": "NASA GES DISC <https://disc.gsfc.nasa.gov>",
77
"license": "MIT",

packages/create-terra-ui-app/boilerplates/nextjs/framework.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export async function create(nextTask, outputDir, boilerplatesDir, appName) {
6767
"import { setBasePath } from '@nasa-terra/components/dist/utilities/base-path.js'"
6868

6969
// Create the setBasePath call with comments (with newline before it)
70-
const setBasePathCall = `\n/**\n * Sets the base path to the Terra UI CDN\n *\n * If you'd rather host the assets locally, you should setup a build task to copy the assets locally and\n * set the base path to your local public folder\n * (see https://terra-ui.netlify.app/frameworks/react/#installation for more information)\n */\nsetBasePath('https://cdn.jsdelivr.net/npm/@nasa-terra/components@0.0.166/cdn/')`
70+
const setBasePathCall = `\n/**\n * Sets the base path to the Terra UI CDN\n *\n * If you'd rather host the assets locally, you should setup a build task to copy the assets locally and\n * set the base path to your local public folder\n * (see https://terra-ui.netlify.app/frameworks/react/#installation for more information)\n */\nsetBasePath('https://cdn.jsdelivr.net/npm/@nasa-terra/components@0.0.167/cdn/')`
7171

7272
// Reconstruct the file: CSS import at top, then existing content with setBasePath import and call inserted
7373
const newLines = [...lines]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
55
[project]
66
name = "terra_ui_components"
77
dependencies = ["anywidget>=0.9"]
8-
version = "0.0.166"
8+
version = "0.0.167"
99
readme = "README.md"
1010
description = "NASA Terra UI Components Library"
1111
requires-python = ">=3.8"

src/components/data-access/data-access.component.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import TerraButton from '../button/button.component.js'
3939
import type { TerraSelectEvent } from '../../events/terra-select.js'
4040
import { getDataAccessNotebook } from './notebooks/data-access-notebook.js'
4141
import { sendDataToJupyterNotebook } from '../../lib/jupyter.js'
42+
import TerraAlert from '../alert/alert.component.js'
4243

4344
/**
4445
* @summary Discover and export collection granules with search, temporal, spatial, and cloud cover filters.
@@ -69,6 +70,7 @@ export default class TerraDataAccess extends TerraElement {
6970
'terra-menu-item': TerraMenuItem,
7071
'terra-button': TerraButton,
7172
'terra-data-grid': TerraDataGrid,
73+
'terra-alert': TerraAlert,
7274
}
7375

7476
@property({ reflect: true, attribute: 'short-name' })
@@ -615,6 +617,18 @@ export default class TerraDataAccess extends TerraElement {
615617
}
616618

617619
render() {
620+
if (this.#controller.sampling?.firstGranules.count === 0) {
621+
return html`
622+
<terra-alert variant="warning" open appearance="white">
623+
<strong>No granules found.</strong>
624+
<p>
625+
This collection does not have any granules available to access
626+
or subset.
627+
</p>
628+
</terra-alert>
629+
`
630+
}
631+
618632
return html`
619633
<div class="filters-compact">
620634
<div class="search-row">

src/components/data-access/data-access.controller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,12 @@ export class DataAccessController {
175175
}
176176

177177
get granuleMaxDate() {
178-
if (!this.#sampling?.lastGranules) {
178+
const granules = this.#sampling?.lastGranules?.items
179+
180+
if (!granules?.length) {
179181
return null
180182
}
181183

182-
const granules = this.#sampling.lastGranules.items
183184
return (
184185
granules[granules.length - 1].temporalExtent?.rangeDateTime
185186
?.endingDateTime ?? null

src/components/data-subsetter/data-subsetter.component.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import {
2424
} from '../../utilities/mimetypes.js'
2525
import { watch } from '../../internal/watch.js'
2626
import { debounce } from '../../internal/debounce.js'
27-
import type { CmrSearchResult } from '../../metadata-catalog/types.js'
27+
import type {
28+
CmrSearchResult,
29+
VariableDetails,
30+
} from '../../metadata-catalog/types.js'
2831
import type { LatLng, LatLngBounds } from 'leaflet'
2932
import { MapEventType } from '../map/type.js'
3033
import { AuthController } from '../../auth/auth.controller.js'
@@ -109,6 +112,9 @@ export default class TerraDataSubsetter extends TerraElement {
109112
@state()
110113
collectionWithServices?: CollectionWithAvailableServices
111114

115+
@state()
116+
variableDetails?: Array<VariableDetails>
117+
112118
@state()
113119
selectedVariables: Variable[] = []
114120

@@ -317,15 +323,6 @@ export default class TerraDataSubsetter extends TerraElement {
317323
this.collectionWithServices?.collection?.EntryTitle ?? 'Download Data'
318324

319325
if (!this.collectionWithServices) {
320-
if (this.controller.fetchCollectionTask.status === TaskStatus.PENDING) {
321-
return html`
322-
<div class="loading-collection">
323-
<terra-loader indeterminate variant="small"></terra-loader>
324-
<span>Loading</span>
325-
</div>
326-
`
327-
}
328-
329326
if (this.controller.fetchCollectionTask.status === TaskStatus.ERROR) {
330327
return html`
331328
<terra-alert open variant="danger" appearance="white">
@@ -579,10 +576,11 @@ export default class TerraDataSubsetter extends TerraElement {
579576
</terra-menu>
580577
</terra-dropdown>
581578
579+
<!--
582580
<terra-button
583581
outline
584582
@click=${() =>
585-
this.#handleJupyterNotebookClick()}
583+
this.#handleJupyterNotebookClick()}
586584
>
587585
<terra-icon
588586
name="outline-code-bracket"
@@ -592,6 +590,7 @@ export default class TerraDataSubsetter extends TerraElement {
592590
></terra-icon>
593591
Open in Jupyter Notebook
594592
</terra-button>
593+
-->
595594
</div>
596595
`
597596
: nothing}
@@ -1058,6 +1057,11 @@ export default class TerraDataSubsetter extends TerraElement {
10581057
}
10591058

10601059
#renderOutputFormatSelection() {
1060+
// TODO: refactor component to use output formats from individual services, then each service can have a map of output format friendly names
1061+
const hasGiovanniService = this.collectionWithServices?.services?.some(
1062+
s => s.name.indexOf('giovanni') !== -1
1063+
)
1064+
10611065
return html`
10621066
<terra-accordion>
10631067
<div slot="summary">
@@ -1068,7 +1072,12 @@ export default class TerraDataSubsetter extends TerraElement {
10681072
slot="summary-right"
10691073
style="display: flex; align-items: center; gap: 10px;"
10701074
>
1071-
<span>${getFriendlyNameForMimeType(this.selectedFormat)}</span>
1075+
<span
1076+
>${getFriendlyNameForMimeType(
1077+
this.selectedFormat,
1078+
hasGiovanniService
1079+
)}</span
1080+
>
10721081
10731082
<button class="reset-btn" @click=${this.#resetFormatSelection}>
10741083
Reset
@@ -1109,7 +1118,10 @@ export default class TerraDataSubsetter extends TerraElement {
11091118
@change=${() =>
11101119
(this.selectedFormat = format)}
11111120
/>
1112-
${getFriendlyNameForMimeType(format)}
1121+
${getFriendlyNameForMimeType(
1122+
format,
1123+
hasGiovanniService
1124+
)}
11131125
</label>
11141126
`
11151127
)
@@ -1168,8 +1180,11 @@ export default class TerraDataSubsetter extends TerraElement {
11681180
end-label="End Date"
11691181
.minDate=${this.granuleMinDate ?? defaultStartDate}
11701182
.maxDate=${this.granuleMaxDate ?? defaultEndDate}
1171-
.startDate=${this.selectedDateRange.startDate}
1172-
.endDate=${this.selectedDateRange.endDate}
1183+
.startDate=${this.selectedDateRange.startDate ??
1184+
this.granuleMinDate}
1185+
.endDate=${this.selectedDateRange.endDate ??
1186+
this.granuleMaxDate}
1187+
.useEndOfDay=${this.#isGiovanniFormat() ? false : true}
11731188
@terra-date-range-change=${this.#handleDateChange}
11741189
></terra-date-picker>
11751190
</div>
@@ -1654,6 +1669,15 @@ export default class TerraDataSubsetter extends TerraElement {
16541669
const groupPath = [...path, key].join('/')
16551670
if (value.__isLeaf) {
16561671
// Leaf node (variable)
1672+
// Look up the long name from variableDetails
1673+
const variableDetail = this.variableDetails?.find(
1674+
d =>
1675+
d.name === value.__variable.name &&
1676+
d.longName !== value.__variable.name // if the long name is the same as the variable name, we'll just use the variable name
1677+
)
1678+
const variableName = variableDetail?.longName
1679+
? `${key} = ${variableDetail.longName}${variableDetail.units ? ` (${variableDetail.units})` : ''}`
1680+
: key
16571681
return html`
16581682
<div class="option-row">
16591683
<label class="checkbox-option">
@@ -1671,7 +1695,7 @@ export default class TerraDataSubsetter extends TerraElement {
16711695
value.__variable
16721696
)}
16731697
/>
1674-
<span>${key}</span>
1698+
<span>${variableName}</span>
16751699
</label>
16761700
</div>
16771701
`
@@ -2063,10 +2087,11 @@ export default class TerraDataSubsetter extends TerraElement {
20632087
</terra-menu>
20642088
</terra-dropdown>
20652089
2090+
<!--
20662091
<terra-button
20672092
outline
20682093
@click=${() =>
2069-
this.#handleJupyterNotebookClick()}
2094+
this.#handleJupyterNotebookClick()}
20702095
>
20712096
<terra-icon
20722097
name="outline-code-bracket"
@@ -2076,7 +2101,7 @@ export default class TerraDataSubsetter extends TerraElement {
20762101
></terra-icon>
20772102
Open in Jupyter Notebook
20782103
</terra-button>
2079-
</div>
2104+
--></div>
20802105
`
20812106
: nothing}
20822107
${this.controller.currentJob!.status === 'running'
@@ -2536,6 +2561,10 @@ export default class TerraDataSubsetter extends TerraElement {
25362561
}
25372562

25382563
#renderDataAccessModeSelection() {
2564+
if (!this.controller.hasGranules) {
2565+
return nothing
2566+
}
2567+
25392568
return html`
25402569
<div class="mode-selection">
25412570
<div class="mode-options">

src/components/data-subsetter/data-subsetter.controller.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Fuse from 'fuse.js'
1616
import type {
1717
CmrSamplingOfGranules,
1818
MetadataCatalogInterface,
19+
VariableDetails,
1920
} from '../../metadata-catalog/types.js'
2021
import { CmrCatalog } from '../../metadata-catalog/cmr-catalog.js'
2122
import { convertVariableEntryIdToGiovanniFormat } from '../../utilities/giovanni.js'
@@ -28,6 +29,7 @@ export class DataSubsetterController {
2829
searchCmrTask: Task<[string | undefined, string], any | undefined>
2930
samplingTask: Task<[string | undefined], CmrSamplingOfGranules | undefined>
3031
giovanniConfiguredVariablesTask: Task<[], Set<string> | undefined>
32+
variableDetailsTask: Task<[string | undefined], VariableDetails[] | undefined>
3133
currentJob: SubsetJobStatus | null
3234

3335
#host: ReactiveControllerHost & TerraDataSubsetter
@@ -52,11 +54,44 @@ export class DataSubsetterController {
5254
)
5355
: undefined
5456

57+
// Trigger variable details fetch when collection is fetched
58+
if (this.#host.collectionWithServices?.conceptId) {
59+
this.variableDetailsTask.run()
60+
}
61+
5562
return this.#host.collectionWithServices
5663
},
5764
args: (): [string | undefined] => [this.#host.collectionEntryId],
5865
})
5966

67+
this.variableDetailsTask = new Task(host, {
68+
task: async ([conceptId], { signal }) => {
69+
if (!conceptId) {
70+
this.#host.variableDetails = undefined
71+
return undefined
72+
}
73+
74+
try {
75+
const details = await this.#metadataCatalog.getVariablesDetails(
76+
conceptId,
77+
{
78+
signal,
79+
}
80+
)
81+
82+
this.#host.variableDetails = details?.collection?.variables?.items
83+
return this.#host.variableDetails
84+
} catch (error) {
85+
console.warn('Failed to fetch variable details:', error)
86+
return undefined
87+
}
88+
},
89+
args: (): [string | undefined] => [
90+
this.#host.collectionWithServices?.conceptId,
91+
],
92+
autoRun: false,
93+
})
94+
6095
this.searchCmrTask = new Task(host, {
6196
task: async ([searchQuery, searchType], { signal }) => {
6297
if (!searchQuery) {
@@ -399,6 +434,13 @@ export class DataSubsetterController {
399434
return labels
400435
}
401436

437+
get hasGranules() {
438+
return (
439+
this.#sampling?.firstGranules?.count &&
440+
this.#sampling.firstGranules.count > 0
441+
)
442+
}
443+
402444
get granuleMinDate() {
403445
if (!this.#sampling?.firstGranules) {
404446
return null
@@ -411,11 +453,12 @@ export class DataSubsetterController {
411453
}
412454

413455
get granuleMaxDate() {
414-
if (!this.#sampling?.lastGranules) {
456+
const granules = this.#sampling?.lastGranules?.items
457+
458+
if (!granules?.length) {
415459
return null
416460
}
417461

418-
const granules = this.#sampling.lastGranules.items
419462
return (
420463
granules[granules.length - 1].temporalExtent?.rangeDateTime
421464
?.endingDateTime ?? null

0 commit comments

Comments
 (0)