Skip to content

Commit 9a9f4eb

Browse files
Add big orange warning for multiple dataset detection (#92)
* Add big organge warning for multiple dataset detection * Git add --update fix...
1 parent c184175 commit 9a9f4eb

File tree

8 files changed

+302
-109
lines changed

8 files changed

+302
-109
lines changed

src/components/DatastoreDetail.vue

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,14 @@
8282
:datastore-name="datastoreName"
8383
:current-filters="currentFilters"
8484
:raw-data="filteredData"
85+
:dynamic-filter-options="dynamicFilterOptions"
8586
class="mb-6"
8687
/>
8788

8889
<FilterSelectors
8990
v-model="currentFilters"
9091
:filter-options="filterOptions"
91-
:raw-data="rawData"
92+
:dynamic-filter-options="dynamicFilterOptions"
9293
@clear="clearFilters"
9394
/>
9495

@@ -168,6 +169,45 @@ const filteredData = computed(() => {
168169
return data;
169170
});
170171
172+
const dynamicFilterOptions = computed(() => {
173+
const result: Record<string, string[]> = {};
174+
175+
for (const [column, allOptions] of Object.entries(filterOptions.value)) {
176+
let availableData = rawData.value;
177+
178+
// Apply all OTHER active filters (not the current column)
179+
for (const [filterColumn, filterValues] of Object.entries(currentFilters.value)) {
180+
if (filterColumn !== column && filterValues && filterValues.length > 0) {
181+
availableData = availableData.filter((row: Record<string, any>) => {
182+
const cellValue = row[filterColumn];
183+
return filterValues.some((fv) => {
184+
if (Array.isArray(cellValue))
185+
return cellValue.some((it: any) => String(it).toLowerCase().includes(fv.toLowerCase()));
186+
return String(cellValue || '')
187+
.toLowerCase()
188+
.includes(fv.toLowerCase());
189+
});
190+
});
191+
}
192+
}
193+
194+
// Find which options from this column exist in the filtered data
195+
const validOptions = new Set<string>();
196+
for (const row of availableData) {
197+
const cellValue = row[column];
198+
if (Array.isArray(cellValue)) {
199+
cellValue.forEach((val: any) => validOptions.add(String(val)));
200+
} else if (cellValue !== null && cellValue !== undefined) {
201+
validOptions.add(String(cellValue));
202+
}
203+
}
204+
205+
result[column] = allOptions.filter((option) => validOptions.has(option));
206+
}
207+
208+
return result;
209+
});
210+
171211
const loadDatastore = async () => {
172212
const existingCache = catalogStore.getDatastoreFromCache(datastoreName.value);
173213
if (existingCache && existingCache.data.length > 0) {

src/components/FilterSelectors.vue

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<MultiSelect
88
:model-value="modelValue[column]"
99
@update:model-value="updateFilter(column, $event)"
10-
:options="dynamicFilterOptions[column] || options"
10+
:options="props.dynamicFilterOptions[column] || options"
1111
display="chip"
1212
class="w-full"
1313
filter
@@ -23,14 +23,13 @@
2323
</template>
2424

2525
<script setup lang="ts">
26-
import { computed } from 'vue';
2726
import Button from 'primevue/button';
2827
import MultiSelect from 'primevue/multiselect';
2928
3029
interface Props {
3130
filterOptions: Record<string, string[]>;
3231
modelValue: Record<string, string[]>;
33-
rawData: Array<Record<string, any>>;
32+
dynamicFilterOptions: Record<string, string[]>;
3433
}
3534
3635
interface Emits {
@@ -50,50 +49,6 @@ const formatColumnName = (c: string) =>
5049
})
5150
.join(' ');
5251
53-
// Compute dynamic filter options based on current filter selections
54-
// This ensures users can't select filter combinations that result in 0 rows
55-
const dynamicFilterOptions = computed(() => {
56-
const result: Record<string, string[]> = {};
57-
58-
// For each filter column, compute which options would yield results
59-
for (const [column, allOptions] of Object.entries(props.filterOptions)) {
60-
// Start with raw data
61-
let availableData = props.rawData;
62-
63-
// Apply all OTHER active filters (not the current column)
64-
for (const [filterColumn, filterValues] of Object.entries(props.modelValue)) {
65-
if (filterColumn !== column && filterValues && filterValues.length > 0) {
66-
availableData = availableData.filter((row: Record<string, any>) => {
67-
const cellValue = row[filterColumn];
68-
return filterValues.some((fv) => {
69-
if (Array.isArray(cellValue))
70-
return cellValue.some((it: any) => String(it).toLowerCase().includes(fv.toLowerCase()));
71-
return String(cellValue || '')
72-
.toLowerCase()
73-
.includes(fv.toLowerCase());
74-
});
75-
});
76-
}
77-
}
78-
79-
// Now find which options from this column exist in the filtered data
80-
const validOptions = new Set<string>();
81-
for (const row of availableData) {
82-
const cellValue = row[column];
83-
if (Array.isArray(cellValue)) {
84-
cellValue.forEach((val: any) => validOptions.add(String(val)));
85-
} else if (cellValue !== null && cellValue !== undefined) {
86-
validOptions.add(String(cellValue));
87-
}
88-
}
89-
90-
// Filter the original options to only include valid ones
91-
result[column] = allOptions.filter((option) => validOptions.has(option));
92-
}
93-
94-
return result;
95-
});
96-
9752
const updateFilter = (column: string, value: string[]) => {
9853
const updatedFilters = { ...props.modelValue, [column]: value };
9954
emit('update:modelValue', updatedFilters);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<div
3+
v-if="visible"
4+
class="mb-4 p-3 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded"
5+
>
6+
<div class="flex items-center mb-2">
7+
<i class="pi pi-exclamation-triangle text-orange-600 mr-2"></i>
8+
<strong class="text-orange-800 dark:text-orange-200 text-sm">Multiple Cell Methods Detected:</strong>
9+
</div>
10+
<p class="text-orange-700 dark:text-orange-300 text-sm">
11+
This dataset contains multiple variable cell methods. You will need to filter by
12+
<code class="px-1 py-0.5 bg-orange-100 dark:bg-orange-900 rounded text-xs font-mono">variable_cell_methods</code>
13+
to select the specific temporal aggregation you need before calling
14+
<code class="px-1 py-0.5 bg-orange-100 dark:bg-orange-900 rounded text-xs font-mono">to_dask()</code>.
15+
</p>
16+
</div>
17+
</template>
18+
19+
<script setup lang="ts">
20+
/**
21+
* Component props for MultipleCellMethodsWarning.
22+
*
23+
* Displays a warning message when a single dataset has multiple variable_cell_methods
24+
* options available, indicating the user should apply additional filtering before
25+
* converting to an xarray dataset.
26+
*/
27+
interface Props {
28+
/**
29+
* Whether to display the warning. Should be true when:
30+
* - The user has filtered down to a single dataset (to_dask mode)
31+
* - There are multiple options available for variable_cell_methods
32+
* - xarray mode is enabled
33+
*/
34+
visible: boolean;
35+
}
36+
37+
defineProps<Props>();
38+
</script>

src/components/QuickStartCode.vue

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
<RequiredProjectsWarning :projects="requiredProjects" />
2222

23+
<MultipleCellMethodsWarning :visible="shouldShowCellMethodsWarning" />
24+
2325
<pre class="bg-gray-800 text-green-400 p-3 rounded text-sm overflow-x-auto"><code>{{ quickStartCode }}</code></pre>
2426

2527
<div class="mt-3 relative inline-block">
@@ -65,6 +67,7 @@ import ToggleSwitch from 'primevue/toggleswitch';
6567
import Toast from 'primevue/toast';
6668
import { useToast } from 'primevue/usetoast';
6769
import RequiredProjectsWarning from './RequiredProjectsWarning.vue';
70+
import MultipleCellMethodsWarning from './MultipleCellMethodsWarning.vue';
6871
import LongUrlConfirmDialog from './LongUrlConfirmDialog.vue';
6972
7073
// Props
@@ -92,6 +95,12 @@ interface Props {
9295
* to be an object and may contain fields such as `path` and `file_id`.
9396
*/
9497
rawData: any[];
98+
99+
/**
100+
* Dynamic filter options for each column (filtered based on other active filters).
101+
* Used to determine if there are multiple variable_cell_methods options available.
102+
*/
103+
dynamicFilterOptions: Record<string, string[]>;
95104
}
96105
97106
/** The typed props object (available in <script setup> via defineProps). */
@@ -189,6 +198,29 @@ const numDatasets = computed(() => {
189198
return fileIds.size;
190199
});
191200
201+
/**
202+
* Determine whether to show the MultipleCellMethodsWarning.
203+
*
204+
* The warning should be displayed when:
205+
* 1. xarray mode is enabled (isXArrayMode is true)
206+
* 2. User has filtered down to a single dataset (numDatasets === 1)
207+
* 3. There are multiple variable_cell_methods options available in dynamicFilterOptions
208+
*
209+
* This indicates the user may need to further filter by variable_cell_methods
210+
* before calling to_dask().
211+
*/
212+
const shouldShowCellMethodsWarning = computed((): boolean => {
213+
if (!isXArrayMode.value) return false;
214+
if (numDatasets.value !== 1) return false;
215+
216+
// Don't show warning if user has already filtered by variable_cell_methods
217+
const hasFilteredCellMethods = (props.currentFilters['variable_cell_methods']?.length ?? 0) > 0;
218+
if (hasFilteredCellMethods) return false;
219+
220+
const cellMethodsOptions = props.dynamicFilterOptions['variable_cell_methods'];
221+
return !!(cellMethodsOptions && cellMethodsOptions.length > 1);
222+
});
223+
192224
/**
193225
* Generates the quick-start Python code snippet shown to users.
194226
*

src/components/__tests__/DatastoreDetail.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ describe('DatastoreDetail', () => {
375375
frequency: ['daily', 'monthly'],
376376
realm: ['ocean', 'atmosphere'],
377377
});
378-
expect(filters.props('rawData')).toEqual(mockDatastoreData);
378+
expect(filters.props('dynamicFilterOptions')).toBeDefined();
379379

380380
// Check DatastoreTable props
381381
const table = wrapper.findComponent({ name: 'DatastoreTable' });

0 commit comments

Comments
 (0)