Skip to content

Commit 72a0782

Browse files
create a history datasets store that caches compact dataset summaries
These are scoped to a `historyId`, `filterText` and the last `update_time`. This is meant to be used in tandem with a newly added `useHistoryDatasets` composable which keeps track of the current history id, text and update time.
1 parent fc2ac14 commit 72a0782

File tree

4 files changed

+208
-129
lines changed

4 files changed

+208
-129
lines changed

client/src/components/Collections/CollectionCreatorIndex.vue

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import { computed, ref, watch } from "vue";
77
import { type CreateNewCollectionPayload, type HDCASummary, type HistoryItemSummary, isHDCA } from "@/api";
88
import { createHistoryDatasetCollectionInstanceFull, type SampleSheetCollectionType } from "@/api/datasetCollections";
99
import type { ExtendedCollectionType } from "@/components/Form/Elements/FormData/types";
10-
import { useCollectionBuilderItemsStore } from "@/stores/collectionBuilderItemsStore";
10+
import { useHistoryDatasets } from "@/composables/useHistoryDatasets";
1111
import { useHistoryItemsStore } from "@/stores/historyItemsStore";
12-
import { useHistoryStore } from "@/stores/historyStore";
1312
import localize from "@/utils/localization";
1413
import { orList } from "@/utils/strings";
1514
import { stateIsTerminal } from "@/utils/utils";
@@ -62,20 +61,17 @@ const createCollectionError = ref<string | null>(null);
6261
const createdCollection = ref<any>(null);
6362
6463
// History items variables
65-
const historyItemsError = ref<string | null>(null);
66-
const collectionItemsStore = useCollectionBuilderItemsStore();
67-
const historyStore = useHistoryStore();
68-
const history = computed(() => historyStore.getHistoryById(props.historyId));
69-
const historyId = computed(() => props.historyId);
70-
const localFilterText = computed(() => props.filterText || "");
71-
const historyUpdateTime = computed(() => history.value?.update_time);
72-
const isFetchingItems = computed(() => collectionItemsStore.isFetching[localFilterText.value]);
73-
const historyDatasets = computed(() => {
74-
if (collectionItemsStore.cachedDatasetsForFilterText) {
75-
return collectionItemsStore.cachedDatasetsForFilterText[localFilterText.value] || [];
76-
} else {
77-
return [];
78-
}
64+
const {
65+
datasets: historyDatasets,
66+
isFetching: isFetchingItems,
67+
error: historyItemsError,
68+
initialFetchDone: initialFetch,
69+
history,
70+
} = useHistoryDatasets({
71+
historyId: () => props.historyId,
72+
filterText: () => props.filterText || "",
73+
enabled: () => localShowToggle.value,
74+
immediate: false,
7975
});
8076
const pairedOrUnpairedSupportedCollectionType = computed<SupportedPairedOrPairedBuilderCollectionTypes | null>(() => {
8177
if (
@@ -87,35 +83,12 @@ const pairedOrUnpairedSupportedCollectionType = computed<SupportedPairedOrPaired
8783
}
8884
});
8985
90-
/** Flag for the initial fetch of history items */
91-
const initialFetch = ref(false);
92-
9386
/** Whether a list of items was selected to create a collection from */
9487
const fromSelection = computed(() => !!props.selectedItems?.length);
9588
9689
/** Items to create the collection from */
9790
const creatorItems = computed(() => (fromSelection.value ? props.selectedItems : historyDatasets.value));
9891
99-
watch(
100-
() => localShowToggle.value,
101-
async (show) => {
102-
if (show) {
103-
await fetchHistoryDatasets();
104-
if (!initialFetch.value) {
105-
initialFetch.value = true;
106-
}
107-
}
108-
},
109-
{ immediate: true },
110-
);
111-
112-
// Fetch items when history ID or update time changes, only if localShowToggle is true
113-
watch([historyId, historyUpdateTime, localFilterText], async () => {
114-
if (localShowToggle.value) {
115-
await fetchHistoryDatasets();
116-
}
117-
});
118-
11992
// If there is a change in `historyDatasets`, but we have selected items, we should update the selected items
12093
watch(
12194
() => historyDatasets.value,
@@ -202,20 +175,6 @@ watch(
202175
},
203176
);
204177
205-
async function fetchHistoryDatasets() {
206-
const { error } = await collectionItemsStore.fetchDatasetsForFiltertext(
207-
historyId.value,
208-
historyUpdateTime.value,
209-
localFilterText.value,
210-
);
211-
if (error) {
212-
historyItemsError.value = error;
213-
console.error("Error fetching history items:", historyItemsError.value);
214-
} else {
215-
historyItemsError.value = null;
216-
}
217-
}
218-
219178
function hideCreator() {
220179
localShowToggle.value = false;
221180
emit("on-hide");
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* Composable for fetching and watching history datasets based on filter criteria.
3+
* This encapsulates the logic for fetching datasets when history ID, update time,
4+
* or filter text changes, making it reusable across components like `CollectionCreatorIndex`
5+
* (and in the future `ChatGXY`).
6+
*/
7+
import { type MaybeRefOrGetter, toValue } from "@vueuse/shared";
8+
import { computed, ref, watch } from "vue";
9+
10+
import { useHistoryDatasetsStore } from "@/stores/historyDatasetsStore";
11+
import { useHistoryStore } from "@/stores/historyStore";
12+
13+
interface UseHistoryDatasetsOptions {
14+
/** The history ID to fetch datasets for */
15+
historyId: MaybeRefOrGetter<string>;
16+
/** Optional filter text to apply when fetching datasets */
17+
filterText?: MaybeRefOrGetter<string>;
18+
/** Whether fetching is enabled. When false, watchers won't trigger fetches. Defaults to `true`. */
19+
enabled?: MaybeRefOrGetter<boolean>;
20+
/** Whether to fetch immediately when the composable is initialized. Defaults to `true`. */
21+
immediate?: boolean;
22+
}
23+
24+
export function useHistoryDatasets(options: UseHistoryDatasetsOptions) {
25+
const { historyId, filterText = "", enabled = true, immediate = true } = options;
26+
27+
const historyStore = useHistoryStore();
28+
const historyDatasetsStore = useHistoryDatasetsStore();
29+
30+
// Internal state
31+
const error = ref<string | null>(null);
32+
const initialFetchDone = ref(false);
33+
34+
// Computed values from options
35+
const historyIdValue = computed(() => toValue(historyId));
36+
const filterTextValue = computed(() => toValue(filterText));
37+
const isEnabled = computed(() => toValue(enabled));
38+
39+
// Get history and its update time from the history store
40+
const history = computed(() => historyStore.getHistoryById(historyIdValue.value));
41+
const historyUpdateTime = computed(() => history.value?.update_time);
42+
43+
// Computed values from the store
44+
const isFetching = computed(() => historyDatasetsStore.isFetching[filterTextValue.value] ?? false);
45+
const datasets = computed(() => {
46+
if (historyDatasetsStore.cachedDatasetsForFilterText) {
47+
return historyDatasetsStore.cachedDatasetsForFilterText[filterTextValue.value] || [];
48+
}
49+
return [];
50+
});
51+
52+
/**
53+
* Fetches history datasets using the current scope values.
54+
* Can be called manually or is triggered automatically by watchers.
55+
*/
56+
async function fetchDatasets() {
57+
const { error: fetchError } = await historyDatasetsStore.fetchDatasetsForFiltertext(
58+
historyIdValue.value,
59+
historyUpdateTime.value,
60+
filterTextValue.value,
61+
);
62+
63+
if (fetchError) {
64+
error.value = fetchError;
65+
console.error("Error fetching history datasets:", fetchError);
66+
} else {
67+
error.value = null;
68+
}
69+
70+
if (!initialFetchDone.value) {
71+
initialFetchDone.value = true;
72+
}
73+
}
74+
75+
// Watch for changes in scope and refetch when enabled
76+
watch([historyIdValue, historyUpdateTime, filterTextValue], async () => {
77+
if (isEnabled.value) {
78+
await fetchDatasets();
79+
}
80+
});
81+
82+
// Watch for enabled state changes
83+
watch(isEnabled, async (nowEnabled) => {
84+
if (nowEnabled) {
85+
await fetchDatasets();
86+
}
87+
});
88+
89+
// Initial fetch if immediate is true and enabled
90+
if (immediate && toValue(enabled)) {
91+
fetchDatasets();
92+
}
93+
94+
return {
95+
/** The fetched datasets for the current filter text */
96+
datasets,
97+
/** Whether datasets are currently being fetched */
98+
isFetching,
99+
/** Any error that occurred during fetching */
100+
error,
101+
/** Whether the initial fetch has completed */
102+
initialFetchDone,
103+
/** The history object from the history store */
104+
history,
105+
/** Manually trigger a fetch */
106+
fetchDatasets,
107+
};
108+
}

client/src/stores/collectionBuilderItemsStore.ts

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,8 @@
11
import { defineStore } from "pinia";
2-
import { ref, set } from "vue";
2+
import { ref } from "vue";
33

4-
import { GalaxyApi, type HDASummary, type HistoryItemSummary } from "@/api";
4+
import type { HistoryItemSummary } from "@/api";
55
import type { ShowElementExtensionFunction } from "@/components/Collections/common/useExtensionFilter";
6-
import { HistoryFilters } from "@/components/History/HistoryFilters";
7-
import { filtersToQueryValues } from "@/components/History/model/queries";
8-
import { errorMessageAsString } from "@/utils/simple-error";
9-
10-
const DEFAULT_FILTERS: Record<string, any> = { visible: true, deleted: false };
11-
12-
/**
13-
* Fetches datasets for the Collection Builder modal. It caches the datasets for each filter text.
14-
*/
15-
export const useCollectionBuilderItemsStore = defineStore("collectionBuilderItemsStore", () => {
16-
const cachedDatasetsForFilterText = ref<Record<string, HDASummary[]>>({});
17-
const errorMessage = ref<string | null>(null);
18-
const isFetching = ref<Record<string, boolean>>({});
19-
const lastFetch = ref<{ id: string; time: string; text: string } | null>(null);
20-
21-
async function fetchDatasetsForFiltertext(historyId: string, historyUpdateTime?: string, filterText = "") {
22-
if (isFetching.value[filterText]) {
23-
return { data: [], error: null };
24-
}
25-
26-
if (
27-
lastFetch.value &&
28-
lastFetch.value.id === historyId &&
29-
lastFetch.value.time === historyUpdateTime &&
30-
lastFetch.value.text === filterText
31-
) {
32-
return {
33-
data: cachedDatasetsForFilterText.value[filterText] || [],
34-
error: errorMessage.value,
35-
};
36-
}
37-
38-
set(isFetching.value, filterText, true);
39-
40-
const filterQuery = getFilterQuery(filterText);
41-
42-
// Note: Had "/api/histories/{history_id}/contents/datasets" before but it was returning datasets and collections
43-
const { data, error: apiError } = await GalaxyApi().GET("/api/histories/{history_id}/contents", {
44-
params: {
45-
path: { history_id: historyId },
46-
query: { ...filterQuery, v: "dev" },
47-
},
48-
});
49-
const returnedData = data as HDASummary[];
50-
51-
let error = null;
52-
if (apiError) {
53-
error = errorMessageAsString(apiError);
54-
errorMessage.value = error;
55-
} else {
56-
set(cachedDatasetsForFilterText.value, filterText, returnedData);
57-
}
58-
59-
set(isFetching.value, filterText, false);
60-
lastFetch.value = { id: historyId, time: historyUpdateTime || "", text: filterText };
61-
62-
return { data: returnedData, error };
63-
}
64-
65-
function getFilterQuery(filterText: string) {
66-
let filters = filterText ? HistoryFilters.getQueryDict(filterText) : DEFAULT_FILTERS;
67-
if (filters.history_content_type) {
68-
delete filters.history_content_type;
69-
}
70-
filters = { ...filters, history_content_type: "dataset" };
71-
return filtersToQueryValues(filters);
72-
}
73-
74-
return {
75-
cachedDatasetsForFilterText,
76-
isFetching,
77-
fetchDatasetsForFiltertext,
78-
};
79-
});
806

817
/**
828
* Stores the history items selected for collection building.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { defineStore } from "pinia";
2+
import { ref, set } from "vue";
3+
4+
import { GalaxyApi, type HDASummary } from "@/api";
5+
import { HistoryFilters } from "@/components/History/HistoryFilters";
6+
import { filtersToQueryValues } from "@/components/History/model/queries";
7+
import { errorMessageAsString } from "@/utils/simple-error";
8+
9+
const DEFAULT_FILTERS: Record<string, any> = { visible: true, deleted: false };
10+
11+
/**
12+
* Fetches compact dataset summaries. It caches the datasets for each filter text, for the current
13+
* history only.
14+
*
15+
* It is scoped to `historyId`, `historyUpdateTime` and `filterText`.
16+
*
17+
* **Note:** *This store does not track the current history - it is meant to be used in tandem with
18+
* the `useHistoryDatasets` composable*.
19+
*/
20+
export const useHistoryDatasetsStore = defineStore("historyDatasetsStore", () => {
21+
const cachedDatasetsForFilterText = ref<Record<string, HDASummary[]>>({});
22+
const errorMessage = ref<string | null>(null);
23+
const isFetching = ref<Record<string, boolean>>({});
24+
25+
/** The current scope of the cached datasets, defined by history ID, update time, and filter text */
26+
const currentScope = ref<{ id: string; time: string; text: string } | null>(null);
27+
28+
async function fetchDatasetsForFiltertext(historyId: string, historyUpdateTime?: string, filterText = "") {
29+
if (isFetching.value[filterText]) {
30+
return { data: [], error: null };
31+
}
32+
33+
if (
34+
currentScope.value &&
35+
currentScope.value.id === historyId &&
36+
currentScope.value.time === historyUpdateTime &&
37+
currentScope.value.text === filterText
38+
) {
39+
return {
40+
data: cachedDatasetsForFilterText.value[filterText] || [],
41+
error: errorMessage.value,
42+
};
43+
}
44+
45+
set(isFetching.value, filterText, true);
46+
47+
const filterQuery = getFilterQuery(filterText);
48+
49+
// Note: Had "/api/histories/{history_id}/contents/datasets" before but it was returning datasets and collections
50+
const { data, error: apiError } = await GalaxyApi().GET("/api/histories/{history_id}/contents", {
51+
params: {
52+
path: { history_id: historyId },
53+
query: { ...filterQuery, v: "dev" },
54+
},
55+
});
56+
const returnedData = data as HDASummary[];
57+
58+
let error = null;
59+
if (apiError) {
60+
error = errorMessageAsString(apiError);
61+
errorMessage.value = error;
62+
} else {
63+
set(cachedDatasetsForFilterText.value, filterText, returnedData);
64+
}
65+
66+
set(isFetching.value, filterText, false);
67+
currentScope.value = { id: historyId, time: historyUpdateTime || "", text: filterText };
68+
69+
return { data: returnedData, error };
70+
}
71+
72+
function getFilterQuery(filterText: string) {
73+
let filters = filterText ? HistoryFilters.getQueryDict(filterText) : DEFAULT_FILTERS;
74+
if (filters.history_content_type) {
75+
delete filters.history_content_type;
76+
}
77+
filters = { ...filters, history_content_type: "dataset" };
78+
return filtersToQueryValues(filters);
79+
}
80+
81+
return {
82+
cachedDatasetsForFilterText,
83+
isFetching,
84+
fetchDatasetsForFiltertext,
85+
};
86+
});

0 commit comments

Comments
 (0)