From 364797ca705d1d83e2cf45357355d6136d16d8a8 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 7 Feb 2024 11:52:53 -0500 Subject: [PATCH 1/5] speling fxies --- lib/galaxy/webapps/galaxy/services/datasets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/services/datasets.py b/lib/galaxy/webapps/galaxy/services/datasets.py index 94a36ecaec25..09f6a29d4181 100644 --- a/lib/galaxy/webapps/galaxy/services/datasets.py +++ b/lib/galaxy/webapps/galaxy/services/datasets.py @@ -406,7 +406,7 @@ def show_storage( # not implemented on nestedobjectstores yet. percent_used = None except FileNotFoundError: - # uninitalized directory (emtpy) disk object store can cause this... + # uninitialized directory (empty) disk object store can cause this... percent_used = None quota_source = dataset.quota_source_info From c743c58264eccc53df3147e535967a10511fa681 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 7 Feb 2024 12:26:54 -0500 Subject: [PATCH 2/5] Modernize DatasetStorage.vue. --- client/src/api/datasets.ts | 2 + client/src/api/index.ts | 5 + .../Dataset/DatasetStorage/DatasetStorage.vue | 129 ++++++++---------- 3 files changed, 67 insertions(+), 69 deletions(-) diff --git a/client/src/api/datasets.ts b/client/src/api/datasets.ts index 642aa0c03a22..63b8b5714d9c 100644 --- a/client/src/api/datasets.ts +++ b/client/src/api/datasets.ts @@ -38,6 +38,8 @@ export async function getDatasets(options: GetDatasetsOptions = {}) { export const fetchDataset = fetcher.path("/api/datasets/{dataset_id}").method("get").create(); +export const fetchDatasetStorage = fetcher.path("/api/datasets/{dataset_id}/storage").method("get").create(); + export async function fetchDatasetDetails(params: { id: string }): Promise { const { data } = await fetchDataset({ dataset_id: params.id, view: "detailed" }); // We know that the server will return a DatasetDetails object because of the view parameter diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 0003833819fa..83da2479adbc 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -27,6 +27,11 @@ export type DatasetSummary = components["schemas"]["HDASummary"]; */ export type DatasetDetails = components["schemas"]["HDADetailed"]; +/** + * Contains storage (object store, quota, etc..) details for a dataset. + */ +export type DatasetStorageDetails = components["schemas"]["DatasetStorageDetails"]; + /** * Represents a HistoryDatasetAssociation with either summary or detailed information. */ diff --git a/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue b/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue index 6e8dd47508e7..584f987f7ab1 100644 --- a/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue +++ b/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue @@ -1,3 +1,63 @@ + + - - From d1c57e3eb1e4d4a206ae94d0180b110dcffa1dd2 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 7 Feb 2024 12:53:39 -0500 Subject: [PATCH 3/5] Modernize the dataset storage API a bit. --- client/src/api/schema/schema.ts | 24 +++++++++++++------ .../webapps/galaxy/services/datasets.py | 12 ++++++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 2ef36c3e4001..8535853d0e57 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -2918,6 +2918,19 @@ export interface components { private: boolean; quota: components["schemas"]["QuotaModel"]; }; + /** ConcreteObjectStoreQuotaSourceDetails */ + ConcreteObjectStoreQuotaSourceDetails: { + /** + * Enabled + * @description Whether the object store tracks quota on the data (independent of Galaxy's configuration) + */ + enabled: boolean; + /** + * Source + * @description The quota source label corresponding to the object store the dataset is stored in (or would be stored in) + */ + source: string | null; + }; /** ContentsObject */ ContentsObject: { /** @@ -3768,9 +3781,9 @@ export interface components { DatasetStorageDetails: { /** * Badges - * @description A mapping of object store labels to badges describing object store properties. + * @description A list of badges describing object store properties for concrete object store dataset is stored in. */ - badges: Record[]; + badges: components["schemas"]["BadgeDict"][]; /** * Dataset State * @description The model state of the supplied dataset instance. @@ -3801,11 +3814,8 @@ export interface components { * @description The percentage indicating how full the store is. */ percent_used: number | null; - /** - * Quota - * @description Information about quota sources around dataset storage. - */ - quota: Record; + /** @description Information about quota sources around dataset storage. */ + quota: components["schemas"]["ConcreteObjectStoreQuotaSourceDetails"]; /** * Shareable * @description Is this dataset shareable. diff --git a/lib/galaxy/webapps/galaxy/services/datasets.py b/lib/galaxy/webapps/galaxy/services/datasets.py index 09f6a29d4181..c185edd3636d 100644 --- a/lib/galaxy/webapps/galaxy/services/datasets.py +++ b/lib/galaxy/webapps/galaxy/services/datasets.py @@ -45,6 +45,7 @@ ) from galaxy.managers.lddas import LDDAManager from galaxy.model.base import transaction +from galaxy.objectstore.badges import BadgeDict from galaxy.schema import ( FilterQueryParams, SerializationParams, @@ -142,9 +143,11 @@ class DatasetStorageDetails(Model): shareable: bool = Field( description="Is this dataset shareable.", ) - quota: dict = Field(description="Information about quota sources around dataset storage.") - badges: List[Dict[str, Any]] = Field( - description="A mapping of object store labels to badges describing object store properties." + quota: ConcreteObjectStoreQuotaSourceDetails = Field( + description="Information about quota sources around dataset storage." + ) + badges: List[BadgeDict] = Field( + description="A list of badges describing object store properties for concrete object store dataset is stored in." ) @@ -413,8 +416,7 @@ def show_storage( quota = ConcreteObjectStoreQuotaSourceDetails( source=quota_source.label, enabled=quota_source.use, - ).model_dump() # TODO: could we bypass the dump? - + ) dataset_state = dataset.state hashes = [h.to_dict() for h in dataset.hashes] sources = [s.to_dict() for s in dataset.sources] From 6fc4b8b47c7e48ef6c2b5298ce3f0e765f986148 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Thu, 8 Feb 2024 09:10:06 -0500 Subject: [PATCH 4/5] Fixup unit tests. --- client/src/api/schema/__mocks__/fetcher.ts | 3 +- .../DatasetStorage/DatasetStorage.test.js | 30 ++++++++----------- .../Dataset/DatasetStorage/DatasetStorage.vue | 8 ++--- client/src/components/HistoryExport/Index.vue | 3 +- client/src/components/LoadingSpan.vue | 1 + .../ObjectStore/DescribeObjectStore.vue | 6 ++++ .../ShowSelectedObjectStore.test.js | 4 --- .../User/DiskUsage/DiskUsageSummary.test.ts | 14 +-------- 8 files changed, 29 insertions(+), 40 deletions(-) diff --git a/client/src/api/schema/__mocks__/fetcher.ts b/client/src/api/schema/__mocks__/fetcher.ts index 5af41cc7e146..56c8a56ed3ef 100644 --- a/client/src/api/schema/__mocks__/fetcher.ts +++ b/client/src/api/schema/__mocks__/fetcher.ts @@ -48,7 +48,8 @@ function getMockReturn(path: Path, method: Method, args: any[]) { } } - return null; + // if no mock has been setup, never resolve API request + return new Promise(() => {}); } function setMockReturn(path: Path | RegExp, method: Method, value: any) { diff --git a/client/src/components/Dataset/DatasetStorage/DatasetStorage.test.js b/client/src/components/Dataset/DatasetStorage/DatasetStorage.test.js index f8218abbe934..990b9a26871b 100644 --- a/client/src/components/Dataset/DatasetStorage/DatasetStorage.test.js +++ b/client/src/components/Dataset/DatasetStorage/DatasetStorage.test.js @@ -1,11 +1,13 @@ import { shallowMount } from "@vue/test-utils"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; import flushPromises from "flush-promises"; import { getLocalVue } from "tests/jest/helpers"; +import { mockFetcher } from "@/api/schema/__mocks__"; + import DatasetStorage from "./DatasetStorage"; +jest.mock("@/api/schema"); + const localVue = getLocalVue(); const TEST_STORAGE_API_RESPONSE_WITHOUT_ID = { @@ -13,17 +15,12 @@ const TEST_STORAGE_API_RESPONSE_WITHOUT_ID = { private: false, }; const TEST_DATASET_ID = "1"; -const TEST_STORAGE_URL = `/api/datasets/${TEST_DATASET_ID}/storage`; +const STORAGE_FETCH_URL = "/api/datasets/{dataset_id}/storage"; const TEST_ERROR_MESSAGE = "Opps all errors."; describe("DatasetStorage.vue", () => { - let axiosMock; let wrapper; - beforeEach(async () => { - axiosMock = new MockAdapter(axios); - }); - function mount() { wrapper = shallowMount(DatasetStorage, { propsData: { datasetId: TEST_DATASET_ID }, @@ -32,7 +29,7 @@ describe("DatasetStorage.vue", () => { } async function mountWithResponse(response) { - axiosMock.onGet(TEST_STORAGE_URL).reply(200, response); + mockFetcher.path(STORAGE_FETCH_URL).method("get").mock({ data: response }); mount(); await flushPromises(); } @@ -40,14 +37,18 @@ describe("DatasetStorage.vue", () => { it("test loading...", async () => { mount(); await wrapper.vm.$nextTick(); + console.log(wrapper.html()); expect(wrapper.findAll("loadingspan-stub").length).toBe(1); expect(wrapper.findAll("describeobjectstore-stub").length).toBe(0); }); it("test error rendering...", async () => { - axiosMock.onGet(TEST_STORAGE_URL).reply(400, { - err_msg: TEST_ERROR_MESSAGE, - }); + mockFetcher + .path(STORAGE_FETCH_URL) + .method("get") + .mock(() => { + throw Error(TEST_ERROR_MESSAGE); + }); mount(); await flushPromises(); expect(wrapper.findAll(".error").length).toBe(1); @@ -59,10 +60,5 @@ describe("DatasetStorage.vue", () => { await mountWithResponse(TEST_STORAGE_API_RESPONSE_WITHOUT_ID); expect(wrapper.findAll("loadingspan-stub").length).toBe(0); expect(wrapper.findAll("describeobjectstore-stub").length).toBe(1); - expect(wrapper.vm.storageInfo.private).toEqual(false); - }); - - afterEach(() => { - axiosMock.restore(); }); }); diff --git a/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue b/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue index 584f987f7ab1..90706afe28ea 100644 --- a/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue +++ b/client/src/components/Dataset/DatasetStorage/DatasetStorage.vue @@ -10,8 +10,8 @@ import DescribeObjectStore from "@/components/ObjectStore/DescribeObjectStore.vu interface DatasetStorageProps { datasetId: string; - datasetType: "hda" | "ldda"; - includeTitle: boolean; + datasetType?: "hda" | "ldda"; + includeTitle?: boolean; } const props = withDefaults(defineProps(), { @@ -48,8 +48,8 @@ watch( const datasetId = props.datasetId; const datasetType = props.datasetType; try { - const { data } = await fetchDatasetStorage({ dataset_id: datasetId, hda_ldda: datasetType }); - storageInfo.value = data; + const response = await fetchDatasetStorage({ dataset_id: datasetId, hda_ldda: datasetType }); + storageInfo.value = response.data; } catch (error) { errorMessage.value = errorMessageAsString(error); } diff --git a/client/src/components/HistoryExport/Index.vue b/client/src/components/HistoryExport/Index.vue index dfbe79cf5dc9..2b1552006b54 100644 --- a/client/src/components/HistoryExport/Index.vue +++ b/client/src/components/HistoryExport/Index.vue @@ -5,6 +5,7 @@ import { useFileSources } from "@/composables/fileSources"; import ToLink from "./ToLink.vue"; import ToRemoteFile from "./ToRemoteFile.vue"; +import LoadingSpan from "@/components/LoadingSpan.vue"; const { isLoading: initializingFileSources, hasWritable: hasWritableFileSources } = useFileSources(); @@ -18,7 +19,7 @@ const props = defineProps();

Export history archive

- + diff --git a/client/src/components/LoadingSpan.vue b/client/src/components/LoadingSpan.vue index c4c3b9d26b5e..3babd0a618d7 100644 --- a/client/src/components/LoadingSpan.vue +++ b/client/src/components/LoadingSpan.vue @@ -6,6 +6,7 @@ + +