diff --git a/client/src/api/datasets.ts b/client/src/api/datasets.ts index 8bdae1e2c012..c6096ea18f65 100644 --- a/client/src/api/datasets.ts +++ b/client/src/api/datasets.ts @@ -1,3 +1,4 @@ +import axios from "axios"; import type { FetchArgType } from "openapi-typescript-fetch"; import { HDADetailed } from "@/api"; @@ -88,3 +89,9 @@ export function getCompositeDatasetLink(historyDatasetId: string, path: string) export type DatasetExtraFiles = components["schemas"]["DatasetExtraFiles"]; export const fetchDatasetExtraFiles = fetcher.path("/api/datasets/{dataset_id}/extra_files").method("get").create(); + +export async function fetchDatasetAttributes(datasetId: string) { + const { data } = await axios.get(withPrefix(`/dataset/get_edit?dataset_id=${datasetId}`)); + + return data; +} diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 6ee22a2590b2..ec217a321012 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -235,3 +235,10 @@ export function userOwnsHistory(user: User | AnonymousUser | null, history: AnyH function hasOwner(history: AnyHistory): history is HistorySummaryExtended { return "user_id" in history && history.user_id !== null; } + +export type DatasetHash = components["schemas"]["DatasetHash"]; + +export type DatasetTransform = { + action: "to_posix_lines" | "spaces_to_tabs" | "datatype_groom"; + datatype_ext: "bam" | "qname_sorted.bam" | "qname_input_sorted.bam" | "isa-tab" | "isa-json"; +}; diff --git a/client/src/api/jobs.ts b/client/src/api/jobs.ts index d18644d3acd3..16a4018853c6 100644 --- a/client/src/api/jobs.ts +++ b/client/src/api/jobs.ts @@ -9,4 +9,11 @@ export const fetchJobDestinationParams = fetcher.path("/api/jobs/{job_id}/destin export const jobsFetcher = fetcher.path("/api/jobs").method("get").create(); -export const jobsReportError = fetcher.path("/api/jobs/{job_id}/error").method("post").create(); +export type ShowFullJobResponse = components["schemas"]["ShowFullJobResponse"]; +export type JobDetails = components["schemas"]["ShowFullJobResponse"] | components["schemas"]["EncodedJobDetails"]; +export const fetchJobDetails = fetcher.path("/api/jobs/{job_id}").method("get").create(); + +export type JobInputSummary = components["schemas"]["JobInputSummary"]; +export const fetchJobCommonProblems = fetcher.path("/api/jobs/{job_id}/common_problems").method("get").create(); + +export const postJobErrorReport = fetcher.path("/api/jobs/{job_id}/error").method("post").create(); diff --git a/client/src/components/DatasetInformation/DatasetAttributes.test.js b/client/src/components/DatasetInformation/DatasetAttributes.test.ts similarity index 53% rename from client/src/components/DatasetInformation/DatasetAttributes.test.js rename to client/src/components/DatasetInformation/DatasetAttributes.test.ts index 7862c91da571..c983536cbf81 100644 --- a/client/src/components/DatasetInformation/DatasetAttributes.test.js +++ b/client/src/components/DatasetInformation/DatasetAttributes.test.ts @@ -1,43 +1,48 @@ +import { createTestingPinia } from "@pinia/testing"; +import { getLocalVue } from "@tests/jest/helpers"; import { mount } 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 { setActivePinia } from "pinia"; -import MockProvider from "../providers/MockProvider"; -import DatasetAttributes from "./DatasetAttributes"; +import DatasetAttributes from "./DatasetAttributes.vue"; + +const DATASET_ID = "dataset_id"; const localVue = getLocalVue(); -async function buildWrapper(conversion_disable = false) { - const wrapper = mount(DatasetAttributes, { +async function mountDatasetAttributes(conversion_disable = false) { + const pinia = createTestingPinia(); + setActivePinia(pinia); + + const axiosMock = new MockAdapter(axios); + axiosMock.onPut(`/dataset/set_edit`).reply(200, { message: "success", status: "success" }); + axiosMock.onGet(`/dataset/get_edit?dataset_id=${DATASET_ID}`).reply(200, { + attribute_inputs: [{ name: "attribute_text", type: "text" }], + conversion_inputs: [{ name: "conversion_text", type: "text" }], + conversion_disable: conversion_disable, + datatype_inputs: [{ name: "datatype_text", type: "text" }], + permission_inputs: [{ name: "permission_text", type: "text" }], + }); + + const wrapper = mount(DatasetAttributes as object, { propsData: { - datasetId: "dataset_id", + datasetId: DATASET_ID, }, localVue, - stubs: { - DatasetAttributesProvider: MockProvider({ - result: { - attribute_inputs: [{ name: "attribute_text", type: "text" }], - conversion_inputs: [{ name: "conversion_text", type: "text" }], - conversion_disable: conversion_disable, - datatype_inputs: [{ name: "datatype_text", type: "text" }], - permission_inputs: [{ name: "permission_text", type: "text" }], - }, - }), - FontAwesomeIcon: false, - FormElement: false, - }, + pinia, }); + await flushPromises(); + return wrapper; } describe("DatasetAttributes", () => { it("check rendering", async () => { - const axiosMock = new MockAdapter(axios); - axiosMock.onPut(`/dataset/set_edit`).reply(200, { message: "success", status: "success" }); - const wrapper = await buildWrapper(); + const wrapper = await mountDatasetAttributes(); + expect(wrapper.findAll("button").length).toBe(6); expect(wrapper.findAll("#attribute_text").length).toBe(1); expect(wrapper.findAll("#conversion_text").length).toBe(1); @@ -45,14 +50,19 @@ describe("DatasetAttributes", () => { expect(wrapper.findAll("#permission_text").length).toBe(1); expect(wrapper.findAll(".tab-pane").length).toBe(3); expect(wrapper.findAll(".ui-portlet-section").length).toBe(2); - const $button = wrapper.find("#dataset-attributes-default-save"); - await $button.trigger("click"); + + const saveButton = wrapper.find("#dataset-attributes-default-save"); + + await saveButton.trigger("click"); + await flushPromises(); + expect(wrapper.find("[role=alert]").text()).toBe("success"); }); it("check rendering without conversion option", async () => { - const wrapper = await buildWrapper(true); + const wrapper = await mountDatasetAttributes(true); + expect(wrapper.findAll("button").length).toBe(5); expect(wrapper.findAll("#attribute_text").length).toBe(1); expect(wrapper.findAll("#conversion_text").length).toBe(0); diff --git a/client/src/components/DatasetInformation/DatasetAttributes.vue b/client/src/components/DatasetInformation/DatasetAttributes.vue index e197e126b6a9..1bad64943b6e 100644 --- a/client/src/components/DatasetInformation/DatasetAttributes.vue +++ b/client/src/components/DatasetInformation/DatasetAttributes.vue @@ -1,157 +1,224 @@ + + +
+ - +
+ + + {{ localize("Save") }} + +
+ + +
+ + diff --git a/client/src/components/DatasetInformation/DatasetDetails.vue b/client/src/components/DatasetInformation/DatasetDetails.vue index f9f2606669ed..667e33d2c802 100644 --- a/client/src/components/DatasetInformation/DatasetDetails.vue +++ b/client/src/components/DatasetInformation/DatasetDetails.vue @@ -1,112 +1,164 @@ - + + + diff --git a/client/src/components/DatasetInformation/DatasetError.test.js b/client/src/components/DatasetInformation/DatasetError.test.js deleted file mode 100644 index b46c8e625b0b..000000000000 --- a/client/src/components/DatasetInformation/DatasetError.test.js +++ /dev/null @@ -1,92 +0,0 @@ -import { mount } from "@vue/test-utils"; -import { createPinia } from "pinia"; -import { useUserStore } from "stores/userStore"; -import { getLocalVue } from "tests/jest/helpers"; - -import MockProvider from "../providers/MockProvider"; -import DatasetError from "./DatasetError"; - -jest.mock("components/providers", () => { - return {}; // stubbed below -}); - -const localVue = getLocalVue(); - -function buildWrapper(has_duplicate_inputs = true, has_empty_inputs = true, user_email = "") { - const pinia = createPinia(); - const wrapper = mount(DatasetError, { - propsData: { - datasetId: "dataset_id", - }, - localVue, - stubs: { - JobDetailsProvider: MockProvider({ - result: { - tool_id: "tool_id", - tool_stderr: "tool_stderr", - job_stderr: "job_stderr", - job_messages: [{ desc: "message_1" }, { desc: "message_2" }], - user_email: user_email, - }, - }), - JobProblemProvider: MockProvider({ - result: { has_duplicate_inputs: has_duplicate_inputs, has_empty_inputs: has_empty_inputs }, - }), - DatasetProvider: MockProvider({ - result: { id: "dataset_id", creating_job: "creating_job" }, - }), - FontAwesomeIcon: false, - FormElement: false, - }, - pinia, - }); - - const userStore = useUserStore(); - userStore.currentUser = { email: user_email || "email" }; - - return wrapper; -} - -describe("DatasetError", () => { - it("check props with common problems", async () => { - const wrapper = buildWrapper(); - expect(wrapper.find("#dataset-error-tool-id").text()).toBe("tool_id"); - expect(wrapper.find("#dataset-error-tool-stderr").text()).toBe("tool_stderr"); - expect(wrapper.find("#dataset-error-job-stderr").text()).toBe("job_stderr"); - const messages = wrapper.findAll("#dataset-error-job-messages .code"); - expect(messages.at(0).text()).toBe("message_1"); - expect(messages.at(1).text()).toBe("message_2"); - expect(wrapper.find("#dataset-error-has-empty-inputs")).toBeDefined(); - expect(wrapper.find("#dataset-error-has-duplicate-inputs")).toBeDefined(); - }); - - it("check props without common problems", async () => { - const wrapper = buildWrapper(false, false, "user_email"); - expect(wrapper.find("#dataset-error-tool-id").text()).toBe("tool_id"); - expect(wrapper.find("#dataset-error-tool-stderr").text()).toBe("tool_stderr"); - expect(wrapper.find("#dataset-error-job-stderr").text()).toBe("job_stderr"); - expect(wrapper.findAll("#dataset-error-has-empty-inputs").length).toBe(0); - expect(wrapper.findAll("#dataset-error-has-duplicate-inputs").length).toBe(0); - expect(wrapper.findAll("#dataset-error-email").length).toBe(0); - }); - - it("hides form fields and button on success", async () => { - const wrapper = buildWrapper(); - const fieldsAndButton = "#fieldsAndButton"; - expect(wrapper.find(fieldsAndButton).exists()).toBe(true); - await wrapper.setData({ resultMessages: [["message", "success"]] }); - expect(wrapper.find(fieldsAndButton).exists()).toBe(false); - }); - - it("does not hide form fields and button on error", async () => { - const wrapper = buildWrapper(); - const fieldsAndButton = "#fieldsAndButton"; - expect(wrapper.find(fieldsAndButton).exists()).toBe(true); - const messages = [ - ["message", "success"], - ["message", "danger"], - ]; // at least one has "danger" - await wrapper.setData({ resultMessages: messages }); - expect(wrapper.find(fieldsAndButton).exists()).toBe(true); - }); -}); diff --git a/client/src/components/DatasetInformation/DatasetError.test.ts b/client/src/components/DatasetInformation/DatasetError.test.ts new file mode 100644 index 000000000000..c3213e8dc6fc --- /dev/null +++ b/client/src/components/DatasetInformation/DatasetError.test.ts @@ -0,0 +1,125 @@ +import { mount } from "@vue/test-utils"; +import flushPromises from "flush-promises"; +import { createPinia } from "pinia"; +import { getLocalVue } from "tests/jest/helpers"; + +import { mockFetcher } from "@/api/schema/__mocks__"; +import { useUserStore } from "@/stores/userStore"; + +import DatasetError from "./DatasetError.vue"; + +const localVue = getLocalVue(); + +const DATASET_ID = "dataset_id"; + +async function montDatasetError(has_duplicate_inputs = true, has_empty_inputs = true, user_email = "") { + const pinia = createPinia(); + + mockFetcher + .path("/api/datasets/{dataset_id}") + .method("get") + .mock({ + data: { + id: DATASET_ID, + creating_job: "creating_job", + }, + }); + + mockFetcher + .path("/api/jobs/{job_id}") + .method("get") + .mock({ + data: { + tool_id: "tool_id", + tool_stderr: "tool_stderr", + job_stderr: "job_stderr", + job_messages: [{ desc: "message_1" }, { desc: "message_2" }], + user_email: user_email, + }, + }); + + mockFetcher + .path("/api/jobs/{job_id}/common_problems") + .method("get") + .mock({ + data: { + has_duplicate_inputs: has_duplicate_inputs, + has_empty_inputs: has_empty_inputs, + }, + }); + + const wrapper = mount(DatasetError as object, { + propsData: { + datasetId: DATASET_ID, + }, + localVue, + pinia, + }); + + const userStore = useUserStore(); + userStore.currentUser = { + email: user_email || "email", + id: "user_id", + tags_used: [], + isAnonymous: false, + total_disk_usage: 0, + }; + + await flushPromises(); + + return wrapper; +} + +describe("DatasetError", () => { + it("check props with common problems", async () => { + const wrapper = await montDatasetError(); + + expect(wrapper.find("#dataset-error-tool-id").text()).toBe("tool_id"); + expect(wrapper.find("#dataset-error-tool-stderr").text()).toBe("tool_stderr"); + expect(wrapper.find("#dataset-error-job-stderr").text()).toBe("job_stderr"); + + const messages = wrapper.findAll("#dataset-error-job-messages .code"); + expect(messages.at(0).text()).toBe("message_1"); + expect(messages.at(1).text()).toBe("message_2"); + + expect(wrapper.find("#dataset-error-has-empty-inputs")).toBeDefined(); + expect(wrapper.find("#dataset-error-has-duplicate-inputs")).toBeDefined(); + }); + + it("check props without common problems", async () => { + const wrapper = await montDatasetError(false, false, "user_email"); + + expect(wrapper.find("#dataset-error-tool-id").text()).toBe("tool_id"); + expect(wrapper.find("#dataset-error-tool-stderr").text()).toBe("tool_stderr"); + expect(wrapper.find("#dataset-error-job-stderr").text()).toBe("job_stderr"); + + expect(wrapper.findAll("#dataset-error-has-empty-inputs").length).toBe(0); + expect(wrapper.findAll("#dataset-error-has-duplicate-inputs").length).toBe(0); + expect(wrapper.findAll("#dataset-error-email").length).toBe(0); + }); + + it("hides form fields and button on success", async () => { + const wrapper = await montDatasetError(); + + mockFetcher + .path("/api/jobs/{job_id}/error") + .method("post") + .mock({ + data: { + messages: ["message", "success"], + }, + }); + + const FormAndSubmitButton = "#dataset-error-form"; + expect(wrapper.find(FormAndSubmitButton).exists()).toBe(true); + + const submitButton = "#dataset-error-submit"; + expect(wrapper.find(submitButton).exists()).toBe(true); + + await wrapper.find(submitButton).trigger("click"); + + await flushPromises(); + + expect(wrapper.find(FormAndSubmitButton).exists()).toBe(false); + }); +}); diff --git a/client/src/components/DatasetInformation/DatasetError.vue b/client/src/components/DatasetInformation/DatasetError.vue index 0deb037e7b1b..0c86dada34d9 100644 --- a/client/src/components/DatasetInformation/DatasetError.vue +++ b/client/src/components/DatasetInformation/DatasetError.vue @@ -1,157 +1,202 @@ - - - + + diff --git a/client/src/components/DatasetInformation/DatasetErrorDetails.vue b/client/src/components/DatasetInformation/DatasetErrorDetails.vue index 8707f6b78d11..729c63ccd9c5 100644 --- a/client/src/components/DatasetInformation/DatasetErrorDetails.vue +++ b/client/src/components/DatasetInformation/DatasetErrorDetails.vue @@ -1,46 +1,52 @@ + + - - diff --git a/client/src/components/DatasetInformation/DatasetHash.vue b/client/src/components/DatasetInformation/DatasetHash.vue index cff960b49b0a..ee5e3f41aa60 100644 --- a/client/src/components/DatasetInformation/DatasetHash.vue +++ b/client/src/components/DatasetInformation/DatasetHash.vue @@ -1,16 +1,15 @@ + + - - diff --git a/client/src/components/DatasetInformation/DatasetHashes.vue b/client/src/components/DatasetInformation/DatasetHashes.vue index be4420236909..fe115a623de8 100644 --- a/client/src/components/DatasetInformation/DatasetHashes.vue +++ b/client/src/components/DatasetInformation/DatasetHashes.vue @@ -1,3 +1,15 @@ + + - - -