mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-01 15:37:32 +08:00
Merge pull request #20914 from guerler/allow_creating_visualizations
Allow creation of visualizations without dataset
This commit is contained in:
@@ -29,16 +29,21 @@ export interface Plugin {
|
||||
html: string;
|
||||
logo?: string;
|
||||
name: string;
|
||||
params?: Record<string, ParamType>;
|
||||
target?: string;
|
||||
tags?: Array<string>;
|
||||
tests?: Array<TestType>;
|
||||
}
|
||||
|
||||
export interface ParamType {
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface PluginData {
|
||||
hdas: Array<Dataset>;
|
||||
}
|
||||
|
||||
export interface ParamType {
|
||||
export interface TestParamType {
|
||||
ftype?: string;
|
||||
label?: string;
|
||||
name: string;
|
||||
@@ -46,7 +51,7 @@ export interface ParamType {
|
||||
}
|
||||
|
||||
export interface TestType {
|
||||
param: ParamType;
|
||||
param: TestParamType;
|
||||
}
|
||||
|
||||
export async function fetchPlugins(datasetId?: string): Promise<Array<Plugin>> {
|
||||
|
||||
@@ -4,11 +4,20 @@ import { createPinia, defineStore, setActivePinia } from "pinia";
|
||||
import { getLocalVue } from "tests/jest/helpers";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { fetchPluginHistoryItems } from "@/api/plugins";
|
||||
import { fetchPlugin, fetchPluginHistoryItems } from "@/api/plugins";
|
||||
|
||||
import VisualizationCreate from "./VisualizationCreate.vue";
|
||||
import FormCardSticky from "@/components/Form/FormCardSticky.vue";
|
||||
|
||||
const PLUGIN = {
|
||||
name: "scatterplot",
|
||||
description: "A great scatterplot plugin.",
|
||||
html: "Scatterplot Plugin",
|
||||
logo: "/logo.png",
|
||||
help: "Some help text",
|
||||
tags: ["tag1", "tag2"],
|
||||
};
|
||||
|
||||
jest.mock("vue-router/composables", () => ({
|
||||
useRouter: () => ({
|
||||
push: jest.fn(),
|
||||
@@ -18,22 +27,13 @@ jest.mock("vue-router/composables", () => ({
|
||||
jest.mock("@/api/plugins", () => ({
|
||||
fetchPlugin: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
name: "scatterplot",
|
||||
description: "A great scatterplot plugin.",
|
||||
html: "Scatterplot Plugin",
|
||||
logo: "/logo.png",
|
||||
help: "Some help text",
|
||||
tags: ["tag1", "tag2"],
|
||||
params: { dataset_id: { required: true } },
|
||||
...PLUGIN,
|
||||
}),
|
||||
),
|
||||
fetchPluginHistoryItems: jest.fn(() => Promise.resolve({ hdas: [] })),
|
||||
}));
|
||||
|
||||
jest.mock("./utilities", () => ({
|
||||
getTestExtensions: jest.fn(() => ["txt"]),
|
||||
getTestUrls: jest.fn(() => [{ name: "Example", url: "https://example.com/data.txt" }]),
|
||||
}));
|
||||
|
||||
let mockedStore;
|
||||
jest.mock("@/stores/historyStore", () => ({
|
||||
useHistoryStore: () => mockedStore,
|
||||
@@ -95,3 +95,16 @@ it("adds hid to dataset names when fetching history items", async () => {
|
||||
{ id: "dataset2", name: "102: Second Dataset" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("displays create new visualization option if dataset is not required", async () => {
|
||||
fetchPlugin.mockResolvedValueOnce(PLUGIN);
|
||||
const wrapper = mount(VisualizationCreate, {
|
||||
localVue,
|
||||
propsData: {
|
||||
visualization: "scatterplot",
|
||||
},
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
const results = await wrapper.vm.doQuery();
|
||||
expect(results).toEqual([{ id: "", name: "Open visualization..." }]);
|
||||
});
|
||||
|
||||
@@ -3,12 +3,12 @@ import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, type Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router/composables";
|
||||
|
||||
import { type Dataset, fetchPlugin, fetchPluginHistoryItems, type Plugin } from "@/api/plugins";
|
||||
import { fetchPlugin, fetchPluginHistoryItems, type Plugin } from "@/api/plugins";
|
||||
import type { OptionType } from "@/components/SelectionField/types";
|
||||
import { useMarkdown } from "@/composables/markdown";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
|
||||
import { getTestExtensions, getTestUrls } from "./utilities";
|
||||
import { getRequiresDataset, getTestExtensions, getTestUrls } from "./utilities";
|
||||
|
||||
import VisualizationExamples from "./VisualizationExamples.vue";
|
||||
import Heading from "@/components/Common/Heading.vue";
|
||||
@@ -27,20 +27,20 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const errorMessage = ref("");
|
||||
const formatsVisible = ref(false);
|
||||
const plugin: Ref<Plugin | undefined> = ref();
|
||||
|
||||
const urlData = computed(() => getTestUrls(plugin.value));
|
||||
const extensions = computed(() => getTestExtensions(plugin.value));
|
||||
const formatsVisible = ref(false);
|
||||
|
||||
function addHidToName(hdas: Array<Dataset>) {
|
||||
return hdas.map((entry) => ({ id: entry.id, name: `${entry.hid}: ${entry.name}` }));
|
||||
}
|
||||
const requiresDataset = computed(() => getRequiresDataset(plugin.value));
|
||||
const testUrls = computed(() => getTestUrls(plugin.value));
|
||||
|
||||
async function doQuery() {
|
||||
if (currentHistoryId.value && plugin.value) {
|
||||
const data = await fetchPluginHistoryItems(plugin.value.name, currentHistoryId.value);
|
||||
return addHidToName(data.hdas);
|
||||
return [
|
||||
...(!requiresDataset.value ? [{ id: "", name: `Open visualization...` }] : []),
|
||||
...data.hdas.map((hda) => ({ id: hda.id, name: `${hda.hid}: ${hda.name}` })),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
@@ -51,7 +51,8 @@ async function getPlugin() {
|
||||
}
|
||||
|
||||
function onSelect(dataset: OptionType) {
|
||||
router.push(`/visualizations/display?visualization=${plugin.value?.name}&dataset_id=${dataset.id}`, {
|
||||
const query = dataset.id ? `&dataset_id=${dataset.id}` : "";
|
||||
router.push(`/visualizations/display?visualization=${plugin.value?.name}${query}`, {
|
||||
// @ts-ignore
|
||||
title: dataset.name,
|
||||
});
|
||||
@@ -73,11 +74,11 @@ defineExpose({ doQuery });
|
||||
:logo="plugin?.logo"
|
||||
:name="plugin?.html">
|
||||
<template v-slot:buttons>
|
||||
<VisualizationExamples :url-data="urlData" />
|
||||
<VisualizationExamples :url-data="testUrls" />
|
||||
</template>
|
||||
<div class="my-3">
|
||||
<SelectionField
|
||||
object-name="Select a dataset..."
|
||||
object-name="Make a selection..."
|
||||
object-title="Select to Visualize"
|
||||
object-type="history_dataset_id"
|
||||
:object-query="doQuery"
|
||||
|
||||
@@ -23,16 +23,17 @@ const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
const srcWithRoot = computed(() => {
|
||||
let url = "";
|
||||
if (props.visualization === "trackster") {
|
||||
if (props.datasetId) {
|
||||
url = `/visualization/trackster?dataset_id=${props.datasetId}`;
|
||||
} else {
|
||||
if (props.visualizationId) {
|
||||
url = `/visualization/trackster?id=${props.visualizationId}`;
|
||||
} else {
|
||||
url = `/visualization/trackster?dataset_id=${props.datasetId}`;
|
||||
}
|
||||
} else {
|
||||
if (props.datasetId) {
|
||||
url = `/plugins/visualizations/${props.visualization}/show?dataset_id=${props.datasetId}`;
|
||||
} else {
|
||||
if (props.visualizationId) {
|
||||
url = `/plugins/visualizations/${props.visualization}/saved?id=${props.visualizationId}`;
|
||||
} else {
|
||||
const query = props.datasetId ? `?dataset_id=${props.datasetId}` : "";
|
||||
url = `/plugins/visualizations/${props.visualization}/show${query}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { getFilename, getTestUrls } from "./utilities";
|
||||
import { getFilename, getRequiresDataset, getTestUrls } from "./utilities";
|
||||
|
||||
describe("Utility Functions", () => {
|
||||
describe("getTestUrls", () => {
|
||||
describe("getRequiresDataset and getTestUrls", () => {
|
||||
it("return wether the visualization requires a dataset or not", () => {
|
||||
expect(getRequiresDataset({})).toEqual(false);
|
||||
const plugin = {
|
||||
params: { dataset_id: { required: true } },
|
||||
};
|
||||
expect(getRequiresDataset(plugin)).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns empty array when plugin is undefined", () => {
|
||||
expect(getTestUrls()).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { Plugin } from "@/api/plugins";
|
||||
|
||||
export function getRequiresDataset(plugin?: Plugin): boolean {
|
||||
return plugin?.params?.dataset_id?.required || false;
|
||||
}
|
||||
|
||||
export function getTestExtensions(plugin?: Plugin): string[] {
|
||||
const results: string[] = [];
|
||||
if (plugin?.data_sources) {
|
||||
|
||||
@@ -38,7 +38,7 @@ heatmap:
|
||||
version: 0.0.15
|
||||
jupyterlite:
|
||||
package: "@galaxyproject/jupyterlite"
|
||||
version: 0.0.12
|
||||
version: 0.0.16
|
||||
kepler:
|
||||
package: "@galaxyproject/kepler"
|
||||
version: 0.0.3
|
||||
|
||||
@@ -72,15 +72,11 @@ class VisualizationsConfigParser:
|
||||
log.info("Visualizations plugin disabled: %s. Skipping...", returned["name"])
|
||||
return None
|
||||
|
||||
# record the embeddable flag - defaults to False
|
||||
returned["embeddable"] = False
|
||||
if "embeddable" in xml_tree.attrib:
|
||||
returned["embeddable"] = asbool(xml_tree.attrib.get("embeddable"))
|
||||
|
||||
# record the visible flag - defaults to False
|
||||
returned["hidden"] = False
|
||||
if "hidden" in xml_tree.attrib:
|
||||
returned["hidden"] = asbool(xml_tree.attrib.get("hidden"))
|
||||
# record boolean flags - defaults to False
|
||||
for keyword in ["embeddable", "hidden"]:
|
||||
returned[keyword] = False
|
||||
if keyword in xml_tree.attrib:
|
||||
returned[keyword] = asbool(xml_tree.attrib.get(keyword))
|
||||
|
||||
# a (for now) text description of what the visualization does
|
||||
description = xml_tree.find("description")
|
||||
|
||||
@@ -116,6 +116,7 @@ class VisualizationPlugin(ServesTemplatesPluginMixin):
|
||||
"tags": self.config.get("tags"),
|
||||
"title": self.config.get("title"),
|
||||
"target": self.config.get("render_target", "galaxy_main"),
|
||||
"params": self.config.get("params"),
|
||||
"embeddable": self.config.get("embeddable"),
|
||||
"entry_point": self.config.get("entry_point"),
|
||||
"settings": self.config.get("settings"),
|
||||
|
||||
Reference in New Issue
Block a user